@runplane/runplane-sdk 1.1.1 → 1.1.3

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
@@ -1,83 +1,122 @@
1
1
  # @runplane/runplane-sdk
2
2
 
3
- Official SDK for the Runplane control plane runtime governance for AI agent actions.
3
+ Control what your AI agents are allowed to do in real time.
4
4
 
5
5
  Runplane sits between your AI agents and execution. Every action passes through `guard()`, which enforces a decision before your code runs.
6
6
 
7
- ## Installation
7
+ ---
8
+
9
+ ## 🚀 Start in 2 Minutes
10
+
11
+ ### 1. Install the SDK
8
12
 
9
13
  ```bash
10
14
  npm install @runplane/runplane-sdk
11
15
  ```
12
16
 
13
- ## Quick Start (CommonJS)
17
+ ---
18
+
19
+ ### 2. Get your API key (Free 14-day trial)
20
+
21
+ 👉 https://runplane.ai/
22
+
23
+ Create your account and copy your API key.
24
+
25
+ ⚠️ **Important:** The SDK will not work without a valid API key.
26
+
27
+ ---
28
+
29
+ ### 3. Run your first protected action
14
30
 
15
31
  ```javascript
16
32
  require("dotenv").config()
17
- const { Runplane } = require("@runplain/runplane-sdk");
33
+ const { Runplane } = require("@runplane/runplane-sdk")
34
+
18
35
  const runplane = new Runplane({
19
36
  apiKey: process.env.RUNPLANE_API_KEY,
20
37
  })
21
38
 
22
- // guard() intercepts execution and enforces the decision
23
- await runplane.guard(
24
- "transfer_funds",
25
- "finance-system",
26
- { fromAccountId: "acc_1", toAccountId: "acc_2", amount: 400 },
27
- async () => {
28
- return await executeTransfer()
29
- }
30
- )
39
+ async function main() {
40
+ await runplane.guard(
41
+ "transfer_funds",
42
+ "finance-system",
43
+ { fromAccountId: "acc_1", toAccountId: "acc_2", amount: 400 },
44
+ async () => {
45
+ return await executeTransfer()
46
+ }
47
+ )
48
+ }
49
+
50
+ main()
31
51
  ```
32
52
 
33
- ## How It Works
53
+ ---
54
+
55
+ ## 💡 What Runplane Does
56
+
57
+ Runplane gives you **real-time control over AI agent actions**.
58
+
59
+ Before any action runs, Runplane evaluates:
60
+
61
+ * Policies
62
+ * Risk
63
+ * Context
34
64
 
35
- `guard()` calls the Runplane API over the network before executing your handler:
65
+ And returns a decision:
66
+
67
+ * **ALLOW** → execution continues
68
+ * **BLOCK** → action is stopped
69
+ * **REQUIRE_APPROVAL** → human approval required
70
+
71
+ ---
72
+
73
+ ## ⚙️ How It Works
74
+
75
+ `guard()` calls the Runplane API before executing your handler:
36
76
 
37
77
  1. Sends action + target + context to Runplane
38
78
  2. Runplane evaluates policies and risk
39
- 3. Returns one of three decisions:
40
- - **ALLOW** → handler executes immediately
41
- - **BLOCK** → throws `RunplaneError`, handler never runs
42
- - **REQUIRE_APPROVAL** → throws `RunplaneError`, awaits human decision
79
+ 3. Returns a decision:
43
80
 
44
- ## API Reference
81
+ * **ALLOW** → handler executes
82
+ * **BLOCK** → throws `RunplaneError`
83
+ * **REQUIRE_APPROVAL** → throws `RunplaneError` and waits for approval
45
84
 
46
- ### `new Runplane(config)`
85
+ ---
47
86
 
48
- Create a new Runplane client.
87
+ ## 📘 API Reference
88
+
89
+ ### `new Runplane(config)`
49
90
 
50
91
  ```javascript
51
92
  const runplane = new Runplane({
52
- apiKey: "your_api_key", // Required
53
- baseUrl: "https://runplane.ai", // Optional, defaults to https://runplane.ai
93
+ apiKey: "your_api_key",
94
+ baseUrl: "https://runplane.ai",
54
95
  })
55
96
  ```
56
97
 
57
- ### `guard(action, target, context, handler)`
98
+ ---
58
99
 
59
- The primary integration method. Wraps your action with Runplane enforcement.
100
+ ### `guard(action, target, context, handler)`
60
101
 
61
102
  ```javascript
62
103
  const result = await runplane.guard(
63
- "delete_record", // action type
64
- "hr_system", // target resource
65
- { employeeId: "emp_123" }, // context
104
+ "delete_record",
105
+ "hr_system",
106
+ { employeeId: "emp_123" },
66
107
  async () => {
67
- // Your code here - only runs if ALLOW
68
108
  return await deleteEmployee("emp_123")
69
109
  }
70
110
  )
71
111
  ```
72
112
 
73
- **Returns:** The result of `handler()` if decision is ALLOW.
113
+ **Returns:** handler result if ALLOW
114
+ **Throws:** `RunplaneError` if BLOCK or REQUIRE_APPROVAL
74
115
 
75
- **Throws:** `RunplaneError` if decision is BLOCK or REQUIRE_APPROVAL.
116
+ ---
76
117
 
77
118
  ### `decide(payload)`
78
119
 
79
- Low-level method to request a decision without automatic enforcement.
80
-
81
120
  ```javascript
82
121
  const result = await runplane.decide({
83
122
  action: "send_email",
@@ -85,17 +124,19 @@ const result = await runplane.decide({
85
124
  context: { recipients: 1200 },
86
125
  })
87
126
 
88
- console.log(result.decision) // "ALLOW" | "BLOCK" | "REQUIRE_APPROVAL"
127
+ console.log(result.decision)
89
128
  ```
90
129
 
91
- ## Handling Decisions
130
+ ---
131
+
132
+ ## 🧠 Handling Decisions
92
133
 
93
134
  ```javascript
94
135
  const { Runplane, RunplaneError } = require("@runplane/runplane-sdk")
95
136
 
96
137
  try {
97
138
  await runplane.guard("action", "target", {}, async () => {
98
- // ...
139
+ // your code
99
140
  })
100
141
  } catch (err) {
101
142
  if (err instanceof RunplaneError) {
@@ -109,36 +150,50 @@ try {
109
150
  }
110
151
  ```
111
152
 
112
- ## Decision Types
153
+ ---
154
+
155
+ ## 📊 Decision Types
113
156
 
114
- | Decision | Behavior |
115
- |----------|----------|
116
- | `ALLOW` | Handler executes immediately, result returned |
117
- | `BLOCK` | Throws `RunplaneError` with code `"BLOCK"` |
118
- | `REQUIRE_APPROVAL` | Throws `RunplaneError` with code `"REQUIRE_APPROVAL"` |
157
+ | Decision | Behavior |
158
+ | ------------------ | ---------------------------- |
159
+ | `ALLOW` | Handler executes immediately |
160
+ | `BLOCK` | Throws `RunplaneError` |
161
+ | `REQUIRE_APPROVAL` | Requires human approval |
119
162
 
120
- ## Error Object
163
+ ---
121
164
 
122
- When `guard()` throws, the error includes:
165
+ ## Error Object
123
166
 
124
167
  ```javascript
125
168
  {
126
169
  message: "Runplane blocked this action",
127
- code: "BLOCK", // "BLOCK" | "REQUIRE_APPROVAL" | "API_ERROR"
128
- runplane: { // Full decision response
170
+ code: "BLOCK",
171
+ runplane: {
129
172
  decision: "BLOCK",
130
- reason: "Policy: destructive action on production database",
173
+ reason: "Policy violation",
131
174
  requestId: "req_abc123",
132
175
  riskScore: 91,
133
176
  }
134
177
  }
135
178
  ```
136
179
 
137
- ## Requirements
180
+ ---
181
+
182
+ ## ⚠️ Requirements
183
+
184
+ * Node.js 18+
185
+ * Runplane API key 👉 https://runplane.ai/
186
+ * Internet access
187
+
188
+ ---
189
+
190
+ ## 💬 Feedback
191
+
192
+ Got stuck? Something unclear? Want to suggest improvements?
193
+
194
+ 👉 https://runplane.ai/
138
195
 
139
- - Node.js 18+
140
- - Valid Runplane API key
141
- - Network access to `https://runplane.ai`
196
+ ---
142
197
 
143
198
  ## License
144
199
 
package/dist/index.d.ts CHANGED
@@ -104,138 +104,17 @@ type RunplaneErrorCode = "BLOCKED" | "DENIED" | "TIMEOUT" | "NETWORK_ERROR" | "I
104
104
  * @runplane/runplane-sdk - Main client for the Runplane API
105
105
  */
106
106
 
107
- /**
108
- * Runplane SDK Client
109
- *
110
- * The main entry point for interacting with the Runplane control plane.
111
- * Use this to request execution clearance before performing sensitive actions.
112
- *
113
- * @example
114
- * ```typescript
115
- * import { Runplane } from "@runplane/runplane-sdk";
116
- *
117
- * const runplane = new Runplane({
118
- * apiKey: process.env.RUNPLANE_SYSTEM_KEY!,
119
- * baseUrl: "https://runplane.ai",
120
- * failMode: "closed"
121
- * });
122
- *
123
- * const decision = await runplane.decide({
124
- * actionType: "send_email",
125
- * target: "marketing_list",
126
- * context: { recipients: 1200 }
127
- * });
128
- *
129
- * if (decision.decision === "ALLOW") {
130
- * // Proceed with the action
131
- * }
132
- * ```
133
- */
134
107
  declare class Runplane {
135
108
  private readonly baseUrl;
136
109
  private readonly apiKey;
137
110
  private readonly timeoutMs;
138
111
  private readonly failMode;
139
112
  private readonly approvalPoller;
113
+ private hasLoggedInit;
140
114
  constructor(config: RunplaneConfig);
141
- /**
142
- * Request a decision from the Runplane control plane.
143
- *
144
- * Call this before executing any sensitive action. The response will indicate
145
- * whether the action should be ALLOWED, BLOCKED, or REQUIRE_APPROVAL.
146
- *
147
- * @param request - The action details to evaluate
148
- * @returns The decision response
149
- *
150
- * @example
151
- * ```typescript
152
- * const decision = await runplane.decide({
153
- * actionType: "delete_record",
154
- * target: "users.prod",
155
- * context: { recordId: "usr_123", reason: "gdpr_request" }
156
- * });
157
- *
158
- * switch (decision.decision) {
159
- * case "ALLOW":
160
- * await deleteRecord(recordId);
161
- * break;
162
- * case "BLOCK":
163
- * console.error("Action blocked:", decision.reason);
164
- * break;
165
- * case "REQUIRE_APPROVAL":
166
- * // Wait for human approval
167
- * const result = await runplane.waitForApproval(decision.requestId);
168
- * if (result.approved) {
169
- * await deleteRecord(recordId);
170
- * }
171
- * break;
172
- * }
173
- * ```
174
- */
175
115
  decide(request: DecideRequest): Promise<DecideResponse>;
176
- /**
177
- * Wait for an approval decision on a pending request.
178
- *
179
- * Use this when decide() returns REQUIRE_APPROVAL. This method will poll
180
- * the approval endpoint until the request is approved, denied, or times out.
181
- *
182
- * @param requestId - The requestId from the decide() response
183
- * @param options - Optional configuration for polling behavior
184
- * @returns The approval result
185
- *
186
- * @example
187
- * ```typescript
188
- * const decision = await runplane.decide({ ... });
189
- *
190
- * if (decision.decision === "REQUIRE_APPROVAL") {
191
- * console.log("Waiting for approval...");
192
- *
193
- * const result = await runplane.waitForApproval(decision.requestId, {
194
- * timeoutMs: 600000, // 10 minutes
195
- * onPoll: (status, elapsed) => {
196
- * console.log(`Still waiting... ${elapsed}ms elapsed`);
197
- * }
198
- * });
199
- *
200
- * if (result.approved) {
201
- * console.log("Approved by:", result.resolvedBy);
202
- * } else {
203
- * console.log("Denied:", result.comment);
204
- * }
205
- * }
206
- * ```
207
- */
208
116
  waitForApproval(requestId: string, options?: WaitForApprovalOptions): Promise<WaitForApprovalResult>;
209
- /**
210
- * Guard a function with containment evaluation.
211
- *
212
- * This is a convenience method that wraps decide() and waitForApproval()
213
- * into a single call. If the action is blocked or denied, it throws.
214
- * If approved, it executes the provided function.
215
- *
216
- * @param actionType - Type of action being performed
217
- * @param target - Target resource
218
- * @param context - Additional context
219
- * @param fn - Function to execute if allowed
220
- * @returns The result of fn()
221
- * @throws RunplaneError if blocked, denied, or times out (in closed mode)
222
- *
223
- * @example
224
- * ```typescript
225
- * const result = await runplane.guard(
226
- * "payment_transfer",
227
- * "external_bank",
228
- * { amount: 50000, currency: "USD" },
229
- * async () => {
230
- * return await paymentService.transfer(amount, recipient);
231
- * }
232
- * );
233
- * ```
234
- */
235
117
  guard<T>(actionType: string, target: string, context: Record<string, unknown> | null, fn: () => Promise<T>): Promise<T>;
236
- /**
237
- * Handle API failures according to failMode
238
- */
239
118
  private handleFailure;
240
119
  }
241
120
 
package/dist/index.js CHANGED
@@ -138,10 +138,16 @@ var DEFAULT_APPROVAL_TIMEOUT_MS = 3e5;
138
138
  var DEFAULT_APPROVAL_POLL_INTERVAL_MS = 2e3;
139
139
  var Runplane = class {
140
140
  constructor(config) {
141
- if (!config.apiKey) {
142
- throw new RunplaneError(
143
- "API key is required",
144
- "INVALID_CONFIG"
141
+ // 🔥 New: log only once per instance
142
+ this.hasLoggedInit = false;
143
+ if (!config?.apiKey || config.apiKey.trim() === "") {
144
+ throw new Error(
145
+ `Runplane API key is required.
146
+
147
+ \u{1F449} Start your free 14-day trial:
148
+ https://runplane.ai/
149
+
150
+ Then add your API key to the SDK config.`
145
151
  );
146
152
  }
147
153
  this.apiKey = config.apiKey;
@@ -155,40 +161,6 @@ var Runplane = class {
155
161
  pollIntervalMs: config.approvalPollIntervalMs ?? DEFAULT_APPROVAL_POLL_INTERVAL_MS
156
162
  });
157
163
  }
158
- /**
159
- * Request a decision from the Runplane control plane.
160
- *
161
- * Call this before executing any sensitive action. The response will indicate
162
- * whether the action should be ALLOWED, BLOCKED, or REQUIRE_APPROVAL.
163
- *
164
- * @param request - The action details to evaluate
165
- * @returns The decision response
166
- *
167
- * @example
168
- * ```typescript
169
- * const decision = await runplane.decide({
170
- * actionType: "delete_record",
171
- * target: "users.prod",
172
- * context: { recordId: "usr_123", reason: "gdpr_request" }
173
- * });
174
- *
175
- * switch (decision.decision) {
176
- * case "ALLOW":
177
- * await deleteRecord(recordId);
178
- * break;
179
- * case "BLOCK":
180
- * console.error("Action blocked:", decision.reason);
181
- * break;
182
- * case "REQUIRE_APPROVAL":
183
- * // Wait for human approval
184
- * const result = await runplane.waitForApproval(decision.requestId);
185
- * if (result.approved) {
186
- * await deleteRecord(recordId);
187
- * }
188
- * break;
189
- * }
190
- * ```
191
- */
192
164
  async decide(request) {
193
165
  const controller = new AbortController();
194
166
  const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
@@ -197,7 +169,7 @@ var Runplane = class {
197
169
  method: "POST",
198
170
  headers: {
199
171
  "Content-Type": "application/json",
200
- "Authorization": `Bearer ${this.apiKey}`
172
+ Authorization: `Bearer ${this.apiKey}`
201
173
  },
202
174
  body: JSON.stringify({
203
175
  agentKey: this.apiKey,
@@ -240,68 +212,16 @@ var Runplane = class {
240
212
  clearTimeout(timeout);
241
213
  }
242
214
  }
243
- /**
244
- * Wait for an approval decision on a pending request.
245
- *
246
- * Use this when decide() returns REQUIRE_APPROVAL. This method will poll
247
- * the approval endpoint until the request is approved, denied, or times out.
248
- *
249
- * @param requestId - The requestId from the decide() response
250
- * @param options - Optional configuration for polling behavior
251
- * @returns The approval result
252
- *
253
- * @example
254
- * ```typescript
255
- * const decision = await runplane.decide({ ... });
256
- *
257
- * if (decision.decision === "REQUIRE_APPROVAL") {
258
- * console.log("Waiting for approval...");
259
- *
260
- * const result = await runplane.waitForApproval(decision.requestId, {
261
- * timeoutMs: 600000, // 10 minutes
262
- * onPoll: (status, elapsed) => {
263
- * console.log(`Still waiting... ${elapsed}ms elapsed`);
264
- * }
265
- * });
266
- *
267
- * if (result.approved) {
268
- * console.log("Approved by:", result.resolvedBy);
269
- * } else {
270
- * console.log("Denied:", result.comment);
271
- * }
272
- * }
273
- * ```
274
- */
275
215
  async waitForApproval(requestId, options) {
276
216
  return this.approvalPoller.poll(requestId, options);
277
217
  }
278
- /**
279
- * Guard a function with containment evaluation.
280
- *
281
- * This is a convenience method that wraps decide() and waitForApproval()
282
- * into a single call. If the action is blocked or denied, it throws.
283
- * If approved, it executes the provided function.
284
- *
285
- * @param actionType - Type of action being performed
286
- * @param target - Target resource
287
- * @param context - Additional context
288
- * @param fn - Function to execute if allowed
289
- * @returns The result of fn()
290
- * @throws RunplaneError if blocked, denied, or times out (in closed mode)
291
- *
292
- * @example
293
- * ```typescript
294
- * const result = await runplane.guard(
295
- * "payment_transfer",
296
- * "external_bank",
297
- * { amount: 50000, currency: "USD" },
298
- * async () => {
299
- * return await paymentService.transfer(amount, recipient);
300
- * }
301
- * );
302
- * ```
303
- */
304
218
  async guard(actionType, target, context, fn) {
219
+ if (!this.hasLoggedInit) {
220
+ console.log(
221
+ "Runplane active. Manage your policies at https://runplane.ai/"
222
+ );
223
+ this.hasLoggedInit = true;
224
+ }
305
225
  const response = await this.decide({
306
226
  actionType,
307
227
  target,
@@ -337,9 +257,6 @@ var Runplane = class {
337
257
  response.requestId
338
258
  );
339
259
  }
340
- /**
341
- * Handle API failures according to failMode
342
- */
343
260
  handleFailure(reason, request) {
344
261
  const requestId = request.requestId ?? crypto.randomUUID();
345
262
  if (this.failMode === "open") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runplane/runplane-sdk",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Runtime governance SDK for AI agent actions. Wrap sensitive operations with guard() to enforce ALLOW, BLOCK, or REQUIRE_APPROVAL decisions before execution.",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -37,7 +37,7 @@
37
37
  "license": "MIT",
38
38
  "repository": {
39
39
  "type": "git",
40
- "url": "https://github.com/runplane/sdk.git"
40
+ "url": "git+https://github.com/runplane/sdk.git"
41
41
  },
42
42
  "homepage": "https://runplane.ai/developer",
43
43
  "bugs": {