Skip to content

Commit be3919e

Browse files
authored
fix(cli): sandbox feedback fixes (1.0.38) (#2)
* fix(auth): accept current tenant response shape * chore(release): normalize cli bin path * feat(cli): streamline sandbox deploy workflows * feat(cli): add vercel-style sandbox ergonomics * feat(cli): expose sandbox source and lifecycle controls * feat(cli): add durable sandbox command controls * feat(cli): harden MCP sandbox and database tools * fix(cli): use backend sandbox service resources * fix(sandbox): harden agent CLI lifecycle * fix(sandbox): accept raw read-file responses * fix(sandbox): accept raw file write responses * fix(sandbox): use preview proxy for port-forward * fix(cli): harden sandbox lifecycle commands * fix(cli): register ssh keys per sandbox * fix(cli): honor json mode for sandbox create * fix(cli): make json mode reliable * Support Computer SSH and CLI API contract flags * Wire releases rollback to deployment rollback API * fix(cli): reject database connect before running * Attach database URLs to deployment apps * Use deployment database attach endpoint * chore(cli): release 1.0.25 * Add database log snapshot command * chore(cli): release 1.0.26 * fix(cli): keep deploy json machine-readable * feat(cli): publish agent and domain command updates * fix(deploy): honor global json output for show and list * fix(cli): honor json mode for deploy and domains * fix(cli): align deploy json and publish state * feat(cli): add workspace cleanup commands * feat(cli): add admin sdk helpers and agent history tickets * fix(cli): verify whoami before trusting auth cache * fix(cli): honor global workspace on sandbox create * fix(cli): clean auth cache and json output * chore(cli): release 1.0.31 * feat(cli): expose agent-readable capabilities * fix(cli): upload storage objects as raw bytes * chore(cli): release 1.0.33 * fix(cli): render sandbox template catalog correctly * chore(cli): release 1.0.34 * feat(cli): add managed database lifecycle commands * chore(cli): bump package to 1.0.35 * feat(cli): add sandbox env and database attach commands * feat(cli): add app starters and manifest deploy defaults * fix(cli): resolve sandbox CLI feedback — cp/ls crash, exec output+quoting, publish, ports, follow - cp/ls: guard against undefined host list (client.listHosts -> ?? []) and give a clear error pointing sandbox IDs to `miosa sandbox cp/ls` instead of crashing with "Cannot read properties of undefined (reading 'find')". - exec: printObject prints full stdout/stderr (no clipping); the 48-char cell clipping stays for table columns only. - exec/run: allowUnknownOption + allowExcessArguments so `bash -c "..."` passes through; joinCommandWords shell-quotes multi-word args so quoting survives. - publish: default --wait timeout 180s -> 600s; explicit --no-wait; clearer "still building" timeout message; attach to existing same-name deployment in a non-terminal state instead of creating a duplicate. - exec/run: --follow/--stream streams output via background logfile polling. - new `sandbox ports <id>` subcommand lists listening TCP ports (ss or /proc). * chore(release): bump CLI to 1.0.38 for sandbox feedback fixes
1 parent 5a26cab commit be3919e

55 files changed

Lines changed: 9977 additions & 1235 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ Precedence: CLI flags > environment variables > config file > interactive prompt
138138
|---|---|
139139
| `MIOSA_API_KEY` | API key (overrides config file) |
140140
| `MIOSA_ENDPOINT` | API endpoint (overrides config file) |
141+
| `MIOSA_JSON` | Prefer JSON output for commands that support structured output |
142+
| `MIOSA_NO_COLOR` | Disable ANSI color output |
141143
| `MIOSA_DEBUG` | Set to any value to enable debug output |
142144

143145
## Commands
@@ -198,9 +200,9 @@ miosa connect
198200
miosa connect my-new-server
199201
```
200202

201-
### `miosa ssh <host> [--cmd "..."]`
203+
### `miosa ssh <computer-or-host> [--cmd "..."]`
202204

203-
Open an interactive PTY terminal session on a host.
205+
Open an interactive PTY terminal session on a MIOSA Computer. If no Computer matches, the CLI falls back to an OpenComputers host with the same name or ID.
204206

205207
```bash
206208
miosa ssh my-mac
@@ -268,14 +270,27 @@ List active tunnels on a host.
268270

269271
Close (revoke) a tunnel.
270272

271-
### `miosa agent <host> "<task>" [--model] [--steps] [--timeout]`
273+
### `miosa agent <computer> "<task>" [--model] [--max-turns]`
272274

273-
Dispatch an AI agent task. Streams thoughts, tool calls, and results live.
275+
Start a resumable Computer Use Agent session on a Computer.
274276

275277
```bash
276278
miosa agent my-mac "run the test suite and fix any failing tests"
277-
miosa agent my-mac "update all npm dependencies" --steps 20
279+
miosa agent my-mac "update all npm dependencies" --max-turns 20
278280
miosa agent my-mac "optimize the database queries" --model nemotron-3-super
281+
miosa agent my-mac --resume <session-id> "continue from the last failure"
282+
miosa agent ls
283+
miosa agent history <session-id> --computer my-mac
284+
```
285+
286+
### `miosa completion <shell>`
287+
288+
Print shell completion for `bash`, `zsh`, or `fish`.
289+
290+
```bash
291+
miosa completion zsh > ~/.zsh/completions/_miosa
292+
miosa completion bash > ~/.local/share/bash-completion/completions/miosa
293+
miosa completion fish > ~/.config/fish/completions/miosa.fish
279294
```
280295

281296
### `miosa watch <host>`

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "@miosa/cli",
3-
"version": "1.0.8",
3+
"version": "1.0.38",
44
"description": "MIOSA platform CLI — projects, sandboxes, deploys, databases, more",
55
"type": "module",
66
"bin": {
7-
"miosa": "./dist/bin/miosa.js"
7+
"miosa": "dist/bin/miosa.js"
88
},
99
"scripts": {
1010
"build": "tsc",

src/app-manifest.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import fs from "node:fs";
2+
import path from "node:path";
3+
4+
export const APP_MANIFEST_FILES = [
5+
"miosa.app.yml",
6+
"miosa.app.yaml",
7+
"miosa.app.json",
8+
] as const;
9+
10+
export interface MiosaAppManifest {
11+
template?: string;
12+
framework?: string;
13+
workdir?: string;
14+
install?: string | false;
15+
dev?: string;
16+
build?: string;
17+
start?: string;
18+
run?: string;
19+
port?: number;
20+
output?: string;
21+
output_path?: string;
22+
readiness?: {
23+
path?: string;
24+
port?: number;
25+
};
26+
}
27+
28+
export interface LoadedAppManifest {
29+
path: string;
30+
manifest: MiosaAppManifest;
31+
}
32+
33+
export function loadAppManifest(dir: string): LoadedAppManifest | null {
34+
for (const file of APP_MANIFEST_FILES) {
35+
const fullPath = path.join(dir, file);
36+
if (!fs.existsSync(fullPath)) continue;
37+
const content = fs.readFileSync(fullPath, "utf8");
38+
return { path: fullPath, manifest: parseAppManifest(file, content) };
39+
}
40+
return null;
41+
}
42+
43+
export function parseAppManifest(
44+
filename: string,
45+
content: string,
46+
): MiosaAppManifest {
47+
if (filename.endsWith(".json")) {
48+
const parsed = JSON.parse(content) as unknown;
49+
return normalizeManifest(parsed);
50+
}
51+
return normalizeManifest(parseSimpleYaml(content));
52+
}
53+
54+
export function manifestProbePath(manifest?: MiosaAppManifest | null): string | undefined {
55+
return manifest?.readiness?.path;
56+
}
57+
58+
export function manifestPort(manifest?: MiosaAppManifest | null): number | undefined {
59+
return numberValue(manifest?.port ?? manifest?.readiness?.port);
60+
}
61+
62+
export function manifestStartCommand(
63+
manifest?: MiosaAppManifest | null,
64+
): string | undefined {
65+
return stringValue(manifest?.dev ?? manifest?.start ?? manifest?.run);
66+
}
67+
68+
export function manifestRunCommand(
69+
manifest?: MiosaAppManifest | null,
70+
): string | undefined {
71+
return stringValue(manifest?.run ?? manifest?.start);
72+
}
73+
74+
export function manifestBuildCommand(
75+
manifest?: MiosaAppManifest | null,
76+
): string | undefined {
77+
return stringValue(manifest?.build);
78+
}
79+
80+
export function manifestOutputPath(
81+
manifest?: MiosaAppManifest | null,
82+
): string | undefined {
83+
return stringValue(manifest?.output_path ?? manifest?.output);
84+
}
85+
86+
function parseSimpleYaml(content: string): Record<string, unknown> {
87+
const root: Record<string, unknown> = {};
88+
let section: Record<string, unknown> | null = null;
89+
90+
for (const rawLine of content.split(/\r?\n/)) {
91+
const withoutComment = rawLine.replace(/\s+#.*$/, "");
92+
if (!withoutComment.trim()) continue;
93+
const indent = withoutComment.match(/^\s*/)?.[0].length ?? 0;
94+
const trimmed = withoutComment.trim();
95+
const idx = trimmed.indexOf(":");
96+
if (idx <= 0) continue;
97+
98+
const key = trimmed.slice(0, idx).trim();
99+
const rawValue = trimmed.slice(idx + 1).trim();
100+
101+
if (indent === 0) {
102+
if (rawValue === "") {
103+
section = {};
104+
root[key] = section;
105+
} else {
106+
section = null;
107+
root[key] = parseScalar(rawValue);
108+
}
109+
} else if (section) {
110+
section[key] = parseScalar(rawValue);
111+
}
112+
}
113+
114+
return root;
115+
}
116+
117+
function parseScalar(raw: string): unknown {
118+
const value = raw.replace(/^['"]|['"]$/g, "");
119+
if (value === "false") return false;
120+
if (value === "true") return true;
121+
if (/^\d+$/.test(value)) return Number(value);
122+
return value;
123+
}
124+
125+
function normalizeManifest(value: unknown): MiosaAppManifest {
126+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
127+
const row = value as Record<string, unknown>;
128+
const readiness =
129+
row["readiness"] && typeof row["readiness"] === "object"
130+
? (row["readiness"] as Record<string, unknown>)
131+
: {};
132+
133+
return {
134+
template: stringValue(row["template"] ?? row["template_id"]),
135+
framework: stringValue(row["framework"]),
136+
workdir: stringValue(row["workdir"] ?? row["workspace"] ?? row["root"]),
137+
install:
138+
row["install"] === false
139+
? false
140+
: stringValue(row["install"] ?? row["install_command"]),
141+
dev: stringValue(row["dev"]),
142+
build: stringValue(row["build"] ?? row["build_command"]),
143+
start: stringValue(row["start"] ?? row["start_command"]),
144+
run: stringValue(row["run"] ?? row["run_command"]),
145+
port: numberValue(row["port"] ?? readiness["port"]),
146+
output: stringValue(row["output"]),
147+
output_path: stringValue(row["output_path"]),
148+
readiness: {
149+
path: stringValue(
150+
readiness["path"] ?? row["probe_path"] ?? row["health_check_path"],
151+
),
152+
port: numberValue(readiness["port"]),
153+
},
154+
};
155+
}
156+
157+
function stringValue(value: unknown): string | undefined {
158+
return typeof value === "string" && value.trim() !== ""
159+
? value.trim()
160+
: undefined;
161+
}
162+
163+
function numberValue(value: unknown): number | undefined {
164+
if (typeof value === "number" && Number.isInteger(value)) return value;
165+
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
166+
return undefined;
167+
}

0 commit comments

Comments
 (0)