-
-
Notifications
You must be signed in to change notification settings - Fork 19
🚀 release: v1.1.0 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,516
−75
Merged
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
0d15382
📦 new: add update checker functionality and context injection
warengonzaga ec863b1
📦 new: implement update checker with caching and runtime detection
warengonzaga 054b7c8
🧪 test: add comprehensive tests for update checker module
warengonzaga 59c79d9
📦 new: add updateContext to AgentContext for software updates
warengonzaga c0a9883
📦 new: add software update check and context to start command
warengonzaga 9398d21
📦 new: update package versions to 1.0.1 across all packages
warengonzaga 4e31c8a
⚙️ setup: update CI workflows for release process and permissions
warengonzaga 1cd2fca
🔧 update: enhance version parsing in isNewerVersion function
warengonzaga 3f91a71
📦 new: add build step for all packages in CI workflow
warengonzaga 080224e
⚙️ setup (ci): update package-build-flow-action to v2.0.1
warengonzaga 6e46e61
⚙️ setup: enforce clean commit convention with husky and ci
warengonzaga 57d3d0f
⚙️ setup (husky): add clean commit validation hook
warengonzaga d71d865
⚙️ setup (ci): fix security, guards, and validation issues
warengonzaga 0407858
⚙️ setup (ci): remove redundant permissions from reusable workflow calls
warengonzaga a240bb6
🔧 update (ci): handle initial push and improve update-checker test
warengonzaga 283a069
📦 new (landing): add landing page with svelte and tailwindcss
warengonzaga cbe57e2
⚙️ setup (ci): add deploy workflow for landing page
warengonzaga 01135d6
⚙️ setup (landing): add build:landing script and update lockfile
warengonzaga af268be
⚙️ setup (husky): add error handling and allow revert commits
warengonzaga cf70b85
🔒 security (update-checker): sanitize version and url for prompt inje…
warengonzaga 7094636
🧪 test (update-checker): add sanitizeForPrompt tests and improve mocking
warengonzaga 858ff22
⚙️ setup (ci): pin action shas and scope deploy permissions
warengonzaga a5c1305
🔒 security (core): harden update checker cache and fetch logic
warengonzaga ea45a35
📦 new (landing): extract GitHubIcon component and improve accessibility
warengonzaga c142513
🔧 update (landing): improve scrollbar styling and Firefox support
warengonzaga 302f44b
🔧 update (landing): clarify QuickStart step 3 description
warengonzaga 99c228a
⚙️ setup (ci): streamline CI workflows by removing unused jobs
warengonzaga bab10e0
🗑️ remove: delete pre-commit script for bun test
warengonzaga 3004a38
🔧 update (ci): add build step for workspace packages
warengonzaga 67f0a76
🔧 update (dockerfile): upgrade bun version for builder and production…
warengonzaga 8dc6789
🚀 release: bump version to 1.1.0 for all packages
warengonzaga 4422bf3
🔧 update (dockerfile): remove frozen-lockfile option from bun install
warengonzaga 958e520
📦 new: add dev:landing script for development of landing page
warengonzaga 61699d8
📖 docs: update README with model names and licensing information
warengonzaga 6e3e0bd
⚙️ setup: update release action to v1.2.1 and change token secret
warengonzaga 8bb8382
📖 docs: update commit message guidelines for breaking changes
warengonzaga 7c8f470
🔧 update: enhance commit message validation for breaking changes
warengonzaga File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| /** | ||
| * Update Checker | ||
| * | ||
| * Lightweight, non-blocking module that checks the npm registry for newer | ||
| * versions of tinyclaw. Results are cached locally (24-hour TTL) to avoid | ||
| * repeated network calls. | ||
| * | ||
| * The update info is injected into the agent's system prompt context so the | ||
| * AI can conversationally inform the user about available upgrades. | ||
| * | ||
| * Runtime detection differentiates npm installs (self-upgradable via shell | ||
| * tool) from Docker containers (manual pull required). | ||
| */ | ||
|
|
||
| import { join } from 'node:path'; | ||
| import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; | ||
| import { logger } from '@tinyclaw/logger'; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Types | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| export type UpdateRuntime = 'npm' | 'docker' | 'source'; | ||
|
|
||
| export interface UpdateInfo { | ||
| /** Currently running version (e.g. "1.0.0"). */ | ||
| current: string; | ||
| /** Latest version published on npm (e.g. "1.1.0"). */ | ||
| latest: string; | ||
| /** Whether a newer version is available. */ | ||
| updateAvailable: boolean; | ||
| /** Detected runtime environment. */ | ||
| runtime: UpdateRuntime; | ||
| /** Timestamp (ms) of the last check. */ | ||
| checkedAt: number; | ||
| /** GitHub release URL for the latest version. */ | ||
| releaseUrl: string; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Constants | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** Time-to-live for the cache file (24 hours). */ | ||
| const CACHE_TTL_MS = 24 * 60 * 60 * 1000; | ||
|
|
||
| /** npm registry endpoint for the tinyclaw package. */ | ||
| const NPM_REGISTRY_URL = 'https://registry.npmjs.org/tinyclaw/latest'; | ||
|
|
||
| /** Maximum time to wait for the registry response (ms). */ | ||
| const FETCH_TIMEOUT_MS = 5_000; | ||
|
|
||
| /** Cache file name within the data directory. */ | ||
| const CACHE_FILENAME = 'update-check.json'; | ||
|
|
||
| /** GitHub releases base URL. */ | ||
| const GITHUB_RELEASES_URL = 'https://github.com/warengonzaga/tinyclaw/releases/tag'; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Runtime detection | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Detect the runtime environment. | ||
| * | ||
| * - Docker: `/.dockerenv` exists or `TINYCLAW_RUNTIME` env is set to "docker" | ||
| * - Source: `TINYCLAW_RUNTIME` env is set to "source" | ||
| * - npm: everything else (global install via npm/bun/pnpm) | ||
| */ | ||
| export function detectRuntime(): UpdateRuntime { | ||
| const envRuntime = process.env.TINYCLAW_RUNTIME?.toLowerCase(); | ||
| if (envRuntime === 'docker') return 'docker'; | ||
| if (envRuntime === 'source') return 'source'; | ||
|
|
||
| // Docker container detection | ||
| try { | ||
| if (existsSync('/.dockerenv')) return 'docker'; | ||
| } catch { | ||
| // Permission errors on exotic platforms — assume npm | ||
| } | ||
|
|
||
| return 'npm'; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Semver comparison (minimal — avoids pulling a full semver library) | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Compare two semver strings. Returns true when `latest` is strictly newer | ||
| * than `current`. Only handles `MAJOR.MINOR.PATCH`; pre-release suffixes | ||
| * are ignored. | ||
| */ | ||
| export function isNewerVersion(current: string, latest: string): boolean { | ||
| const parse = (v: string): number[] => | ||
| v.replace(/^v/, '').split('.').map(Number).slice(0, 3); | ||
| const [cMaj = 0, cMin = 0, cPat = 0] = parse(current); | ||
| const [lMaj = 0, lMin = 0, lPat = 0] = parse(latest); | ||
| if (lMaj !== cMaj) return lMaj > cMaj; | ||
| if (lMin !== cMin) return lMin > cMin; | ||
| return lPat > cPat; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Cache I/O | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| function getCachePath(dataDir: string): string { | ||
| return join(dataDir, 'data', CACHE_FILENAME); | ||
| } | ||
|
|
||
| function readCache(dataDir: string): UpdateInfo | null { | ||
| try { | ||
| const raw = readFileSync(getCachePath(dataDir), 'utf-8'); | ||
| const cached = JSON.parse(raw) as UpdateInfo; | ||
| if (cached && typeof cached.checkedAt === 'number') return cached; | ||
| } catch { | ||
| // Missing or corrupt — will re-check | ||
| } | ||
| return null; | ||
| } | ||
warengonzaga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function writeCache(dataDir: string, info: UpdateInfo): void { | ||
| try { | ||
| const dir = join(dataDir, 'data'); | ||
| if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); | ||
| writeFileSync(getCachePath(dataDir), JSON.stringify(info, null, 2), 'utf-8'); | ||
| } catch (err) { | ||
| logger.debug('Failed to write update cache', err); | ||
| } | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Registry fetch | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| async function fetchLatestVersion(): Promise<string | null> { | ||
| try { | ||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); | ||
|
|
||
| const res = await fetch(NPM_REGISTRY_URL, { | ||
| signal: controller.signal, | ||
| headers: { Accept: 'application/json' }, | ||
| }); | ||
| clearTimeout(timeout); | ||
|
|
||
| if (!res.ok) return null; | ||
| const data = (await res.json()) as { version?: string }; | ||
| return data.version ?? null; | ||
| } catch { | ||
| // Network error, timeout, or offline — silently return null | ||
| return null; | ||
| } | ||
| } | ||
warengonzaga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Public API | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Check for available updates. | ||
| * | ||
| * - Returns cached result if the cache is still fresh (< 24 hours old). | ||
| * - Otherwise fetches the npm registry in the background. | ||
| * - Never throws — returns null on any failure so startup is never delayed. | ||
| * | ||
| * @param currentVersion - The currently running version string. | ||
| * @param dataDir - The tinyclaw data directory (e.g. `~/.tinyclaw`). | ||
| */ | ||
| export async function checkForUpdate( | ||
| currentVersion: string, | ||
| dataDir: string, | ||
| ): Promise<UpdateInfo | null> { | ||
| try { | ||
| // Return cached result if still fresh | ||
| const cached = readCache(dataDir); | ||
| if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) { | ||
| // Re-evaluate against the current binary version (in case user | ||
| // upgraded manually since the last check) | ||
| return { | ||
| ...cached, | ||
| current: currentVersion, | ||
| updateAvailable: isNewerVersion(currentVersion, cached.latest), | ||
| }; | ||
| } | ||
|
|
||
| // Fetch latest version from npm | ||
| const latest = await fetchLatestVersion(); | ||
| if (!latest) return cached ?? null; // Network failure — use stale cache if available | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const runtime = detectRuntime(); | ||
| const info: UpdateInfo = { | ||
| current: currentVersion, | ||
| latest, | ||
| updateAvailable: isNewerVersion(currentVersion, latest), | ||
| runtime, | ||
| checkedAt: Date.now(), | ||
| releaseUrl: `${GITHUB_RELEASES_URL}/v${latest}`, | ||
| }; | ||
|
|
||
| writeCache(dataDir, info); | ||
|
|
||
| if (info.updateAvailable) { | ||
| logger.info('Update available', { current: currentVersion, latest, runtime }, { emoji: '🆕' }); | ||
| } | ||
|
|
||
| return info; | ||
| } catch (err) { | ||
| logger.debug('Update check failed', err); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // System prompt context builder | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Build a system prompt section that informs the agent about an available | ||
| * update. Returns an empty string if no update is available or info is null. | ||
| */ | ||
| export function buildUpdateContext(info: UpdateInfo | null): string { | ||
| if (!info?.updateAvailable) return ''; | ||
|
|
||
| const upgradeInstructions = | ||
| info.runtime === 'npm' | ||
| ? `Since you are running as an npm global install, you can upgrade yourself using the shell tool: | ||
| \`bun install -g tinyclaw@latest\` or \`npm install -g tinyclaw@latest\` | ||
| After the upgrade completes, request a restart using the tinyclaw_restart tool so the supervisor can reload with the new version.` | ||
| : info.runtime === 'docker' | ||
| ? `Since you are running inside a Docker container, you cannot upgrade yourself directly. | ||
| Instruct the owner to run the following commands externally: | ||
| \`docker pull ghcr.io/warengonzaga/tinyclaw:latest\` | ||
| Then restart the container (e.g. \`docker restart tinyclaw\` or \`docker compose up -d\`).` | ||
| : `Since you are running from source, instruct the owner to pull the latest changes and rebuild: | ||
| \`git pull && bun install && bun run build:packages\` | ||
| Then restart using the tinyclaw_restart tool.`; | ||
|
|
||
| return ` | ||
|
|
||
| ## Software Update Available | ||
| - **Current version:** ${info.current} | ||
| - **Latest version:** ${info.latest} | ||
| - **Runtime:** ${info.runtime} | ||
| - **Release notes:** ${info.releaseUrl} | ||
|
|
||
| ${upgradeInstructions} | ||
|
|
||
| **Behavior guidelines:** | ||
| - Mention the update naturally early in the conversation, but do not interrupt an ongoing task. | ||
| - Do not repeat the update reminder if the owner has already acknowledged or dismissed it. | ||
| - If the owner agrees to update, proceed with the appropriate upgrade path above. | ||
| - After a successful upgrade and restart, confirm the new version is running.`; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.