runcloud 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -34,7 +34,7 @@ runcloud account # show your entitlement and metered balance
34
34
 
35
35
  Email signs you in without leaving the terminal. Google and GitHub open a browser and
36
36
  show a short code to confirm, so login works over SSH and on headless machines too —
37
- pick with `--email`, `--google`, or `--github` to skip the prompt.
37
+ pick with `--email`, `--google`, `--github`, or `--browser` to skip the prompt.
38
38
 
39
39
  The token expires — re-run `runcloud login` when a command reports you're logged out.
40
40
 
@@ -84,6 +84,8 @@ runcloud sandbox create --disk 80 # request an 80 GiB root filesy
84
84
  runcloud sandbox exec <id> npm run build # run a command inside it
85
85
  runcloud sandbox shell <id> # interactive shell, streamed over the API
86
86
  runcloud sandbox logs <id> --lines 200 # tail its logs
87
+ runcloud sandbox metrics <id> --range 24h # CPU, memory, network, and disk usage
88
+ runcloud sandbox pause <id> # park it; `resume` brings it back warm
87
89
  runcloud sandbox list
88
90
  runcloud sandbox rm <id> # destroy it
89
91
  ```
@@ -112,13 +114,21 @@ runcloud sandbox key "$SANDBOX_ID" "ctrl+l"
112
114
  Treat a signed desktop URL as a secret. It grants temporary access to that
113
115
  desktop without asking the browser to send your CLI credential.
114
116
 
115
- ### Persistence and a public hostname
117
+ ### Lifetime, pausing, and a public hostname
116
118
 
117
- A sandbox pauses when idle by default. Tune that with `--idle-pause <seconds>`,
118
- turn it off with `--persistent`, and publish a guest port at a stable
119
- `<name>-box.run.cloud` hostname with `--expose`:
119
+ Two clocks stop a sandbox, and the first one bites after five minutes:
120
+
121
+ - `--timeout <seconds>` — maximum lifetime, wall-clock from boot or the last
122
+ resume, busy or not. **Defaults to 300**; `0` removes the limit.
123
+ - `--idle-pause <seconds>` — pause after that much inactivity.
124
+
125
+ Both *pause* rather than destroy. `runcloud sandbox resume <id>` brings a
126
+ sandbox back warm with its filesystem intact, and a paused sandbox is destroyed
127
+ after 48 hours. `--persistent` stops both clocks; `--expose` implies it and
128
+ publishes a guest port at a stable `<name>-box.run.cloud` hostname.
120
129
 
121
130
  ```bash
131
+ runcloud sandbox create --timeout 3600 # an hour of wall clock, then pause
122
132
  runcloud sandbox create --idle-pause 600 # pause after 10 idle minutes
123
133
  runcloud sandbox create --name my-project --persistent # never pauses
124
134
  runcloud sandbox create --name my-project --expose 3000 # persistent + https://my-project-box.run.cloud
@@ -126,6 +136,10 @@ runcloud sandbox expose <id> --port 8080 # expose later, or m
126
136
  runcloud sandbox get <id> # inspect, including the hostname
127
137
  ```
128
138
 
139
+ The CLI only sets the timer at create time. To extend it on a running sandbox —
140
+ what a long job or a live agent session needs — call
141
+ `sandbox.setTimeout(id, seconds)` from [`@run-cloud/sdk`](https://www.npmjs.com/package/@run-cloud/sdk).
142
+
129
143
  `--expose` needs `--name` — that name is the hostname prefix, and it is fixed
130
144
  once minted.
131
145
 
@@ -152,6 +166,48 @@ runcloud sandbox restore <snapshot-id> --name fork # fork a NEW sandbox from t
152
166
  runcloud sandbox snapshot rm <snapshot-id>
153
167
  ```
154
168
 
169
+ ### Secrets
170
+
171
+ Store secrets in a named group, then attach the group when you create a sandbox:
172
+
173
+ ```bash
174
+ runcloud secret-group create aws --key AWS_ACCESS_KEY_ID --key AWS_SECRET_ACCESS_KEY
175
+ runcloud secret-group create prod --from-dotenv ./.env.production
176
+ runcloud secret-group list
177
+ runcloud secret-group show aws # names only, never values
178
+ runcloud secrets set DATABASE_URL --group prod # hidden prompt, or --stdin
179
+ runcloud secrets set-file sa.json --group prod --path .config/gcloud/sa.json --from-file ./sa.json
180
+
181
+ runcloud sandbox create --secret-group prod --env LOG_LEVEL=debug
182
+ runcloud sandbox create --secret prod/DATABASE_URL # one secret out of a group
183
+ runcloud sandbox create --no-secrets # state explicitly that it holds none
184
+ ```
185
+
186
+ `--secret-group`, `--secret`, and `--env` are repeatable and **order matters** —
187
+ a later one wins a name collision, and `--env` is applied last.
188
+
189
+ `runcloud sandbox secrets <id>` re-attaches on a running sandbox. It *replaces*
190
+ the whole set rather than merging, which is how a credential gets revoked:
191
+ passing no flags at all takes them all back.
192
+
193
+ A value can never be passed as a command argument — it would land in your shell
194
+ history, in `ps` output, and in CI logs. Use a hidden prompt, a file, or stdin.
195
+
196
+ ## Custom images
197
+
198
+ A sandbox boots an OCI image. Register your own once, then create sandboxes from
199
+ it:
200
+
201
+ ```bash
202
+ runcloud image create --ref python:3.12-slim # registers, and waits for the build
203
+ runcloud image list
204
+ runcloud image refresh python:3.12-slim # re-pin the tag to its current upstream digest
205
+ runcloud sandbox create --image python:3.12-slim
206
+ ```
207
+
208
+ Built in: `runcloud/agent-base` (the default), `runcloud/desktop`, and
209
+ `runcloud/box` (the default with `--expose`).
210
+
155
211
  ## Demos
156
212
 
157
213
  ```bash
@@ -194,6 +194,23 @@ async function waitForBuild(client, buildId, log) {
194
194
  status = event.status;
195
195
  }
196
196
  }
197
+ if (status !== 'completed' && status !== 'failed' && status !== 'cancelled') {
198
+ const deadline = Date.now() + 60 * 60 * 1000;
199
+ let lastLoggedStatus = status;
200
+ while (Date.now() < deadline) {
201
+ const build = (await client.get(`/builds/${buildId}`));
202
+ const current = typeof build.status === 'string' ? build.status : status;
203
+ if (current !== lastLoggedStatus) {
204
+ log(`Build ${buildId} ${current}`);
205
+ lastLoggedStatus = current;
206
+ }
207
+ status = current;
208
+ if (status === 'completed' || status === 'failed' || status === 'cancelled')
209
+ return status;
210
+ await new Promise((resolve) => setTimeout(resolve, 5_000));
211
+ }
212
+ throw new Error(`iOS simulator build ${buildId} timed out`);
213
+ }
197
214
  return status;
198
215
  }
199
216
  async function waitForAndroidApkBuild(client, buildId, log) {
@@ -11,11 +11,11 @@ export function desktopRunUrl(project, mode, start = true, platform = 'ios') {
11
11
  url.searchParams.set('request', randomUUID());
12
12
  return url.toString();
13
13
  }
14
- export async function startDesktopRun(project, mode, platform = 'ios', fetchImpl = fetch) {
14
+ export async function startDesktopRun(project, mode, platform = 'ios', key = 'default', sessionId, fetchImpl = fetch) {
15
15
  const response = await fetchImpl(DESKTOP_RUN_START_URL, {
16
16
  method: 'POST',
17
17
  headers: { 'content-type': 'application/json' },
18
- body: JSON.stringify({ cwd: resolve(project), key: 'default', mode, platform }),
18
+ body: JSON.stringify({ cwd: resolve(project), key, mode, platform, ...(sessionId ? { sessionId } : {}) }),
19
19
  });
20
20
  if (response.ok)
21
21
  return;
@@ -38,14 +38,18 @@ export function registerDesktop(program) {
38
38
  .requiredOption('--project <path>', 'local Expo project directory')
39
39
  .option('--mode <mode>', 'simulator mode: local or remote', 'local')
40
40
  .option('--platform <platform>', 'simulator platform: ios or android', 'ios')
41
+ .option('--key <key>', 'run lane key in the desktop sidecar', 'default')
42
+ .option('--session-id <id>', 'control-plane session id for session-scoped remote simulator builds')
43
+ .option('--no-open', 'skip the newly:// opener and call the running sidecar directly')
41
44
  .action(async (opts) => {
42
45
  if (opts.mode !== 'local' && opts.mode !== 'remote')
43
46
  throw new Error('--mode must be local or remote');
44
47
  if (opts.platform !== 'ios' && opts.platform !== 'android')
45
48
  throw new Error('--platform must be ios or android');
46
49
  const url = desktopRunUrl(opts.project, opts.mode, false, opts.platform);
47
- openDesktopUrl(url);
48
- await startDesktopRun(opts.project, opts.mode, opts.platform);
49
- console.log(`Opened Newly and started the ${opts.mode} ${opts.platform} simulator run for ${resolve(opts.project)}`);
50
+ if (opts.open)
51
+ openDesktopUrl(url);
52
+ await startDesktopRun(opts.project, opts.mode, opts.platform, opts.key, opts.sessionId);
53
+ console.log(`${opts.open ? 'Opened Newly and started' : 'Started'} the ${opts.mode} ${opts.platform} simulator run for ${resolve(opts.project)}`);
50
54
  });
51
55
  }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.1.24';
1
+ export const CLI_VERSION = '0.1.26';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [