| export interface PulseOptions { | ||
| interval?: string; | ||
| url?: string; | ||
| expect?: string; | ||
| method?: string; | ||
| headers?: Record<string, string>; | ||
| body?: string; | ||
| ttl?: string; | ||
| subdomain?: string; | ||
| } | ||
| export interface PulseResult { | ||
| subdomain: string; | ||
| publicUrl: string; | ||
| mode: 'active' | 'expect'; | ||
| intervalMs: number; | ||
| target?: string; | ||
| method?: string; | ||
| autoStopAt: string | null; | ||
| stopToken: string; | ||
| pricing: { | ||
| runsPerCredit: number; | ||
| monthlyCapCredits: number; | ||
| note: string; | ||
| }; | ||
| cost: number; | ||
| balance: number; | ||
| } | ||
| export declare class PulseError extends Error { | ||
| code: string; | ||
| constructor(code: string, message?: string); | ||
| } | ||
| export declare function createPulse(opts: PulseOptions): Promise<PulseResult>; | ||
| /** Stop a pulse with the stopToken returned at creation. */ | ||
| export declare function stopPulse(subdomain: string, stopToken: string): Promise<void>; |
| /** | ||
| * `otterkit pulse` - the third primitive: scheduled, stateful HTTP. | ||
| * Active mode calls a URL on an interval and stores every run server-side; | ||
| * expect mode is a dead-man's switch that alerts when check-ins stop. | ||
| * Fully server-side (no daemon): the CLI provisions and walks away. | ||
| */ | ||
| import { getToken } from './credentials.js'; | ||
| const TUNNEL_SERVER = process.env.OTTERKIT_TUNNEL_URL || 'https://otterkit.app'; | ||
| export class PulseError extends Error { | ||
| code; | ||
| constructor(code, message) { | ||
| super(message ?? code); | ||
| this.code = code; | ||
| this.name = 'PulseError'; | ||
| } | ||
| } | ||
| export async function createPulse(opts) { | ||
| const token = getToken(); | ||
| if (!token) | ||
| throw new PulseError('not_logged_in', 'Run `otterkit login` first.'); | ||
| const resp = await fetch(`${TUNNEL_SERVER}/api/agent/pulses`, { | ||
| method: 'POST', | ||
| headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| interval: opts.interval, | ||
| url: opts.url, | ||
| expect: opts.expect, | ||
| method: opts.method, | ||
| headers: opts.headers, | ||
| body: opts.body, | ||
| ttl: opts.ttl, | ||
| subdomain: opts.subdomain, | ||
| }), | ||
| }); | ||
| const json = (await resp.json().catch(() => ({}))); | ||
| if (!resp.ok) { | ||
| throw new PulseError(json.error ?? `http_${resp.status}`, json.message ?? `Pulse provisioning failed (${resp.status}).`); | ||
| } | ||
| return json; | ||
| } | ||
| /** Stop a pulse with the stopToken returned at creation. */ | ||
| export async function stopPulse(subdomain, stopToken) { | ||
| const resp = await fetch(`${TUNNEL_SERVER}/api/agent/tunnels/${subdomain}?token=${encodeURIComponent(stopToken)}`, { method: 'DELETE' }); | ||
| if (!resp.ok) | ||
| throw new PulseError(`http_${resp.status}`, 'Could not stop the pulse.'); | ||
| } |
+46
-0
@@ -26,2 +26,3 @@ /** | ||
| import { sendCore, verifyCore, SendError } from './send.js'; | ||
| import { createPulse } from './pulse.js'; | ||
| import { listEvents } from './providers.js'; | ||
@@ -463,2 +464,47 @@ import { createWait, listWaits, cancelWait, sessionToken } from './waits.js'; | ||
| }); | ||
| server.registerTool('pulse_create', { | ||
| title: 'Create a pulse (scheduled HTTP / dead-man switch)', | ||
| description: 'Provision a pulse - the scheduled third primitive. Active mode calls a public URL on an interval (min 30s) and stores every response as run history (read it with webhook_history). Expect mode returns a URL that must be pinged within the window, alerting the owner on silence. Fully server-side: survives this machine dying. Costs 1 credit per 100 runs, never more than 300 credits/30 days; 1 credit charged at creation. Default TTL "never".', | ||
| inputSchema: { | ||
| interval: z | ||
| .string() | ||
| .optional() | ||
| .describe('Active mode: run every e.g. 30s, 10m, 4h, 1d (min 30s)'), | ||
| url: z.string().optional().describe('Active mode: public http(s) URL to call each run'), | ||
| expect: z | ||
| .string() | ||
| .optional() | ||
| .describe('Expect mode: alert if no check-in arrives within e.g. 15m, 1h, 1d'), | ||
| method: z.string().optional().describe('HTTP method for active runs (default GET)'), | ||
| body: z.string().optional().describe('Request body for active runs'), | ||
| ttl: z.string().optional().describe('Auto-stop after e.g. 4h, 3d (default: never)'), | ||
| subdomain: z.string().optional().describe('Stable name for the pulse'), | ||
| }, | ||
| }, async (args) => { | ||
| try { | ||
| const r = await createPulse({ | ||
| interval: args.interval, | ||
| url: args.url, | ||
| expect: args.expect, | ||
| method: args.method, | ||
| body: args.body, | ||
| ttl: args.ttl, | ||
| subdomain: args.subdomain, | ||
| }); | ||
| return { | ||
| content: [{ type: 'text', text: JSON.stringify(r) }], | ||
| }; | ||
| } | ||
| catch (e) { | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: JSON.stringify({ error: e.message }), | ||
| }, | ||
| ], | ||
| isError: true, | ||
| }; | ||
| } | ||
| }); | ||
| server.registerTool('webhook_history', { | ||
@@ -465,0 +511,0 @@ title: 'Server-side stored capture history', |
+1
-1
| { | ||
| "name": "otterkit", | ||
| "version": "0.34.0", | ||
| "version": "0.35.0", | ||
| "description": "OtterKit CLI - provision and connect tunnels for AI agents", | ||
@@ -5,0 +5,0 @@ "mcpName": "io.github.useotterkit/otterkit", |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
279942
3%48
4.35%6528
3%28
3.7%23
9.52%