Skip to content

Commit a86f9f9

Browse files
committed
feat(sdk): added secrets as first class concepts and examples
1 parent 4d9624c commit a86f9f9

10 files changed

Lines changed: 826 additions & 36 deletions

File tree

EXAMPLES.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Runnable examples live in [`examples/`](./examples).
99

1010
- [Devbox From Blueprint (Run Command, Shutdown)](#devbox-from-blueprint-lifecycle)
1111
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
12+
- [Secrets with Devbox (Create, Inject, Verify, Delete)](#secrets-with-devbox)
1213

1314
<a id="devbox-from-blueprint-lifecycle"></a>
1415
## Devbox From Blueprint (Run Command, Shutdown)
@@ -70,3 +71,32 @@ yarn test:examples
7071
```
7172

7273
**Source:** [`examples/mcp-github-tools.ts`](./examples/mcp-github-tools.ts)
74+
75+
<a id="secrets-with-devbox"></a>
76+
## Secrets with Devbox (Create, Inject, Verify, Delete)
77+
78+
**Use case:** Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
79+
80+
**Tags:** `secrets`, `devbox`, `environment-variables`, `cleanup`
81+
82+
### Workflow
83+
- Create a secret with a test value
84+
- Create a devbox with the secret mapped to an env var
85+
- Execute a command that reads the secret from the environment
86+
- Verify the value matches
87+
- Shutdown devbox and delete secret
88+
89+
### Prerequisites
90+
- `RUNLOOP_API_KEY`
91+
92+
### Run
93+
```sh
94+
yarn tsn -T examples/secrets-with-devbox.ts
95+
```
96+
97+
### Test
98+
```sh
99+
yarn test:examples
100+
```
101+
102+
**Source:** [`examples/secrets-with-devbox.ts`](./examples/secrets-with-devbox.ts)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ The SDK provides object-oriented interfaces for all major Runloop resources:
7979
- **[`runloop.agent`](https://runloopai.github.io/api-client-ts/stable/classes/sdk.AgentOps.html)** - Agent management (create, list agents from npm/pip/git)
8080
- **[`runloop.scenario`](https://runloopai.github.io/api-client-ts/stable/classes/sdk.ScenarioOps.html)** - Scenario management (list scenarios, start runs)
8181
- **[`runloop.scorer`](https://runloopai.github.io/api-client-ts/stable/classes/sdk.ScorerOps.html)** - Scorer management (create, list, update)
82+
- **[`runloop.secret`](https://runloopai.github.io/api-client-ts/stable/classes/sdk.SecretOps.html)** - Secret management (create, update, list, delete encrypted key-value pairs)
8283
- **[`runloop.api`](https://runloopai.github.io/api-client-ts/stable/modules/types.html)** - Direct access to the REST API client
8384

8485
## TypeScript Support

examples/mcp-github-tools.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,13 @@ export async function recipe(ctx: RecipeContext, options: McpExampleOptions): Pr
7878
cleanup.add(`mcp_config:${mcpConfig.id}`, () => sdk.mcpConfig.fromId(mcpConfig.id).delete());
7979

8080
// Store the GitHub PAT as a Runloop secret
81-
// Note: secrets are currently available via sdk.api, not sdk.secret ops
82-
const secretName = uniqueName('example-github-mcp');
83-
await sdk.api.secrets.create({
81+
const secretName = 'GITHUB_MCP_EXAMPLE';
82+
const secret = await sdk.secret.create({
8483
name: secretName,
8584
value: githubToken,
8685
});
8786
resourcesCreated.push(`secret:${secretName}`);
88-
cleanup.add(`secret:${secretName}`, () => sdk.api.secrets.delete(secretName));
87+
cleanup.add(`secret:${secretName}`, () => secret.delete());
8988

9089
// Launch a devbox with MCP Hub wiring
9190
const devbox = await sdk.devbox.create({
@@ -97,7 +96,7 @@ export async function recipe(ctx: RecipeContext, options: McpExampleOptions): Pr
9796
mcp: {
9897
MCP_SECRET: {
9998
mcp_config: mcpConfig.id,
100-
secret: secretName,
99+
secret: secret,
101100
},
102101
},
103102
});

examples/registry.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { ExampleResult } from './types';
66
import { runDevboxFromBlueprintLifecycleExample } from './devbox-from-blueprint-lifecycle';
77
import { runMcpGithubToolsExample } from './mcp-github-tools';
8+
import { runSecretsWithDevboxExample } from './secrets-with-devbox';
89

910
export interface ExampleRegistryEntry {
1011
slug: string;
@@ -29,4 +30,11 @@ export const exampleRegistry: ExampleRegistryEntry[] = [
2930
requiredEnv: ['RUNLOOP_API_KEY', 'GITHUB_TOKEN', 'ANTHROPIC_API_KEY'],
3031
run: runMcpGithubToolsExample,
3132
},
33+
{
34+
slug: 'secrets-with-devbox',
35+
title: 'Secrets with Devbox (Create, Inject, Verify, Delete)',
36+
fileName: 'secrets-with-devbox.ts',
37+
requiredEnv: ['RUNLOOP_API_KEY'],
38+
run: runSecretsWithDevboxExample,
39+
},
3240
];

examples/secrets-with-devbox.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env -S npm run tsn -T
2+
3+
/**
4+
---
5+
title: Secrets with Devbox (Create, Inject, Verify, Delete)
6+
slug: secrets-with-devbox
7+
use_case: Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
8+
workflow:
9+
- Create a secret with a test value
10+
- Create a devbox with the secret mapped to an env var
11+
- Execute a command that reads the secret from the environment
12+
- Verify the value matches
13+
- Shutdown devbox and delete secret
14+
tags:
15+
- secrets
16+
- devbox
17+
- environment-variables
18+
- cleanup
19+
prerequisites:
20+
- RUNLOOP_API_KEY
21+
run: yarn tsn -T examples/secrets-with-devbox.ts
22+
test: yarn test:examples
23+
---
24+
*/
25+
26+
import { RunloopSDK } from '@runloop/api-client';
27+
import { wrapRecipe, runAsCli } from './_harness';
28+
import type { RecipeContext, RecipeOutput } from './types';
29+
30+
export async function recipe(ctx: RecipeContext): Promise<RecipeOutput> {
31+
const { cleanup } = ctx;
32+
33+
const sdk = new RunloopSDK({
34+
bearerToken: process.env['RUNLOOP_API_KEY'],
35+
});
36+
37+
const secretName = 'RUNLOOP_SECRET_EXAMPLE';
38+
const secretValue = 'my-secret-value';
39+
40+
const secret = await sdk.secret.create({
41+
name: secretName,
42+
value: secretValue,
43+
});
44+
cleanup.add(`secret:${secretName}`, () => secret.delete());
45+
46+
const devbox = await sdk.devbox.create({
47+
name: 'secrets-example-devbox',
48+
secrets: {
49+
MY_SECRET_ENV: secret,
50+
},
51+
launch_parameters: {
52+
resource_size_request: 'X_SMALL',
53+
keep_alive_time_seconds: 60 * 5,
54+
},
55+
});
56+
cleanup.add(`devbox:${devbox.id}`, () => sdk.devbox.fromId(devbox.id).shutdown());
57+
58+
const result = await devbox.cmd.exec('echo $MY_SECRET_ENV');
59+
const stdout = await result.stdout();
60+
61+
const updatedSecret = await sdk.secret.update(secret, {
62+
value: 'updated-secret-value',
63+
});
64+
65+
const secrets = await sdk.secret.list();
66+
const foundSecret = secrets.find((s) => s.name === secretName);
67+
68+
const secretInfo = await secret.getInfo();
69+
const updatedInfo = await updatedSecret.getInfo();
70+
71+
return {
72+
resourcesCreated: [`secret:${secretName}`, `devbox:${devbox.id}`],
73+
checks: [
74+
{
75+
name: 'secret created successfully',
76+
passed: secret.name === secretName && secretInfo.id.startsWith('sec_'),
77+
details: `name=${secret.name}, id=${secretInfo.id}`,
78+
},
79+
{
80+
name: 'devbox can read secret as env var',
81+
passed: result.exitCode === 0 && stdout.trim() === secretValue,
82+
details: `exitCode=${result.exitCode}, stdout="${stdout.trim()}"`,
83+
},
84+
{
85+
name: 'secret updated successfully',
86+
passed: updatedSecret.name === secretName,
87+
details: `update_time_ms=${updatedInfo.update_time_ms}`,
88+
},
89+
{
90+
name: 'secret appears in list',
91+
passed: foundSecret !== undefined && foundSecret.name === secretName,
92+
details: foundSecret ? `found name=${foundSecret.name}` : 'not found',
93+
},
94+
],
95+
};
96+
}
97+
98+
export const runSecretsWithDevboxExample = wrapRecipe({ recipe });
99+
100+
if (require.main === module) {
101+
void runAsCli(runSecretsWithDevboxExample);
102+
}

llms.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
- [Devbox lifecycle example](examples/devbox-from-blueprint-lifecycle.ts): Create blueprint, launch devbox, run commands, cleanup
1313
- [MCP GitHub example](examples/mcp-github-tools.ts): MCP Hub integration with Claude Code
14+
- [Secrets with Devbox example](examples/secrets-with-devbox.ts): Create secret, inject into devbox, verify, cleanup
1415

1516
## API Reference
1617

@@ -21,7 +22,7 @@
2122

2223
- Always use `RunloopSDK` as the entry point (not legacy `Runloop` default export)
2324
- Prefer SDK methods (`sdk.devbox`, `sdk.blueprint`, etc.) for object-oriented patterns and convenience
24-
- For resources without SDK coverage (e.g., secrets, benchmarks), use `sdk.api.*` as a fallback
25+
- For resources without SDK coverage (e.g., benchmarks), use `sdk.api.*` as a fallback
2526
- Use `sdk.devbox.create()` to create devboxes; they auto-await readiness
2627
- Use `devbox.cmd.exec('command')` for most commands—blocks until completion, returns `ExecutionResult` with stdout/stderr
2728
- Use `devbox.cmd.execAsync('command')` for long-running or background processes (servers, watchers)—returns immediately with `Execution` handle to check status, get result, or kill

0 commit comments

Comments
 (0)