From ebc546828488353eeb3d8b0441519721e3ae9925 Mon Sep 17 00:00:00 2001 From: rcholic Date: Sun, 21 Dec 2025 14:21:04 -0800 Subject: [PATCH 1/4] add specs --- spec/README.md | 72 +++++++++++ spec/SNAPSHOT_V1.md | 208 ++++++++++++++++++++++++++++++ spec/sdk-types.md | 259 ++++++++++++++++++++++++++++++++++++++ spec/snapshot.schema.json | 148 ++++++++++++++++++++++ 4 files changed, 687 insertions(+) create mode 100644 spec/README.md create mode 100644 spec/SNAPSHOT_V1.md create mode 100644 spec/sdk-types.md create mode 100644 spec/snapshot.schema.json diff --git a/spec/README.md b/spec/README.md new file mode 100644 index 0000000..008a18b --- /dev/null +++ b/spec/README.md @@ -0,0 +1,72 @@ +# Sentience API Specification + +This directory contains the **single source of truth** for the API contract between the Chrome extension and SDKs. + +## Files + +- **`snapshot.schema.json`** - JSON Schema for snapshot response validation +- **`SNAPSHOT_V1.md`** - Human-readable snapshot API contract +- **`sdk-types.md`** - SDK-level type definitions (ActionResult, WaitResult, TraceStep) + +## Purpose + +These specifications ensure: +1. **Consistency**: Both Python and TypeScript SDKs implement the same contract +2. **Validation**: SDKs can validate extension responses +3. **Type Safety**: Strong typing in both languages +4. **Documentation**: Clear reference for developers + +## Usage + +### For SDK Developers + +1. **Read** `SNAPSHOT_V1.md` for human-readable contract +2. **Use** `snapshot.schema.json` for JSON Schema validation +3. **Reference** `sdk-types.md` for SDK-level types + +### For Extension Developers + +1. **Ensure** extension output matches `snapshot.schema.json` +2. **Update** schema when adding new fields +3. **Version** schema for breaking changes + +## Versioning + +- **v1.0.0**: Initial stable version (Day 1) +- Future versions: Increment major version for breaking changes +- SDKs should validate version and handle compatibility + +## Validation + +Both SDKs should validate extension responses: + +**Python**: +```python +import jsonschema +from spec.snapshot.schema import load_schema + +schema = load_schema() +jsonschema.validate(snapshot_data, schema) +``` + +**TypeScript**: +```typescript +import Ajv from 'ajv'; +import schema from './spec/snapshot.schema.json'; + +const ajv = new Ajv(); +const validate = ajv.compile(schema); +validate(snapshot_data); +``` + +## Testing + +- Validate against real extension output +- Test with edge cases (empty pages, many elements, errors) +- Verify type coercion and defaults + +--- + +**Last Updated**: Day 1 Implementation +**Status**: ✅ Stable + diff --git a/spec/SNAPSHOT_V1.md b/spec/SNAPSHOT_V1.md new file mode 100644 index 0000000..389df5c --- /dev/null +++ b/spec/SNAPSHOT_V1.md @@ -0,0 +1,208 @@ +# Sentience Snapshot API Contract v1 + +**Version**: 1.0.0 +**Last Updated**: [Current Date] +**Status**: Stable + +This document defines the **single source of truth** for the snapshot data structure returned by `window.sentience.snapshot()`. Both Python and TypeScript SDKs must implement this contract exactly. + +## Overview + +The snapshot API returns a structured representation of the current page state, including: +- All interactive elements with semantic roles +- Element positions (bounding boxes) +- Importance scores (AI-optimized ranking) +- Visual cues (primary actions, colors, clickability) +- Optional screenshot + +## Response Structure + +### Top-Level Object + +```typescript +{ + status: "success" | "error", + timestamp?: string, // ISO 8601 + url: string, + viewport?: { width: number, height: number }, + elements: Element[], + screenshot?: string, // Base64 data URL + screenshot_format?: "png" | "jpeg", + error?: string, // If status is "error" + requires_license?: boolean // If license required +} +``` + +### Element Object + +```typescript +{ + id: number, // REQUIRED: Unique identifier (registry index) + role: string, // REQUIRED: Semantic role + text: string | null, // Text content, aria-label, or placeholder + importance: number, // REQUIRED: Importance score (-300 to ~1800) + bbox: BBox, // REQUIRED: Bounding box + visual_cues: VisualCues, // REQUIRED: Visual analysis + in_viewport: boolean, // Is element visible in viewport + is_occluded: boolean, // Is element covered by overlay + z_index: number // CSS z-index (0 if auto) +} +``` + +### BBox (Bounding Box) + +```typescript +{ + x: number, // Left edge in pixels + y: number, // Top edge in pixels + width: number, // Width in pixels + height: number // Height in pixels +} +``` + +### VisualCues + +```typescript +{ + is_primary: boolean, // Visually prominent primary action + background_color_name: string | null, // Named color from palette + is_clickable: boolean // Has pointer cursor or actionable role +} +``` + +## Field Details + +### `id` (required) +- **Type**: `integer` +- **Description**: Unique element identifier, corresponds to index in `window.sentience_registry` +- **Usage**: Used for actions like `click(id)` +- **Stability**: May change between page loads (not persistent) + +### `role` (required) +- **Type**: `string` +- **Values**: `"button"`, `"link"`, `"textbox"`, `"searchbox"`, `"checkbox"`, `"radio"`, `"combobox"`, `"image"`, `"generic"` +- **Description**: Semantic role inferred from HTML tag, ARIA attributes, and context +- **Usage**: Primary filter for query engine + +### `text` (optional) +- **Type**: `string | null` +- **Description**: Text content extracted from element: + - `aria-label` if present + - `value` or `placeholder` for inputs + - `alt` for images + - `innerText` for other elements (truncated to 100 chars) +- **Usage**: Text matching in query engine + +### `importance` (required) +- **Type**: `integer` +- **Range**: -300 to ~1800 +- **Description**: AI-optimized importance score calculated from: + - Role priority (inputs: 1000, buttons: 500, links: 100) + - Area score (larger elements score higher, capped at 200) + - Visual prominence (+200 for primary actions) + - Viewport/occlusion penalties (-500 off-screen, -800 occluded) +- **Usage**: Ranking and filtering elements + +### `bbox` (required) +- **Type**: `BBox` object +- **Description**: Element position and size in viewport coordinates +- **Coordinates**: Relative to viewport (0,0) at top-left +- **Usage**: Spatial queries, visual grounding, click coordinates + +### `visual_cues` (required) +- **Type**: `VisualCues` object +- **Description**: Visual analysis results +- **Fields**: + - `is_primary`: True if element is visually prominent primary action + - `background_color_name`: Nearest named color (32-color palette) or null + - `is_clickable`: True if element has pointer cursor or actionable role + +### `in_viewport` (optional) +- **Type**: `boolean` +- **Description**: True if element is visible in current viewport +- **Default**: `true` (if not present, assume visible) + +### `is_occluded` (optional) +- **Type**: `boolean` +- **Description**: True if element is covered by another element +- **Default**: `false` (if not present, assume not occluded) + +### `z_index` (optional) +- **Type**: `integer` +- **Description**: CSS z-index value (0 if "auto" or not set) +- **Default**: `0` + +## Element Sorting + +Elements in the `elements` array are sorted by: +1. **Primary sort**: `importance` (descending) - most important first +2. **Secondary sort**: `bbox.y` (ascending) - top-to-bottom reading order (if limit applied) + +## Example Response + +```json +{ + "status": "success", + "timestamp": "2025-01-20T10:30:00Z", + "url": "https://example.com", + "viewport": { + "width": 1280, + "height": 800 + }, + "elements": [ + { + "id": 42, + "role": "button", + "text": "Sign In", + "importance": 850, + "bbox": { + "x": 100, + "y": 200, + "width": 120, + "height": 40 + }, + "visual_cues": { + "is_primary": true, + "background_color_name": "blue", + "is_clickable": true + }, + "in_viewport": true, + "is_occluded": false, + "z_index": 0 + } + ] +} +``` + +## Error Response + +```json +{ + "status": "error", + "error": "Headless mode requires a valid license key...", + "requires_license": true +} +``` + +## SDK Implementation Requirements + +Both Python and TypeScript SDKs must: + +1. **Validate** snapshot response against this schema +2. **Parse** all required fields correctly +3. **Handle** optional fields gracefully (defaults) +4. **Type-check** all fields (Pydantic for Python, TypeScript types for TS) +5. **Preserve** field names exactly (no renaming) + +## Versioning + +- **v1.0.0**: Initial stable version +- Future versions will increment major version for breaking changes +- SDKs should validate version and handle compatibility + +## Related Documents + +- `snapshot.schema.json` - JSON Schema validation +- Extension implementation: `sentience-chrome/injected_api.js` +- WASM implementation: `sentience-chrome/src/lib.rs` + diff --git a/spec/sdk-types.md b/spec/sdk-types.md new file mode 100644 index 0000000..0b9c1df --- /dev/null +++ b/spec/sdk-types.md @@ -0,0 +1,259 @@ +# SDK-Level Type Definitions + +**Purpose**: Define SDK-level types that extend the snapshot contract with action results, wait results, and trace steps. + +## Core Types + +### Snapshot +```typescript +interface Snapshot { + status: "success" | "error"; + timestamp?: string; + url: string; + viewport?: { width: number; height: number }; + elements: Element[]; + screenshot?: string; + screenshot_format?: "png" | "jpeg"; + error?: string; + requires_license?: boolean; +} +``` + +### Element +```typescript +interface Element { + id: number; + role: string; + text: string | null; + importance: number; + bbox: BBox; + visual_cues: VisualCues; + in_viewport: boolean; + is_occluded: boolean; + z_index: number; +} +``` + +### BBox +```typescript +interface BBox { + x: number; + y: number; + width: number; + height: number; +} +``` + +### Viewport +```typescript +interface Viewport { + width: number; + height: number; +} +``` + +### VisualCues +```typescript +interface VisualCues { + is_primary: boolean; + background_color_name: string | null; + is_clickable: boolean; +} +``` + +## Action Types + +### ActionResult +```typescript +interface ActionResult { + success: boolean; + duration_ms: number; + outcome?: "navigated" | "dom_updated" | "no_change" | "error"; + url_changed?: boolean; + snapshot_after?: Snapshot; // Optional in Week 1, required in Week 2 + error?: { + code: string; + reason: string; + recovery_hint?: string; + }; +} +``` + +**Fields**: +- `success`: True if action completed successfully +- `duration_ms`: Time taken in milliseconds +- `outcome`: What happened after action (navigation, DOM update, no change, error) +- `url_changed`: True if URL changed (for navigation detection) +- `snapshot_after`: Post-action snapshot (optional in MVP, required for recorder) +- `error`: Error details if action failed + +## Wait & Assert Types + +### WaitResult +```typescript +interface WaitResult { + found: boolean; + element?: Element; + duration_ms: number; + timeout: boolean; +} +``` + +**Fields**: +- `found`: True if element was found +- `element`: Found element (if found) +- `duration_ms`: Time taken to find (or timeout) +- `timeout`: True if wait timed out + +### AssertionError +```typescript +class AssertionError extends Error { + predicate: string | object; + timeout: number; + element?: Element; +} +``` + +## Trace Types (for Recorder) + +### Trace +```typescript +interface Trace { + version: string; // "1.0.0" + created_at: string; // ISO 8601 + start_url: string; + steps: TraceStep[]; +} +``` + +### TraceStep +```typescript +interface TraceStep { + ts: number; // Timestamp (milliseconds since start) + type: "navigation" | "click" | "type" | "press" | "wait" | "assert"; + selector?: string; // Semantic selector (inferred) + element_id?: number; // Element ID + text?: string; // For type actions (may be masked) + key?: string; // For press actions + url?: string; // For navigation + snapshot?: Snapshot; // Optional: snapshot at this step +} +``` + +**Step Types**: +- `navigation`: `goto(url)` +- `click`: Click on element +- `type`: Type text into element +- `press`: Press keyboard key +- `wait`: Wait for element/predicate +- `assert`: Assertion check + +## Query Types + +### QuerySelector +```typescript +// String DSL +type QuerySelectorString = string; // e.g., "role=button text~'Sign in'" + +// Structured object +interface QuerySelectorObject { + role?: string; + text?: string | RegExp; + name?: string | RegExp; + clickable?: boolean; + isPrimary?: boolean; + importance?: number | { min?: number; max?: number }; +} + +type QuerySelector = QuerySelectorString | QuerySelectorObject; +``` + +## Python Equivalents (Pydantic) + +```python +from pydantic import BaseModel +from typing import Optional, List, Union +from datetime import datetime + +class BBox(BaseModel): + x: float + y: float + width: float + height: float + +class Viewport(BaseModel): + width: float + height: float + +class VisualCues(BaseModel): + is_primary: bool + background_color_name: Optional[str] + is_clickable: bool + +class Element(BaseModel): + id: int + role: str + text: Optional[str] + importance: int + bbox: BBox + visual_cues: VisualCues + in_viewport: bool = True + is_occluded: bool = False + z_index: int = 0 + +class Snapshot(BaseModel): + status: str # "success" | "error" + timestamp: Optional[str] = None + url: str + viewport: Optional[Viewport] = None + elements: List[Element] + screenshot: Optional[str] = None + screenshot_format: Optional[str] = None + error: Optional[str] = None + requires_license: Optional[bool] = None + +class ActionResult(BaseModel): + success: bool + duration_ms: int + outcome: Optional[str] = None + url_changed: Optional[bool] = None + snapshot_after: Optional[Snapshot] = None + error: Optional[dict] = None + +class WaitResult(BaseModel): + found: bool + element: Optional[Element] = None + duration_ms: int + timeout: bool + +class TraceStep(BaseModel): + ts: int + type: str + selector: Optional[str] = None + element_id: Optional[int] = None + text: Optional[str] = None + key: Optional[str] = None + url: Optional[str] = None + snapshot: Optional[Snapshot] = None + +class Trace(BaseModel): + version: str + created_at: str + start_url: str + steps: List[TraceStep] +``` + +## Type Validation Rules + +1. **Required Fields**: Must be present and non-null +2. **Optional Fields**: May be omitted or null +3. **Type Coercion**: Numbers should be validated (no NaN, Infinity) +4. **Enum Values**: String enums must match exactly +5. **Array Bounds**: Elements array should be validated (not empty for success status) + +## Compatibility Notes + +- SDKs should handle missing optional fields gracefully +- Default values should match extension behavior +- Type coercion should be minimal (prefer validation errors) + diff --git a/spec/snapshot.schema.json b/spec/snapshot.schema.json new file mode 100644 index 0000000..3a1ab6b --- /dev/null +++ b/spec/snapshot.schema.json @@ -0,0 +1,148 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Sentience Snapshot Schema v1", + "description": "Single source of truth for snapshot data structure between extension and SDKs", + "version": "1.0.0", + "type": "object", + "required": ["status", "url", "elements"], + "properties": { + "status": { + "type": "string", + "enum": ["success", "error"], + "description": "Operation status" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of snapshot" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Current page URL" + }, + "viewport": { + "type": "object", + "required": ["width", "height"], + "properties": { + "width": { + "type": "number", + "description": "Viewport width in pixels" + }, + "height": { + "type": "number", + "description": "Viewport height in pixels" + } + } + }, + "elements": { + "type": "array", + "items": { + "$ref": "#/definitions/Element" + }, + "description": "Array of analyzed elements, sorted by importance (descending)" + }, + "screenshot": { + "type": "string", + "description": "Base64-encoded screenshot data URL (optional)", + "pattern": "^data:image/(png|jpeg);base64," + }, + "screenshot_format": { + "type": "string", + "enum": ["png", "jpeg"], + "description": "Screenshot format (optional)" + }, + "error": { + "type": "string", + "description": "Error message if status is 'error'" + }, + "requires_license": { + "type": "boolean", + "description": "True if operation requires license key (headless mode)" + } + }, + "definitions": { + "Element": { + "type": "object", + "required": ["id", "role", "importance", "bbox", "visual_cues"], + "properties": { + "id": { + "type": "integer", + "description": "Unique element identifier (index in registry)" + }, + "role": { + "type": "string", + "enum": ["button", "link", "textbox", "searchbox", "checkbox", "radio", "combobox", "image", "generic"], + "description": "Semantic role of the element" + }, + "text": { + "type": ["string", "null"], + "description": "Text content, aria-label, or placeholder (max 100 chars)" + }, + "importance": { + "type": "integer", + "description": "Importance score (-300 to ~1800), higher = more important" + }, + "bbox": { + "$ref": "#/definitions/BBox" + }, + "visual_cues": { + "$ref": "#/definitions/VisualCues" + }, + "in_viewport": { + "type": "boolean", + "description": "True if element is visible in viewport" + }, + "is_occluded": { + "type": "boolean", + "description": "True if element is covered by another element" + }, + "z_index": { + "type": "integer", + "description": "CSS z-index value (0 if auto or not set)" + } + } + }, + "BBox": { + "type": "object", + "required": ["x", "y", "width", "height"], + "properties": { + "x": { + "type": "number", + "description": "X coordinate (left edge) in pixels" + }, + "y": { + "type": "number", + "description": "Y coordinate (top edge) in pixels" + }, + "width": { + "type": "number", + "description": "Width in pixels" + }, + "height": { + "type": "number", + "description": "Height in pixels" + } + } + }, + "VisualCues": { + "type": "object", + "required": ["is_primary", "is_clickable"], + "properties": { + "is_primary": { + "type": "boolean", + "description": "True if element is visually prominent primary action" + }, + "background_color_name": { + "type": ["string", "null"], + "description": "Nearest named color from 32-color palette (e.g., 'blue', 'red')" + }, + "is_clickable": { + "type": "boolean", + "description": "True if element has pointer cursor or actionable role" + } + } + } + } +} + From 50ba93c2cdb751141d2499b425f9fcd52e1d398e Mon Sep 17 00:00:00 2001 From: rcholic Date: Sun, 21 Dec 2025 14:31:29 -0800 Subject: [PATCH 2/4] set up for pkg release --- .github/workflows/release.yml | 85 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 49 ++++++++++++++++++++ .gitignore | 56 ++++++++--------------- MANIFEST.in | 5 +++ pyproject.toml | 20 +++++++++ 5 files changed, 177 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 MANIFEST.in diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a397e7c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,85 @@ +name: Release to PyPI + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 0.1.0)' + required: true + type: string + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Extract version from tag or input + id: version + run: | + if [ "${{ github.event_name }}" == "release" ]; then + VERSION=${GITHUB_REF#refs/tags/v} + VERSION=${VERSION#refs/tags/} + else + VERSION="${{ github.event.inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Update version in pyproject.toml + run: | + VERSION="${{ steps.version.outputs.version }}" + sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml + + - name: Update version in __init__.py + run: | + VERSION="${{ steps.version.outputs.version }}" + sed -i "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" sentience/__init__.py + + - name: Build package + run: | + python -m build + + - name: Check package + run: | + twine check dist/* + + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + twine upload dist/* + + - name: Create GitHub Release + if: github.event_name == 'workflow_dispatch' + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.version.outputs.version }} + name: Release v${{ steps.version.outputs.version }} + body: | + Release v${{ steps.version.outputs.version }} of sentience-python + + ## Installation + ```bash + pip install sentience-python==${{ steps.version.outputs.version }} + ``` + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..657bf1e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,49 @@ +name: Test + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Playwright browsers + run: | + python -m pip install --upgrade pip + pip install playwright + playwright install chromium + + - name: Install dependencies + run: | + pip install -e ".[dev]" + + - name: Build extension (if needed) + run: | + if [ -d "../sentience-chrome" ]; then + cd ../sentience-chrome && ./build.sh || echo "Extension build skipped (may not be available in CI)" + else + echo "Extension directory not found, skipping build" + fi + + - name: Run tests + run: | + pytest tests/ -v + env: + CI: true + diff --git a/.gitignore b/.gitignore index e95a650..9132b01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,9 @@ # Python -# Ignore __pycache__ directories at any level __pycache__/ -**/__pycache__/ -# Ignore Python cache files *.py[cod] -*.pyc -*.pyo -*.pyd *$py.class *.so .Python - -# Distribution / packaging build/ develop-eggs/ dist/ @@ -24,51 +16,39 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ -share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST -# PyInstaller -*.manifest -*.spec - -# Unit test / coverage reports -.pytest_cache/ -.coverage -.coverage.* -.cache -htmlcov/ -.tox/ -.nox/ -coverage/ -*.cover -*.py,cover -.hypothesis/ - # Virtual environments -.env -.venv -env/ venv/ +env/ ENV/ -env.bak/ -venv.bak/ +.venv -# IDEs +# IDE .vscode/ .idea/ *.swp *.swo -*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# Jupyter +.ipynb_checkpoints + +# Environment +.env +.env.local # OS .DS_Store Thumbs.db -# Project specific -snapshot_*.json -*.log - +# Playwright +.playwright/ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ab56f16 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include README.md +include LICENSE +recursive-include spec * +recursive-include sentience *.py + diff --git a/pyproject.toml b/pyproject.toml index 9f12580..5676790 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,26 @@ version = "0.1.0" description = "Python SDK for Sentience AI Agent Browser Automation" readme = "README.md" requires-python = ">=3.8" +license = {text = "MIT"} +authors = [ + {name = "Sentience Team"} +] +keywords = ["browser-automation", "playwright", "ai-agent", "web-automation", "sentience"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +project-urls = { + "Homepage" = "https://github.com/SentienceAPI/sentience-python", + "Repository" = "https://github.com/SentienceAPI/sentience-python", + "Issues" = "https://github.com/SentienceAPI/sentience-python/issues", +} dependencies = [ "playwright>=1.40.0", "pydantic>=2.0.0", From 2cd9b544d1dd0b60e0c4857f4fe194e957dbd268 Mon Sep 17 00:00:00 2001 From: rcholic Date: Sun, 21 Dec 2025 14:35:20 -0800 Subject: [PATCH 3/4] fix actions --- .github/workflows/test.yml | 2 +- pyproject.toml | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 657bf1e..7f8f626 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.11'] steps: - name: Checkout code diff --git a/pyproject.toml b/pyproject.toml index 5676790..0e5965a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "sentience-python" version = "0.1.0" description = "Python SDK for Sentience AI Agent Browser Automation" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.11" license = {text = "MIT"} authors = [ {name = "Sentience Team"} @@ -18,16 +18,15 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] -project-urls = { - "Homepage" = "https://github.com/SentienceAPI/sentience-python", - "Repository" = "https://github.com/SentienceAPI/sentience-python", - "Issues" = "https://github.com/SentienceAPI/sentience-python/issues", -} + +[project.urls] +Homepage = "https://github.com/SentienceAPI/sentience-python" +Repository = "https://github.com/SentienceAPI/sentience-python" +Issues = "https://github.com/SentienceAPI/sentience-python/issues" + +[project] dependencies = [ "playwright>=1.40.0", "pydantic>=2.0.0", From d4596ea7523c48f86fa3937ea36a6dc0a12e0b42 Mon Sep 17 00:00:00 2001 From: rcholic Date: Sun, 21 Dec 2025 14:35:30 -0800 Subject: [PATCH 4/4] fix actions --- pyproject.toml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0e5965a..e7bdcdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,6 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", ] - -[project.urls] -Homepage = "https://github.com/SentienceAPI/sentience-python" -Repository = "https://github.com/SentienceAPI/sentience-python" -Issues = "https://github.com/SentienceAPI/sentience-python/issues" - -[project] dependencies = [ "playwright>=1.40.0", "pydantic>=2.0.0", @@ -35,6 +28,11 @@ dependencies = [ "playwright-stealth>=1.0.6", # Bot evasion and stealth mode ] +[project.urls] +Homepage = "https://github.com/SentienceAPI/sentience-python" +Repository = "https://github.com/SentienceAPI/sentience-python" +Issues = "https://github.com/SentienceAPI/sentience-python/issues" + [project.scripts] sentience = "sentience.cli:main" @@ -47,4 +45,3 @@ dev = [ [tool.setuptools.packages.find] where = ["."] include = ["sentience*"] -