@magnetoagents/cli 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Magneto contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # magneto CLI
2
+
3
+ Thin Node CLI for Magneto's public programmatic API (`/api/v1`) using workspace `sk_live_*` keys. Same surface as the Agent Skill and hosted MCP — no private endpoints, no session cookies. This is the docs' copy-paste reference client.
4
+
5
+ Published as **`@magnetoagents/cli`**. The installed command is **`magneto`**.
6
+
7
+ ## Install
8
+
9
+ | Method | Command |
10
+ |--------|---------|
11
+ | npm (global) | `npm install -g @magnetoagents/cli` |
12
+ | npx | `npx @magnetoagents/cli` |
13
+ | from-source | `cd app/cli && npm ci && npm run build` then `node dist/cli.js` / `npm link` |
14
+
15
+ The first registry publish waits on the `@magneto` npm org + `NPM_TOKEN` repo secret. Until then, use from-source.
16
+
17
+ ## Quickstart
18
+
19
+ About 90 seconds after you have an `sk_live_*` key (mint in the dashboard):
20
+
21
+ ```bash
22
+ npx @magnetoagents/cli login sk_live_…
23
+ npx @magnetoagents/cli computers list
24
+ ```
25
+
26
+ From-source, after `npm ci && npm run build` in `app/cli`:
27
+
28
+ ```bash
29
+ export MAGNETO_API_BASE=http://localhost:${FRONTEND_PORT:-3000} # omit for production
30
+ node dist/cli.js login sk_live_…
31
+ node dist/cli.js computers list
32
+ ```
33
+
34
+ This quickstart only lists. Do not `create` or `delete` unless you mean it.
35
+
36
+ ## Versioning (v0.x)
37
+
38
+ Bump `package.json` `version` by hand (patch = fix, minor = additive or breaking while major is 0). `magneto --version` reads that field at runtime. Publish is `workflow_dispatch` on `.github/workflows/npm-publish.yml`, not a git tag.
39
+
40
+ ## Auth
41
+
42
+ ```bash
43
+ magneto login sk_live_…
44
+ # writes ~/.magneto/credentials.json (mode 0600), dir 0700
45
+ ```
46
+
47
+ Resolution order:
48
+
49
+ 1. `MAGNETO_API_KEY` environment variable
50
+ 2. `~/.magneto/credentials.json` → `{ "api_key": "sk_live_…" }`
51
+
52
+ ## Base URL
53
+
54
+ | Env | Default |
55
+ |-----|---------|
56
+ | `MAGNETO_API_BASE` or `MAGNETO_BASE_URL` | `https://magnetoapp.io` (client appends `/api/v1`) |
57
+ | `MAGNETO_TIMEOUT_MS` | `30000` (JSON requests only; streams and signed-URL downloads are exempt) |
58
+
59
+ Local / worktree:
60
+
61
+ ```bash
62
+ export MAGNETO_API_BASE=http://localhost:${FRONTEND_PORT:-3000}
63
+ # worktree ports often come from repo `.ports.env`
64
+ ```
65
+
66
+ The client retries **once** on HTTP 429, honoring `Retry-After` (delta-seconds or HTTP-date, capped at 60s).
67
+
68
+ ## Commands
69
+
70
+ ```bash
71
+ magneto login <key>
72
+
73
+ magneto computers list [--output table|json|quiet]
74
+ magneto computers get <id> [--output table|json|quiet]
75
+ magneto computers create [--name NAME] [--flavor agent|desktop] [--template-id N] [--vcpu N] [--ram-gb N] [--disk-gb N]
76
+ magneto computers start <id>
77
+ magneto computers stop <id>
78
+ magneto computers restart <id>
79
+ magneto computers resize <id> --vcpu N --ram-gb N --disk-gb N
80
+ magneto computers delete <id> --confirm <name> # irreversible; name must match
81
+ magneto computers uptime <id> [--window-seconds N] [--output table|json|quiet]
82
+
83
+ magneto gateway <id> --target desktop|terminal [--open]
84
+ # prints a 60-second signed URL to stdout. Do not write it to shell history or logs.
85
+
86
+ magneto run "Open a browser" --computer <id> [--model …] [--max-steps N]
87
+ magneto runs list --computer <id> [--limit N] [--offset N] [--output table|json|quiet]
88
+ magneto runs get <run_id> --computer <id> [--output table|json|quiet]
89
+ magneto runs export <run_id> --computer <id> [-o file]
90
+ magneto runs stop <run_id> --computer <id>
91
+
92
+ magneto bash <id> -- "cmd with spaces" [--timeout 60]
93
+ # requires scope `exec` (not in the default key set)
94
+
95
+ magneto files list [--output table|json|quiet]
96
+ magneto files upload <path>
97
+ magneto files download <id> [-o dir/]
98
+
99
+ magneto templates list [--skip N] [--limit N] [--output table|json|quiet]
100
+ magneto skills search [query] [--skip N] [--limit N] [--output table|json|quiet]
101
+ # search is client-side over the fetched page; v1 has no ?q=.
102
+ magneto skills install <skill_id> --computer <id>
103
+ magneto skills uninstall <skill_id> --computer <id>
104
+
105
+ magneto account [--output table|json|quiet]
106
+ ```
107
+
108
+ ## Output formats
109
+
110
+ `--output` is the long flag only (no `-o` alias). `-o` is the path flag on `runs export` and `files download`.
111
+
112
+ | Value | Meaning |
113
+ |-------|---------|
114
+ | `table` | Fixed-width columns (no TUI deps). Empty list prints `(none)`. |
115
+ | `json` | Pretty-printed raw response |
116
+ | `quiet` | One id per line |
117
+
118
+ When `--output` is omitted: **table** if stdout is a TTY, **json** if piped (so existing `jq` one-liners keep working). An explicit `--output` always wins.
119
+
120
+ Mutations (`create`, `start`, `stop`, `restart`, `resize`, `delete`, `skills install`/`uninstall`, `runs stop`, `files upload`) still print pretty JSON of the response (204 → silent / `ok`).
121
+
122
+ ## Exit codes
123
+
124
+ | Code | Meaning |
125
+ |------|---------|
126
+ | `0` | Success |
127
+ | `1` | API or client error (including 402 / 404 / 409 / 422 / 429-after-retry / 5xx / timeout / missing key) |
128
+ | `2` | HTTP 401 or 403 |
129
+ | `N` | `magneto bash` only: remote `exit_code` on HTTP 200 (`N & 255`) |
130
+
131
+ Error lines look like:
132
+
133
+ ```
134
+ error: <detail> (HTTP <status>) · request-id: <id> · Retry-After: <n>
135
+ ```
136
+
137
+ `request-id` / `Retry-After` segments are omitted when the server did not send them.
138
+
139
+ ## Scopes
140
+
141
+ Keys are minted in the dashboard. Default scopes do **not** include `exec`.
142
+
143
+ | Command | Required scope |
144
+ |---------|----------------|
145
+ | `computers *`, `gateway`, `skills install`, `skills uninstall` | `computers` |
146
+ | `run`, `runs *` | `runs` |
147
+ | `bash` | **`exec`** (opt-in, not default) |
148
+ | `files *` | `files` |
149
+ | `templates list`, `skills search` | `catalog` |
150
+ | `account` | `account` |
151
+ | `login` | (none — writes the key locally) |
152
+
153
+ A key without `exec` fails `magneto bash` with HTTP 403 `Missing scope: exec` and exit 2.
154
+
155
+ ## Notes
156
+
157
+ - **Gateway URL** is a 60-second credential. It is printed to stdout only. `--open` launches the default browser (`open` / `xdg-open` / `cmd /c start`). Do not write the URL to shell history files or logs.
158
+ - **`computers delete --confirm <name>`** is irreversible deprovision. The name must match the computer; there is no interactive prompt.
159
+ - **`skills search [query]`** always calls `GET /api/v1/skills?skip=&limit=`. A query filters that page client-side (case-insensitive substring on `name` + `description`). v1 has no `?q=`.
160
+
161
+ ## Live smoke
162
+
163
+ Verification lane (`BACKEND_PORT=8020 FRONTEND_PORT=3020 ./scripts/start.sh`). Mint a key with `exec` if you want `bash` to succeed.
164
+
165
+ ```bash
166
+ export MAGNETO_API_BASE=http://localhost:${FRONTEND_PORT:-3020}
167
+ export MAGNETO_API_KEY=sk_live_…
168
+ cd app/cli
169
+ npx magneto login "$MAGNETO_API_KEY"
170
+ npx magneto computers create --name cli-440 --flavor desktop --vcpu 2 --ram-gb 4 --disk-gb 32
171
+ ID=… # from --output json / quiet
172
+ npx magneto computers uptime "$ID"
173
+ npx magneto bash "$ID" -- "echo ok"
174
+ npx magneto run "Open a browser" --computer "$ID"
175
+ npx magneto runs list --computer "$ID"
176
+ npx magneto runs export "$RUN_ID" --computer "$ID" -o /tmp/run-440.json
177
+ npx magneto files upload ./README.md
178
+ npx magneto files download "$FILE_ID" -o /tmp/
179
+ npx magneto computers delete "$ID" --confirm cli-440
180
+ ```
181
+
182
+ After revoke in the dashboard, `computers list` should exit 2 with HTTP 403 `API key revoked`.
183
+
184
+ ## Related surfaces
185
+
186
+ - Agent Skill: `https://magnetoapp.io/skills/computer/SKILL.md`
187
+ - Hosted MCP: `https://magnetoapp.io/mcp` (Bearer key)
188
+ - Stdio package: `packages/mcp` (`@magnetoagents/mcp`)
189
+
190
+ All three consume Worker `/api/v1` + `requireApiKey` (#388 / #110 / #440).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ export type CliIo = {
4
+ fetchImpl?: typeof fetch;
5
+ stdout?: {
6
+ write(s: string | Uint8Array): void;
7
+ isTTY?: boolean;
8
+ };
9
+ stderr?: {
10
+ write(s: string): void;
11
+ };
12
+ exit?: (code: number) => never;
13
+ openUrl?: (url: string) => Promise<void>;
14
+ env?: NodeJS.ProcessEnv;
15
+ };
16
+ /** Thrown by injected `io.exit` in tests so action `catch` blocks do not remap the code to 1. */
17
+ export declare class CliExitError extends Error {
18
+ readonly exitCode: number;
19
+ constructor(exitCode: number);
20
+ }
21
+ export declare function handleError(err: unknown, io?: CliIo): never;
22
+ export declare function buildProgram(io?: CliIo): Command;