
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@shiplightai/sdk
Advanced tools
A companion SDK for Playwright that makes your tests resilient to UI changes like dynamic IDs, layout rearrangements, and styling updates.
npm install @shiplightai/sdk playwright
import { chromium } from 'playwright';
import { createAgent, configureSdk } from '@shiplightai/sdk';
// Configure SDK with API key (call once at startup)
configureSdk({
env: { GOOGLE_API_KEY: process.env.GOOGLE_API_KEY! },
// Or for Claude: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! }
});
// Create an agent
const agent = createAgent({
model: 'gemini-2.5-pro',
});
// Use with Playwright
const browser = await chromium.launch();
const page = await browser.newPage();
// Login using the Sauce Labs demo site (public test site)
await agent.login(page, {
url: 'https://www.saucedemo.com/',
username: 'standard_user',
password: 'secret_sauce',
});
// Verify login succeeded
await agent.assert(page, 'Products page is visible');
// Extract data from the page
await agent.extract(page, 'the first product name', 'productName');
console.log('First product:', agent.getVariable('productName'));
await browser.close();
Extend the agent with custom actions for your specific use case:
import { createAgent, z } from '@shiplightai/sdk';
const agent = createAgent({ model: 'gemini-2.5-pro' });
// Register a custom action
agent.registerAction({
name: 'extract_email_code',
description: 'Extract verification code from email inbox',
schema: z.object({
email_address: z.string().describe('The email address to check'),
code_type: z.enum(['verification', 'reset']).describe('Type of code'),
}),
async execute(args, ctx) {
// Your custom logic here
const code = await myEmailService.getCode(args.email_address, args.code_type);
// Store the result for later use
ctx.variableStore.set('verification_code', code);
return { success: true, message: `Found code: ${code}` };
},
});
// The agent will automatically use your action when needed
await agent.act(page, 'Get the verification code from email');
await agent.act(page, 'Enter {{ verification_code }} in the input field');
createAgent(options)Create a new agent instance.
const agent = createAgent({
// Required: LLM model to use
model: 'gemini-2.5-pro',
// Optional: Initial variables
variables: { username: 'test@example.com' },
// Optional: Keys to mark as sensitive (masked in logs)
sensitiveKeys: ['password', 'apiKey'],
// Optional: Directory for test data files
testDataDir: './test-data',
// Optional: Directory for downloads
downloadDir: './downloads',
// Optional: Self-healing strategy ('none' | 'single' | 'multi')
selfHealingStrategy: 'single',
});
| Provider | Model | Notes |
|---|---|---|
gemini-2.5-pro | Recommended, requires GOOGLE_API_KEY | |
gemini-3-pro-preview | More powerful, higher cost | |
| Anthropic | claude-haiku-4-5 | Fast and affordable, requires ANTHROPIC_API_KEY |
| Anthropic | claude-sonnet-4-5 | Balanced speed and capability |
| Anthropic | claude-opus-4-5 | Most capable |
| OpenAI | computer-use-preview | Optional, for vision operations (drag and drop) |
agent.registerAction(action)Register a custom action.
agent.registerAction({
// Unique action name (snake_case recommended)
name: 'my_action',
// Description for the agent
description: 'What this action does and when to use it',
// Zod schema for parameters
schema: z.object({
param1: z.string().describe('Description of the parameter'),
param2: z.number().optional(),
}),
// Execute function
async execute(args, ctx) {
// args: validated parameters
// ctx.page: Playwright page
// ctx.variableStore: access to variables
return { success: true, message: 'Optional status message' };
},
});
agent.act(page, instruction)Perform a single action on the page.
await agent.act(page, 'Click the login button');
await agent.act(page, 'Fill the email field with test@example.com');
await agent.act(page, 'Select "Express" from shipping dropdown');
agent.run(page, instruction, options?)Run a multi-step instruction until the goal is achieved.
await agent.run(page, 'Complete the checkout process');
await agent.run(page, 'Fill out the entire registration form');
await agent.run(page, 'Add 3 items to cart', { maxSteps: 10 });
agent.assert(page, statement)Assert a condition (throws on failure).
await agent.assert(page, 'Login button is visible');
await agent.assert(page, 'Cart contains 3 items');
agent.evaluate(page, statement)Evaluate a condition (returns boolean, doesn't throw).
const isLoggedIn = await agent.evaluate(page, 'User is logged in');
if (!isLoggedIn) {
await agent.run(page, 'Click the login button');
}
agent.extract(page, description, variableName)Extract data from the page and store in a variable.
await agent.extract(page, 'the order total', 'orderTotal');
await agent.run(page, 'Verify {{ orderTotal }} is displayed on receipt');
agent.waitUntil(page, condition, timeoutSeconds?)Wait until a condition becomes true.
await agent.waitUntil(page, 'Loading spinner is no longer visible');
const appeared = await agent.waitUntil(page, 'Table shows at least 5 rows', 30);
if (!appeared) {
throw new Error('Data did not load in time');
}
The execute function receives a context object:
interface IActionExecutionContext {
// Playwright page instance
page: Page;
// Variable store for reading/writing variables
variableStore: VariableStore;
}
async execute(args, ctx) {
// Read a variable
const email = ctx.variableStore.get('email');
// Set a variable
ctx.variableStore.set('result', 'some value');
// Set a sensitive variable (masked in logs)
ctx.variableStore.set('token', secretToken, true);
return { success: true };
}
Configure SDK-wide settings before creating agents:
import { configureSdk, getSdkConfig, LogLevel } from '@shiplightai/sdk';
configureSdk({
// Log level: DEBUG, INFO, WARN, ERROR
logLevel: LogLevel.INFO,
// Enable detailed agent logging
debugAgent: false,
// Environment variables (API keys)
env: {
// At least one model provider key is required
GOOGLE_API_KEY: 'your-google-api-key', // for Gemini models
// ANTHROPIC_API_KEY: 'your-anthropic-key', // for Claude models
// OPENAI_API_KEY: 'sk-...', // for vision operations
},
// Optional: paths for logs and results
agentLogPath: './logs/agent.log',
testResultsJsonPath: './results.json',
});
// Read current config
const config = getSdkConfig();
API keys must be passed via configureSdk({ env }) — the SDK does not read process.env directly.
| Key | Required | Description |
|---|---|---|
GOOGLE_API_KEY | Yes* | Google AI API key for Gemini models |
ANTHROPIC_API_KEY | Yes* | Anthropic API key for Claude models |
OPENAI_API_KEY | No | OpenAI API key (for vision operations) |
*At least one model provider key is required.
MIT
FAQs
Shiplight SDK with pluggable custom actions
The npm package @shiplightai/sdk receives a total of 12 weekly downloads. As such, @shiplightai/sdk popularity was classified as not popular.
We found that @shiplightai/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.