@miosa/cli 0.2.0

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 (294) hide show
  1. package/README.md +327 -0
  2. package/dist/bin/miosa.d.ts +3 -0
  3. package/dist/bin/miosa.d.ts.map +1 -0
  4. package/dist/bin/miosa.js +139 -0
  5. package/dist/bin/miosa.js.map +1 -0
  6. package/dist/client.d.ts +74 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +523 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/commands/agent.d.ts +18 -0
  11. package/dist/commands/agent.d.ts.map +1 -0
  12. package/dist/commands/agent.js +468 -0
  13. package/dist/commands/agent.js.map +1 -0
  14. package/dist/commands/alerts.d.ts +3 -0
  15. package/dist/commands/alerts.d.ts.map +1 -0
  16. package/dist/commands/alerts.js +41 -0
  17. package/dist/commands/alerts.js.map +1 -0
  18. package/dist/commands/api-keys.d.ts +3 -0
  19. package/dist/commands/api-keys.d.ts.map +1 -0
  20. package/dist/commands/api-keys.js +119 -0
  21. package/dist/commands/api-keys.js.map +1 -0
  22. package/dist/commands/api-resource.d.ts +20 -0
  23. package/dist/commands/api-resource.d.ts.map +1 -0
  24. package/dist/commands/api-resource.js +120 -0
  25. package/dist/commands/api-resource.js.map +1 -0
  26. package/dist/commands/apps.d.ts +3 -0
  27. package/dist/commands/apps.d.ts.map +1 -0
  28. package/dist/commands/apps.js +218 -0
  29. package/dist/commands/apps.js.map +1 -0
  30. package/dist/commands/audit.d.ts +3 -0
  31. package/dist/commands/audit.d.ts.map +1 -0
  32. package/dist/commands/audit.js +25 -0
  33. package/dist/commands/audit.js.map +1 -0
  34. package/dist/commands/auth.d.ts +3 -0
  35. package/dist/commands/auth.d.ts.map +1 -0
  36. package/dist/commands/auth.js +363 -0
  37. package/dist/commands/auth.js.map +1 -0
  38. package/dist/commands/backups.d.ts +3 -0
  39. package/dist/commands/backups.d.ts.map +1 -0
  40. package/dist/commands/backups.js +23 -0
  41. package/dist/commands/backups.js.map +1 -0
  42. package/dist/commands/checkpoints.d.ts +3 -0
  43. package/dist/commands/checkpoints.d.ts.map +1 -0
  44. package/dist/commands/checkpoints.js +33 -0
  45. package/dist/commands/checkpoints.js.map +1 -0
  46. package/dist/commands/computers.d.ts +3 -0
  47. package/dist/commands/computers.d.ts.map +1 -0
  48. package/dist/commands/computers.js +118 -0
  49. package/dist/commands/computers.js.map +1 -0
  50. package/dist/commands/config.d.ts +3 -0
  51. package/dist/commands/config.d.ts.map +1 -0
  52. package/dist/commands/config.js +114 -0
  53. package/dist/commands/config.js.map +1 -0
  54. package/dist/commands/connect.d.ts +3 -0
  55. package/dist/commands/connect.d.ts.map +1 -0
  56. package/dist/commands/connect.js +96 -0
  57. package/dist/commands/connect.js.map +1 -0
  58. package/dist/commands/containers.d.ts +3 -0
  59. package/dist/commands/containers.d.ts.map +1 -0
  60. package/dist/commands/containers.js +20 -0
  61. package/dist/commands/containers.js.map +1 -0
  62. package/dist/commands/cp.d.ts +3 -0
  63. package/dist/commands/cp.d.ts.map +1 -0
  64. package/dist/commands/cp.js +102 -0
  65. package/dist/commands/cp.js.map +1 -0
  66. package/dist/commands/cron.d.ts +3 -0
  67. package/dist/commands/cron.d.ts.map +1 -0
  68. package/dist/commands/cron.js +65 -0
  69. package/dist/commands/cron.js.map +1 -0
  70. package/dist/commands/databases.d.ts +3 -0
  71. package/dist/commands/databases.d.ts.map +1 -0
  72. package/dist/commands/databases.js +222 -0
  73. package/dist/commands/databases.js.map +1 -0
  74. package/dist/commands/db.d.ts +3 -0
  75. package/dist/commands/db.d.ts.map +1 -0
  76. package/dist/commands/db.js +174 -0
  77. package/dist/commands/db.js.map +1 -0
  78. package/dist/commands/deploy.d.ts +3 -0
  79. package/dist/commands/deploy.d.ts.map +1 -0
  80. package/dist/commands/deploy.js +579 -0
  81. package/dist/commands/deploy.js.map +1 -0
  82. package/dist/commands/desktop.d.ts +3 -0
  83. package/dist/commands/desktop.d.ts.map +1 -0
  84. package/dist/commands/desktop.js +276 -0
  85. package/dist/commands/desktop.js.map +1 -0
  86. package/dist/commands/dev.d.ts +3 -0
  87. package/dist/commands/dev.d.ts.map +1 -0
  88. package/dist/commands/dev.js +246 -0
  89. package/dist/commands/dev.js.map +1 -0
  90. package/dist/commands/doctor.d.ts +3 -0
  91. package/dist/commands/doctor.d.ts.map +1 -0
  92. package/dist/commands/doctor.js +241 -0
  93. package/dist/commands/doctor.js.map +1 -0
  94. package/dist/commands/domains.d.ts +3 -0
  95. package/dist/commands/domains.d.ts.map +1 -0
  96. package/dist/commands/domains.js +31 -0
  97. package/dist/commands/domains.js.map +1 -0
  98. package/dist/commands/enterprise-util.d.ts +37 -0
  99. package/dist/commands/enterprise-util.d.ts.map +1 -0
  100. package/dist/commands/enterprise-util.js +185 -0
  101. package/dist/commands/enterprise-util.js.map +1 -0
  102. package/dist/commands/exec.d.ts +3 -0
  103. package/dist/commands/exec.d.ts.map +1 -0
  104. package/dist/commands/exec.js +68 -0
  105. package/dist/commands/exec.js.map +1 -0
  106. package/dist/commands/functions.d.ts +3 -0
  107. package/dist/commands/functions.d.ts.map +1 -0
  108. package/dist/commands/functions.js +47 -0
  109. package/dist/commands/functions.js.map +1 -0
  110. package/dist/commands/gha-runners.d.ts +3 -0
  111. package/dist/commands/gha-runners.d.ts.map +1 -0
  112. package/dist/commands/gha-runners.js +33 -0
  113. package/dist/commands/gha-runners.js.map +1 -0
  114. package/dist/commands/groups.d.ts +3 -0
  115. package/dist/commands/groups.d.ts.map +1 -0
  116. package/dist/commands/groups.js +38 -0
  117. package/dist/commands/groups.js.map +1 -0
  118. package/dist/commands/host.d.ts +3 -0
  119. package/dist/commands/host.d.ts.map +1 -0
  120. package/dist/commands/host.js +74 -0
  121. package/dist/commands/host.js.map +1 -0
  122. package/dist/commands/hosts.d.ts +3 -0
  123. package/dist/commands/hosts.d.ts.map +1 -0
  124. package/dist/commands/hosts.js +90 -0
  125. package/dist/commands/hosts.js.map +1 -0
  126. package/dist/commands/link.d.ts +8 -0
  127. package/dist/commands/link.d.ts.map +1 -0
  128. package/dist/commands/link.js +124 -0
  129. package/dist/commands/link.js.map +1 -0
  130. package/dist/commands/login.d.ts +3 -0
  131. package/dist/commands/login.d.ts.map +1 -0
  132. package/dist/commands/login.js +172 -0
  133. package/dist/commands/login.js.map +1 -0
  134. package/dist/commands/logout.d.ts +3 -0
  135. package/dist/commands/logout.d.ts.map +1 -0
  136. package/dist/commands/logout.js +17 -0
  137. package/dist/commands/logout.js.map +1 -0
  138. package/dist/commands/logs.d.ts +3 -0
  139. package/dist/commands/logs.d.ts.map +1 -0
  140. package/dist/commands/logs.js +94 -0
  141. package/dist/commands/logs.js.map +1 -0
  142. package/dist/commands/ls.d.ts +3 -0
  143. package/dist/commands/ls.d.ts.map +1 -0
  144. package/dist/commands/ls.js +67 -0
  145. package/dist/commands/ls.js.map +1 -0
  146. package/dist/commands/machines.d.ts +3 -0
  147. package/dist/commands/machines.d.ts.map +1 -0
  148. package/dist/commands/machines.js +29 -0
  149. package/dist/commands/machines.js.map +1 -0
  150. package/dist/commands/mcp.d.ts +21 -0
  151. package/dist/commands/mcp.d.ts.map +1 -0
  152. package/dist/commands/mcp.js +1021 -0
  153. package/dist/commands/mcp.js.map +1 -0
  154. package/dist/commands/meshes.d.ts +3 -0
  155. package/dist/commands/meshes.d.ts.map +1 -0
  156. package/dist/commands/meshes.js +27 -0
  157. package/dist/commands/meshes.js.map +1 -0
  158. package/dist/commands/network-policy.d.ts +3 -0
  159. package/dist/commands/network-policy.d.ts.map +1 -0
  160. package/dist/commands/network-policy.js +40 -0
  161. package/dist/commands/network-policy.js.map +1 -0
  162. package/dist/commands/project.d.ts +4 -0
  163. package/dist/commands/project.d.ts.map +1 -0
  164. package/dist/commands/project.js +25 -0
  165. package/dist/commands/project.js.map +1 -0
  166. package/dist/commands/pull.d.ts +3 -0
  167. package/dist/commands/pull.d.ts.map +1 -0
  168. package/dist/commands/pull.js +155 -0
  169. package/dist/commands/pull.js.map +1 -0
  170. package/dist/commands/regions.d.ts +3 -0
  171. package/dist/commands/regions.d.ts.map +1 -0
  172. package/dist/commands/regions.js +67 -0
  173. package/dist/commands/regions.js.map +1 -0
  174. package/dist/commands/releases.d.ts +3 -0
  175. package/dist/commands/releases.d.ts.map +1 -0
  176. package/dist/commands/releases.js +176 -0
  177. package/dist/commands/releases.js.map +1 -0
  178. package/dist/commands/rm.d.ts +3 -0
  179. package/dist/commands/rm.d.ts.map +1 -0
  180. package/dist/commands/rm.js +42 -0
  181. package/dist/commands/rm.js.map +1 -0
  182. package/dist/commands/run.d.ts +3 -0
  183. package/dist/commands/run.d.ts.map +1 -0
  184. package/dist/commands/run.js +131 -0
  185. package/dist/commands/run.js.map +1 -0
  186. package/dist/commands/sandbox.d.ts +3 -0
  187. package/dist/commands/sandbox.d.ts.map +1 -0
  188. package/dist/commands/sandbox.js +352 -0
  189. package/dist/commands/sandbox.js.map +1 -0
  190. package/dist/commands/schedules.d.ts +3 -0
  191. package/dist/commands/schedules.d.ts.map +1 -0
  192. package/dist/commands/schedules.js +37 -0
  193. package/dist/commands/schedules.js.map +1 -0
  194. package/dist/commands/secrets.d.ts +3 -0
  195. package/dist/commands/secrets.d.ts.map +1 -0
  196. package/dist/commands/secrets.js +194 -0
  197. package/dist/commands/secrets.js.map +1 -0
  198. package/dist/commands/services.d.ts +3 -0
  199. package/dist/commands/services.d.ts.map +1 -0
  200. package/dist/commands/services.js +70 -0
  201. package/dist/commands/services.js.map +1 -0
  202. package/dist/commands/shell.d.ts +16 -0
  203. package/dist/commands/shell.d.ts.map +1 -0
  204. package/dist/commands/shell.js +527 -0
  205. package/dist/commands/shell.js.map +1 -0
  206. package/dist/commands/snapshot.d.ts +10 -0
  207. package/dist/commands/snapshot.d.ts.map +1 -0
  208. package/dist/commands/snapshot.js +181 -0
  209. package/dist/commands/snapshot.js.map +1 -0
  210. package/dist/commands/ssh.d.ts +3 -0
  211. package/dist/commands/ssh.d.ts.map +1 -0
  212. package/dist/commands/ssh.js +37 -0
  213. package/dist/commands/ssh.js.map +1 -0
  214. package/dist/commands/status.d.ts +3 -0
  215. package/dist/commands/status.d.ts.map +1 -0
  216. package/dist/commands/status.js +300 -0
  217. package/dist/commands/status.js.map +1 -0
  218. package/dist/commands/storage.d.ts +3 -0
  219. package/dist/commands/storage.d.ts.map +1 -0
  220. package/dist/commands/storage.js +180 -0
  221. package/dist/commands/storage.js.map +1 -0
  222. package/dist/commands/tenant.d.ts +3 -0
  223. package/dist/commands/tenant.d.ts.map +1 -0
  224. package/dist/commands/tenant.js +87 -0
  225. package/dist/commands/tenant.js.map +1 -0
  226. package/dist/commands/tunnel.d.ts +3 -0
  227. package/dist/commands/tunnel.d.ts.map +1 -0
  228. package/dist/commands/tunnel.js +418 -0
  229. package/dist/commands/tunnel.js.map +1 -0
  230. package/dist/commands/up.d.ts +14 -0
  231. package/dist/commands/up.d.ts.map +1 -0
  232. package/dist/commands/up.js +703 -0
  233. package/dist/commands/up.js.map +1 -0
  234. package/dist/commands/util.d.ts +19 -0
  235. package/dist/commands/util.d.ts.map +1 -0
  236. package/dist/commands/util.js +116 -0
  237. package/dist/commands/util.js.map +1 -0
  238. package/dist/commands/volumes.d.ts +3 -0
  239. package/dist/commands/volumes.d.ts.map +1 -0
  240. package/dist/commands/volumes.js +196 -0
  241. package/dist/commands/volumes.js.map +1 -0
  242. package/dist/commands/watch.d.ts +3 -0
  243. package/dist/commands/watch.d.ts.map +1 -0
  244. package/dist/commands/watch.js +398 -0
  245. package/dist/commands/watch.js.map +1 -0
  246. package/dist/commands/webhooks.d.ts +3 -0
  247. package/dist/commands/webhooks.d.ts.map +1 -0
  248. package/dist/commands/webhooks.js +23 -0
  249. package/dist/commands/webhooks.js.map +1 -0
  250. package/dist/commands/whoami.d.ts +3 -0
  251. package/dist/commands/whoami.d.ts.map +1 -0
  252. package/dist/commands/whoami.js +84 -0
  253. package/dist/commands/whoami.js.map +1 -0
  254. package/dist/commands/workspaces.d.ts +3 -0
  255. package/dist/commands/workspaces.d.ts.map +1 -0
  256. package/dist/commands/workspaces.js +87 -0
  257. package/dist/commands/workspaces.js.map +1 -0
  258. package/dist/config.d.ts +28 -0
  259. package/dist/config.d.ts.map +1 -0
  260. package/dist/config.js +129 -0
  261. package/dist/config.js.map +1 -0
  262. package/dist/errors.d.ts +22 -0
  263. package/dist/errors.d.ts.map +1 -0
  264. package/dist/errors.js +62 -0
  265. package/dist/errors.js.map +1 -0
  266. package/dist/framework-detector.d.ts +22 -0
  267. package/dist/framework-detector.d.ts.map +1 -0
  268. package/dist/framework-detector.js +373 -0
  269. package/dist/framework-detector.js.map +1 -0
  270. package/dist/pty/raw-mode.d.ts +7 -0
  271. package/dist/pty/raw-mode.d.ts.map +1 -0
  272. package/dist/pty/raw-mode.js +22 -0
  273. package/dist/pty/raw-mode.js.map +1 -0
  274. package/dist/pty/ws-pty-client.d.ts +12 -0
  275. package/dist/pty/ws-pty-client.d.ts.map +1 -0
  276. package/dist/pty/ws-pty-client.js +69 -0
  277. package/dist/pty/ws-pty-client.js.map +1 -0
  278. package/dist/types.d.ts +326 -0
  279. package/dist/types.d.ts.map +1 -0
  280. package/dist/types.js +16 -0
  281. package/dist/types.js.map +1 -0
  282. package/dist/ui/progress.d.ts +10 -0
  283. package/dist/ui/progress.d.ts.map +1 -0
  284. package/dist/ui/progress.js +36 -0
  285. package/dist/ui/progress.js.map +1 -0
  286. package/dist/ui/spinner.d.ts +4 -0
  287. package/dist/ui/spinner.d.ts.map +1 -0
  288. package/dist/ui/spinner.js +7 -0
  289. package/dist/ui/spinner.js.map +1 -0
  290. package/dist/ui/table.d.ts +8 -0
  291. package/dist/ui/table.d.ts.map +1 -0
  292. package/dist/ui/table.js +46 -0
  293. package/dist/ui/table.js.map +1 -0
  294. package/package.json +53 -0
@@ -0,0 +1,703 @@
1
+ /**
2
+ * `miosa up` — smart context-aware launch command.
3
+ *
4
+ * Detects what you're working with and does the right thing:
5
+ * .miosa.json or miosa.json → redeploy existing project
6
+ * Dockerfile → build and deploy as container (not yet live — falls through to deploy)
7
+ * package.json / mix.exs / requirements.txt → detect framework, deploy
8
+ * --computer flag → create a desktop computer
9
+ * --sandbox flag → create a sandbox
10
+ * nothing → interactive mode to pick an action
11
+ */
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { execSync } from "node:child_process";
15
+ import chalk from "chalk";
16
+ import { loadConfig } from "../config.js";
17
+ import { MiosaClient, parseSse } from "../client.js";
18
+ import { handleError } from "./util.js";
19
+ import { detectFramework, FRAMEWORK_LABELS, } from "../framework-detector.js";
20
+ import { UserError } from "../errors.js";
21
+ import { toDeploymentId } from "../types.js";
22
+ // ── .miosa.json helpers ───────────────────────────────────────────────────────
23
+ const PROJECT_CONFIG_FILES = [".miosa.json", "miosa.json"];
24
+ function loadProjectConfig(dir) {
25
+ for (const filename of PROJECT_CONFIG_FILES) {
26
+ const p = path.join(dir, filename);
27
+ if (!fs.existsSync(p))
28
+ continue;
29
+ try {
30
+ return JSON.parse(fs.readFileSync(p, "utf8"));
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ function saveProjectConfig(dir, cfg) {
39
+ const p = path.join(dir, ".miosa.json");
40
+ fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n");
41
+ }
42
+ // ── Signals ───────────────────────────────────────────────────────────────────
43
+ /**
44
+ * Write a single status line. In JSON mode output nothing (caller handles JSON).
45
+ * Uses \r to overwrite the same line for streaming progress feel.
46
+ */
47
+ function line(msg, opts) {
48
+ if (opts.json)
49
+ return;
50
+ process.stdout.write(msg + "\n");
51
+ }
52
+ function lineProgress(msg, opts) {
53
+ if (opts.json)
54
+ return;
55
+ process.stdout.write(` ${msg}\n`);
56
+ }
57
+ function getGitInfo(dir) {
58
+ try {
59
+ execSync("git rev-parse --git-dir", { cwd: dir, stdio: "ignore" });
60
+ }
61
+ catch {
62
+ throw new UserError("Not a git repository.", "Run `git init && git remote add origin <url>` first.");
63
+ }
64
+ let repoUrl;
65
+ try {
66
+ repoUrl = execSync("git remote get-url origin", {
67
+ cwd: dir,
68
+ encoding: "utf8",
69
+ }).trim();
70
+ }
71
+ catch {
72
+ throw new UserError("No git remote named 'origin' found.", "Add one with: git remote add origin https://github.com/you/repo");
73
+ }
74
+ // Normalize SSH → HTTPS
75
+ if (repoUrl.startsWith("git@github.com:")) {
76
+ repoUrl = repoUrl
77
+ .replace("git@github.com:", "https://github.com/")
78
+ .replace(/\.git$/, "");
79
+ }
80
+ repoUrl = repoUrl.replace(/\.git$/, "");
81
+ let currentBranch = "main";
82
+ try {
83
+ currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
84
+ cwd: dir,
85
+ encoding: "utf8",
86
+ }).trim();
87
+ }
88
+ catch {
89
+ // default
90
+ }
91
+ return { repoUrl, currentBranch };
92
+ }
93
+ // ── Deploy log streaming ──────────────────────────────────────────────────────
94
+ async function streamDeployLogs(client, deploymentId, opts) {
95
+ const res = await client.streamDeploymentLogs(deploymentId);
96
+ let lastState = "success";
97
+ for await (const event of parseSse(res.body)) {
98
+ switch (event.type) {
99
+ case "stdout":
100
+ if (!opts.json)
101
+ process.stdout.write(chalk.dim(" ") + event.data);
102
+ break;
103
+ case "stderr":
104
+ if (!opts.json)
105
+ process.stderr.write(chalk.red(" ") + event.data);
106
+ break;
107
+ case "error":
108
+ if (!opts.json)
109
+ console.error(chalk.red(` [error] ${event.message}`));
110
+ lastState = "failure";
111
+ break;
112
+ case "done": {
113
+ if (event.result && typeof event.result === "object") {
114
+ const r = event.result;
115
+ if (r["state"] === "failed")
116
+ lastState = "failure";
117
+ }
118
+ return lastState;
119
+ }
120
+ case "unknown": {
121
+ try {
122
+ const parsed = JSON.parse(event.raw);
123
+ if (typeof parsed["line"] === "string" && !opts.json) {
124
+ const stream = parsed["stream"] ?? "stdout";
125
+ const logLine = parsed["line"];
126
+ if (stream === "stderr") {
127
+ process.stderr.write(chalk.red(" ") + logLine + "\n");
128
+ }
129
+ else {
130
+ process.stdout.write(chalk.dim(" ") + logLine + "\n");
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ // ignore unparseable frames
136
+ }
137
+ break;
138
+ }
139
+ default:
140
+ break;
141
+ }
142
+ }
143
+ return lastState;
144
+ }
145
+ // ── Mode: redeploy existing .miosa.json ──────────────────────────────────────
146
+ async function runRedeploy(client, projectCfg, opts) {
147
+ if (!opts.json) {
148
+ console.log();
149
+ line(` Detected: ${chalk.cyan(FRAMEWORK_LABELS[projectCfg.framework] ?? projectCfg.framework)}`, opts);
150
+ line(` Found .miosa.json → redeploying ${chalk.bold(projectCfg.name)}`, opts);
151
+ console.log();
152
+ }
153
+ lineProgress("Queuing build...", opts);
154
+ try {
155
+ await client.redeployDeployment(projectCfg.deploymentId);
156
+ }
157
+ catch (err) {
158
+ handleError(err);
159
+ }
160
+ lineProgress("Build queued", opts);
161
+ if (!opts.json) {
162
+ console.log();
163
+ line(` ${chalk.bold("Build log:")}`, opts);
164
+ line(` ${"─".repeat(60)}`, opts);
165
+ }
166
+ const buildResult = await streamDeployLogs(client, projectCfg.deploymentId, opts);
167
+ if (!opts.json) {
168
+ line(` ${"─".repeat(60)}`, opts);
169
+ console.log();
170
+ }
171
+ if (buildResult === "success") {
172
+ try {
173
+ const dep = await client.getDeployment(projectCfg.deploymentId);
174
+ const tenant = await client.getTenant();
175
+ const url = `https://${dep.slug}.${tenant.slug}.miosa.app`;
176
+ if (opts.json) {
177
+ console.log(JSON.stringify({ id: dep.id, url, name: dep.name, state: dep.state }));
178
+ }
179
+ else {
180
+ console.log(chalk.green(" Deployed"));
181
+ console.log();
182
+ console.log(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
183
+ console.log();
184
+ }
185
+ }
186
+ catch {
187
+ if (opts.json) {
188
+ console.log(JSON.stringify({ id: projectCfg.deploymentId, state: "running" }));
189
+ }
190
+ else {
191
+ console.log(chalk.green(" Deployed"));
192
+ }
193
+ }
194
+ }
195
+ else {
196
+ if (opts.json) {
197
+ console.log(JSON.stringify({ id: projectCfg.deploymentId, state: "failed" }));
198
+ }
199
+ else {
200
+ console.log(chalk.red(" Build failed."));
201
+ console.log();
202
+ console.log(chalk.dim(" View full logs: miosa deploy logs"));
203
+ }
204
+ process.exit(1);
205
+ }
206
+ }
207
+ // ── Mode: first deploy ────────────────────────────────────────────────────────
208
+ async function runFirstDeploy(cwd, client, opts) {
209
+ const { repoUrl, currentBranch } = getGitInfo(cwd);
210
+ const detection = detectFramework(cwd);
211
+ const hasDockerfile = fs.existsSync(path.join(cwd, "Dockerfile"));
212
+ let framework = "unknown";
213
+ let buildCommand = "npm run build";
214
+ let runCommand = "npm start";
215
+ let frameworkLabel = "application";
216
+ if (detection) {
217
+ framework = detection.framework;
218
+ buildCommand = detection.buildCommand;
219
+ runCommand = detection.runCommand;
220
+ frameworkLabel =
221
+ FRAMEWORK_LABELS[detection.framework] ?? detection.framework;
222
+ }
223
+ else if (hasDockerfile) {
224
+ frameworkLabel = "container (Dockerfile)";
225
+ buildCommand = "";
226
+ runCommand = "";
227
+ }
228
+ if (!opts.json) {
229
+ console.log();
230
+ if (detection) {
231
+ line(` Detected: ${chalk.cyan(frameworkLabel)} (confidence ${detection.confidence}%)`, opts);
232
+ }
233
+ else if (hasDockerfile) {
234
+ line(` Detected: ${chalk.cyan("Dockerfile")}`, opts);
235
+ }
236
+ else {
237
+ line(chalk.yellow(" No framework detected — deploying as generic app"), opts);
238
+ }
239
+ line(` Repo: ${chalk.dim(repoUrl)}`, opts);
240
+ line(` Branch: ${chalk.dim(currentBranch)}`, opts);
241
+ console.log();
242
+ }
243
+ // ── Determine deploy parameters ──────────────────────────────────────────
244
+ let deployName;
245
+ let deployBranch;
246
+ let deployBuild;
247
+ let deployRun;
248
+ const defaultName = path.basename(cwd).replace(/[^a-z0-9-]/gi, "-");
249
+ if (opts.yes || opts.json) {
250
+ // Non-interactive: use flags or defaults
251
+ deployName = opts.name ?? defaultName;
252
+ deployBranch = currentBranch;
253
+ deployBuild = buildCommand;
254
+ deployRun = runCommand;
255
+ }
256
+ else {
257
+ // Interactive
258
+ const { default: inquirer } = await import("inquirer");
259
+ const answers = await inquirer.prompt([
260
+ {
261
+ type: "input",
262
+ name: "name",
263
+ message: "Name:",
264
+ default: opts.name ?? defaultName,
265
+ validate: (v) => (v.length > 0 ? true : "Name is required"),
266
+ },
267
+ {
268
+ type: "input",
269
+ name: "branch",
270
+ message: "Branch:",
271
+ default: currentBranch,
272
+ },
273
+ {
274
+ type: "input",
275
+ name: "buildCommand",
276
+ message: "Build command:",
277
+ default: buildCommand,
278
+ },
279
+ {
280
+ type: "input",
281
+ name: "runCommand",
282
+ message: "Run command:",
283
+ default: runCommand,
284
+ },
285
+ {
286
+ type: "confirm",
287
+ name: "confirm",
288
+ message: "Deploy?",
289
+ default: true,
290
+ },
291
+ ]);
292
+ if (!answers.confirm) {
293
+ line(chalk.dim(" Cancelled."), opts);
294
+ process.exit(0);
295
+ }
296
+ deployName = answers.name;
297
+ deployBranch = answers.branch;
298
+ deployBuild = answers.buildCommand;
299
+ deployRun = answers.runCommand;
300
+ }
301
+ // ── Create deployment ─────────────────────────────────────────────────────
302
+ lineProgress(`Creating deployment "${deployName}"...`, opts);
303
+ let deployment;
304
+ let webhookSecret;
305
+ try {
306
+ const result = await client.createDeployment({
307
+ name: deployName,
308
+ repo_url: repoUrl,
309
+ branch: deployBranch,
310
+ build_command: deployBuild || undefined,
311
+ run_command: deployRun || undefined,
312
+ auto_deploy: true,
313
+ });
314
+ deployment = result.data;
315
+ webhookSecret = result.webhook_secret;
316
+ }
317
+ catch (err) {
318
+ handleError(err);
319
+ }
320
+ lineProgress(`Deployment "${deployment.name}" created`, opts);
321
+ // ── Save .miosa.json ──────────────────────────────────────────────────────
322
+ const projectCfg = {
323
+ version: 1,
324
+ deploymentId: toDeploymentId(deployment.id),
325
+ name: deployment.name,
326
+ framework,
327
+ buildCommand: deployBuild,
328
+ runCommand: deployRun,
329
+ branch: deployBranch,
330
+ };
331
+ saveProjectConfig(cwd, projectCfg);
332
+ if (!opts.json)
333
+ lineProgress("Saved .miosa.json", opts);
334
+ // ── Webhook notice (human-only — agents don't need this) ──────────────────
335
+ if (!opts.json && !opts.yes) {
336
+ console.log();
337
+ console.log(chalk.bold.yellow(" ACTION REQUIRED — GitHub Webhook"));
338
+ console.log(chalk.dim(" The webhook secret below is shown ONCE. Store it now."));
339
+ console.log();
340
+ console.log(` ${chalk.bold("Webhook URL:")} https://api.miosa.ai/api/v1/integrations/github/webhook`);
341
+ console.log(` ${chalk.bold("Content type:")} application/json`);
342
+ console.log(` ${chalk.bold("Secret:")} ${chalk.green(webhookSecret)}`);
343
+ console.log(` ${chalk.bold("Events:")} push`);
344
+ console.log();
345
+ console.log(chalk.dim(" Add this at: " + repoUrl + "/settings/hooks/new"));
346
+ console.log();
347
+ }
348
+ // ── Trigger initial build ─────────────────────────────────────────────────
349
+ lineProgress("Queuing initial build...", opts);
350
+ try {
351
+ await client.redeployDeployment(projectCfg.deploymentId);
352
+ }
353
+ catch (err) {
354
+ handleError(err);
355
+ }
356
+ lineProgress("Build queued", opts);
357
+ // ── Stream logs ───────────────────────────────────────────────────────────
358
+ if (!opts.json) {
359
+ console.log();
360
+ line(` ${chalk.bold("Build log:")}`, opts);
361
+ line(` ${"─".repeat(60)}`, opts);
362
+ }
363
+ const buildResult = await streamDeployLogs(client, projectCfg.deploymentId, opts);
364
+ if (!opts.json) {
365
+ line(` ${"─".repeat(60)}`, opts);
366
+ console.log();
367
+ }
368
+ if (buildResult === "success") {
369
+ try {
370
+ const dep = await client.getDeployment(projectCfg.deploymentId);
371
+ const tenant = await client.getTenant();
372
+ const url = `https://${dep.slug}.${tenant.slug}.miosa.app`;
373
+ if (opts.json) {
374
+ console.log(JSON.stringify({ id: dep.id, url, name: dep.name, state: dep.state }));
375
+ }
376
+ else {
377
+ console.log(chalk.green(" Deployed"));
378
+ console.log();
379
+ console.log(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
380
+ console.log();
381
+ console.log(chalk.dim(" Next steps:"));
382
+ console.log(chalk.dim(" miosa deploy logs — tail logs"));
383
+ console.log(chalk.dim(" miosa deploy domain add example.com — add custom domain"));
384
+ console.log(chalk.dim(" miosa deploy env set KEY=VALUE — set env var"));
385
+ }
386
+ }
387
+ catch {
388
+ if (opts.json) {
389
+ console.log(JSON.stringify({ id: deployment.id, state: "running" }));
390
+ }
391
+ else {
392
+ console.log(chalk.green(" Deployed"));
393
+ }
394
+ }
395
+ }
396
+ else {
397
+ if (opts.json) {
398
+ console.log(JSON.stringify({ id: deployment.id, state: "failed" }));
399
+ }
400
+ else {
401
+ console.log(chalk.red(" Build failed."));
402
+ console.log();
403
+ console.log(chalk.dim(" View full logs: miosa deploy logs"));
404
+ }
405
+ process.exit(1);
406
+ }
407
+ }
408
+ async function runComputerMode(client, opts) {
409
+ let computerName;
410
+ let computerOs;
411
+ let computerSize;
412
+ if (opts.yes || opts.json) {
413
+ computerName = opts.name ?? `my-computer-${Date.now().toString(36)}`;
414
+ computerOs = opts.os;
415
+ computerSize = opts.size;
416
+ }
417
+ else {
418
+ const { default: inquirer } = await import("inquirer");
419
+ const answers = await inquirer.prompt([
420
+ {
421
+ type: "input",
422
+ name: "name",
423
+ message: "Computer name:",
424
+ default: opts.name ?? "my-computer",
425
+ },
426
+ {
427
+ type: "list",
428
+ name: "os",
429
+ message: "Operating system:",
430
+ choices: [
431
+ { name: "Ubuntu 22.04", value: "ubuntu" },
432
+ { name: "Debian 12", value: "debian" },
433
+ { name: "macOS (coming soon)", value: "macos", disabled: true },
434
+ ],
435
+ default: opts.os,
436
+ },
437
+ {
438
+ type: "list",
439
+ name: "size",
440
+ message: "Size:",
441
+ choices: [
442
+ { name: "small (2 vCPU, 4 GB RAM)", value: "small" },
443
+ { name: "medium (4 vCPU, 8 GB RAM)", value: "medium" },
444
+ { name: "large (8 vCPU, 16 GB RAM)", value: "large" },
445
+ ],
446
+ default: opts.size,
447
+ },
448
+ ]);
449
+ computerName = answers.name;
450
+ computerOs = answers.os;
451
+ computerSize = answers.size;
452
+ }
453
+ if (!opts.json) {
454
+ console.log();
455
+ lineProgress(`Creating computer "${computerName}"...`, opts);
456
+ }
457
+ let computer;
458
+ try {
459
+ const result = await client.apiPost("/api/v1/computers", {
460
+ name: computerName,
461
+ os: computerOs,
462
+ size: computerSize,
463
+ desktop: true,
464
+ });
465
+ computer = result.data;
466
+ }
467
+ catch (err) {
468
+ handleError(err);
469
+ }
470
+ // Poll until running or timeout (30s)
471
+ lineProgress("Booting...", opts);
472
+ const deadline = Date.now() + 30_000;
473
+ let finalComputer = computer;
474
+ while (Date.now() < deadline) {
475
+ try {
476
+ const polled = await client.apiGet(`/api/v1/computers/${encodeURIComponent(computer.id)}`);
477
+ finalComputer = polled.data;
478
+ if (finalComputer.state === "running")
479
+ break;
480
+ }
481
+ catch {
482
+ // poll errors are transient — keep trying
483
+ }
484
+ await sleep(1_000);
485
+ lineProgress(` state: ${finalComputer.state}`, opts);
486
+ }
487
+ const desktopUrl = finalComputer.desktop_url ??
488
+ `https://app.miosa.ai/computers/${finalComputer.id}`;
489
+ if (opts.json) {
490
+ console.log(JSON.stringify({
491
+ id: finalComputer.id,
492
+ name: finalComputer.name,
493
+ state: finalComputer.state,
494
+ url: desktopUrl,
495
+ }));
496
+ }
497
+ else {
498
+ console.log();
499
+ console.log(chalk.green(" Ready"));
500
+ console.log();
501
+ console.log(` ${chalk.bold("Desktop:")} ${chalk.cyan(desktopUrl)}`);
502
+ console.log(` ${chalk.bold("SSH:")} ${chalk.dim(`miosa ssh ${finalComputer.id}`)}`);
503
+ console.log();
504
+ }
505
+ }
506
+ async function runSandboxMode(client, opts) {
507
+ let sandboxName;
508
+ let sandboxImage;
509
+ if (opts.yes || opts.json) {
510
+ sandboxName = opts.name ?? `sandbox-${Date.now().toString(36)}`;
511
+ sandboxImage = opts.image;
512
+ }
513
+ else {
514
+ const { default: inquirer } = await import("inquirer");
515
+ const answers = await inquirer.prompt([
516
+ {
517
+ type: "input",
518
+ name: "name",
519
+ message: "Sandbox name:",
520
+ default: opts.name ?? "my-sandbox",
521
+ },
522
+ {
523
+ type: "list",
524
+ name: "image",
525
+ message: "Image:",
526
+ choices: [
527
+ { name: "miosa-sandbox (default)", value: "miosa-sandbox" },
528
+ { name: "python-3.12", value: "python-3.12" },
529
+ { name: "node-20", value: "node-20" },
530
+ { name: "ubuntu-22.04", value: "ubuntu-22.04" },
531
+ ],
532
+ default: opts.image,
533
+ },
534
+ ]);
535
+ sandboxName = answers.name;
536
+ sandboxImage = answers.image;
537
+ }
538
+ if (!opts.json) {
539
+ console.log();
540
+ lineProgress(`Creating sandbox "${sandboxName}"...`, opts);
541
+ }
542
+ const start = Date.now();
543
+ let sandbox;
544
+ try {
545
+ const result = await client.apiPost("/api/v1/sandboxes", {
546
+ name: sandboxName,
547
+ template_id: sandboxImage,
548
+ });
549
+ sandbox = result.data;
550
+ }
551
+ catch (err) {
552
+ handleError(err);
553
+ }
554
+ const elapsed = Date.now() - start;
555
+ if (opts.json) {
556
+ console.log(JSON.stringify({
557
+ id: sandbox.id,
558
+ name: sandbox.name,
559
+ state: sandbox.state,
560
+ }));
561
+ }
562
+ else {
563
+ console.log(chalk.green(` Ready (${elapsed}ms)`));
564
+ console.log();
565
+ console.log(` ${chalk.bold("Exec:")} ${chalk.dim(`miosa sandbox exec ${sandbox.id} "python app.py"`)}`);
566
+ console.log(` ${chalk.bold("SSH:")} ${chalk.dim(`miosa sandbox ssh ${sandbox.id}`)}`);
567
+ console.log();
568
+ }
569
+ }
570
+ // ── Mode: interactive disambiguation ─────────────────────────────────────────
571
+ async function runInteractiveDisambiguate(cwd, client, opts) {
572
+ const { default: inquirer } = await import("inquirer");
573
+ console.log();
574
+ console.log(` ${chalk.dim("No project detected in current directory. What would you like to create?")}`);
575
+ console.log();
576
+ const { action } = await inquirer.prompt([
577
+ {
578
+ type: "list",
579
+ name: "action",
580
+ message: "Action:",
581
+ choices: [
582
+ { name: "Deploy a GitHub repository", value: "deploy" },
583
+ { name: "Create a desktop computer", value: "computer" },
584
+ { name: "Create a sandbox", value: "sandbox" },
585
+ ],
586
+ },
587
+ ]);
588
+ switch (action) {
589
+ case "deploy":
590
+ await runFirstDeploy(cwd, client, opts);
591
+ break;
592
+ case "computer":
593
+ await runComputerMode(client, opts);
594
+ break;
595
+ case "sandbox":
596
+ await runSandboxMode(client, opts);
597
+ break;
598
+ }
599
+ }
600
+ // ── Context detection ─────────────────────────────────────────────────────────
601
+ function detectMode(cwd, opts) {
602
+ // Explicit flags override everything
603
+ if (opts.computer)
604
+ return "computer";
605
+ if (opts.sandbox)
606
+ return "sandbox";
607
+ // Existing project config
608
+ if (loadProjectConfig(cwd))
609
+ return "redeploy";
610
+ // Deployable source code present
611
+ const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
612
+ const hasMixExs = fs.existsSync(path.join(cwd, "mix.exs"));
613
+ const hasRequirements = fs.existsSync(path.join(cwd, "requirements.txt"));
614
+ const hasDockerfile = fs.existsSync(path.join(cwd, "Dockerfile"));
615
+ const hasGoMod = fs.existsSync(path.join(cwd, "go.mod"));
616
+ const hasCargo = fs.existsSync(path.join(cwd, "Cargo.toml"));
617
+ const hasGemfile = fs.existsSync(path.join(cwd, "Gemfile"));
618
+ const hasIndexHtml = fs.existsSync(path.join(cwd, "index.html"));
619
+ if (hasPackageJson ||
620
+ hasMixExs ||
621
+ hasRequirements ||
622
+ hasDockerfile ||
623
+ hasGoMod ||
624
+ hasCargo ||
625
+ hasGemfile ||
626
+ hasIndexHtml) {
627
+ return "deploy-new";
628
+ }
629
+ return "interactive";
630
+ }
631
+ // ── Helpers ───────────────────────────────────────────────────────────────────
632
+ function sleep(ms) {
633
+ return new Promise((resolve) => setTimeout(resolve, ms));
634
+ }
635
+ // ── register ──────────────────────────────────────────────────────────────────
636
+ export function register(program) {
637
+ program
638
+ .command("up")
639
+ .description("Smart launch: deploy app, create computer, or start sandbox — auto-detected from context")
640
+ .addHelpText("after", `
641
+ Context detection (evaluated in order):
642
+ .miosa.json / miosa.json found → redeploy existing deployment
643
+ --computer flag → create a desktop computer
644
+ --sandbox flag → create a sandbox
645
+ package.json / mix.exs / etc. → detect framework, deploy
646
+ empty directory → interactive mode
647
+
648
+ Examples:
649
+ miosa up Auto-detect and deploy
650
+ miosa up --yes --json --name my-app Scriptable deploy (no prompts)
651
+ miosa up --computer --os ubuntu --size medium Create a desktop computer
652
+ miosa up --sandbox --image python-3.12 Create a Python sandbox
653
+ miosa up --computer --yes --json Scriptable computer creation
654
+ `)
655
+ .option("-y, --yes", "Skip all interactive prompts, use defaults", false)
656
+ .option("--json", "Output machine-readable JSON (implies --yes)", false)
657
+ .option("--name <name>", "Resource name (app, computer, or sandbox)")
658
+ // computer options
659
+ .option("--computer", "Create a desktop computer", false)
660
+ .option("--os <os>", "Operating system for computer mode", "ubuntu")
661
+ .option("--size <size>", "Size: small | medium | large", "small")
662
+ // sandbox options
663
+ .option("--sandbox", "Create a sandbox", false)
664
+ .option("--image <image>", "Sandbox image/template", "miosa-sandbox")
665
+ .action(async (rawOpts) => {
666
+ // --json implies --yes
667
+ const opts = { ...rawOpts, yes: rawOpts.yes || rawOpts.json };
668
+ try {
669
+ const cwd = process.cwd();
670
+ const config = loadConfig();
671
+ const client = new MiosaClient(config);
672
+ const mode = detectMode(cwd, opts);
673
+ switch (mode) {
674
+ case "redeploy": {
675
+ const projectCfg = loadProjectConfig(cwd);
676
+ // projectCfg is guaranteed non-null when mode === "redeploy"
677
+ await runRedeploy(client, projectCfg, opts);
678
+ break;
679
+ }
680
+ case "deploy-new":
681
+ await runFirstDeploy(cwd, client, opts);
682
+ break;
683
+ case "computer":
684
+ await runComputerMode(client, opts);
685
+ break;
686
+ case "sandbox":
687
+ await runSandboxMode(client, opts);
688
+ break;
689
+ case "interactive":
690
+ if (opts.yes || opts.json) {
691
+ // Non-interactive but no context — require explicit flag
692
+ throw new UserError("Cannot determine what to create. Specify --computer, --sandbox, or run from a project directory.", "Examples:\n miosa up --computer\n miosa up --sandbox\n cd my-project && miosa up");
693
+ }
694
+ await runInteractiveDisambiguate(cwd, client, opts);
695
+ break;
696
+ }
697
+ }
698
+ catch (err) {
699
+ handleError(err);
700
+ }
701
+ });
702
+ }
703
+ //# sourceMappingURL=up.js.map