diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3b6f03a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install + - run: bun tsc --noEmit + - run: bun test diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..005a522 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,14 @@ +name: Release Please + +on: + push: + branches: [main] + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..466df71 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/SKILL.md b/SKILL.md index e03359a..6b124fc 100644 --- a/SKILL.md +++ b/SKILL.md @@ -347,10 +347,16 @@ mcp__aibtc__stacks_sign_message(message: "Bitcoin will be the currency of AIs") ``` Register: + +**Important:** `stacks_sign_message` returns the signature with a `0x` prefix. The `/api/register` endpoint rejects it — strip the prefix before sending: `stx_sig="${stx_sig#0x}"` + ```bash +# Strip 0x prefix from Stacks signature (AIBTC endpoint rejects it) +stx_sig="${stx_sig#0x}" + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://aibtc.com/api/register \ -H "Content-Type: application/json" \ - -d '{"bitcoinSignature":"","stacksSignature":""}') + -d "{\"bitcoinSignature\":\"$btc_sig\",\"stacksSignature\":\"$stx_sig\",\"btcAddress\":\"$btc_address\"}") HTTP_CODE=$(echo "$RESPONSE" | tail -1) BODY=$(echo "$RESPONSE" | head -1) if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then diff --git a/daemon/loop.test.ts b/daemon/loop.test.ts new file mode 100644 index 0000000..10295ba --- /dev/null +++ b/daemon/loop.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from "bun:test"; +import { existsSync, readFileSync } from "fs"; +import { join } from "path"; + +const ROOT = join(import.meta.dir, ".."); + +describe("loop-starter-kit scaffold", () => { + it("has SKILL.md", () => { + expect(existsSync(join(ROOT, "SKILL.md"))).toBe(true); + }); + + it("has CLAUDE.md template", () => { + expect(existsSync(join(ROOT, "CLAUDE.md"))).toBe(true); + }); + + it("has SOUL.md template", () => { + expect(existsSync(join(ROOT, "SOUL.md"))).toBe(true); + }); + + it("has daemon directory with required files", () => { + const required = ["loop.md", "STATE.md", "health.json", "queue.json", "processed.json", "outbox.json"]; + for (const file of required) { + expect(existsSync(join(ROOT, "daemon", file))).toBe(true); + } + }); + + it("has memory directory with required files", () => { + const required = ["journal.md", "contacts.md", "learnings.md"]; + for (const file of required) { + expect(existsSync(join(ROOT, "memory", file))).toBe(true); + } + }); + + it("health.json is valid JSON with expected fields", () => { + const health = JSON.parse(readFileSync(join(ROOT, "daemon", "health.json"), "utf-8")); + expect(health).toHaveProperty("cycle"); + expect(health).toHaveProperty("status"); + expect(health).toHaveProperty("phases"); + }); + + it("SKILL.md documents 0x prefix stripping for registration", () => { + const skill = readFileSync(join(ROOT, "SKILL.md"), "utf-8"); + expect(skill).toContain("0x prefix"); + expect(skill).toContain("stx_sig=\"${stx_sig#0x}\""); + }); + + it("has wrangler.jsonc with worker-logs service binding", () => { + const wrangler = readFileSync(join(ROOT, "wrangler.jsonc"), "utf-8"); + expect(wrangler).toContain("worker-logs"); + expect(wrangler).toContain("WORKER_LOGS"); + }); + + it("wrangler.jsonc has staging and production environments", () => { + const wrangler = readFileSync(join(ROOT, "wrangler.jsonc"), "utf-8"); + expect(wrangler).toContain("staging"); + expect(wrangler).toContain("production"); + }); + + it("has CI workflow", () => { + expect(existsSync(join(ROOT, ".github", "workflows", "ci.yml"))).toBe(true); + }); + + it("has release-please config", () => { + expect(existsSync(join(ROOT, "release-please-config.json"))).toBe(true); + expect(existsSync(join(ROOT, ".release-please-manifest.json"))).toBe(true); + }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..55c7296 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "loop-starter-kit", + "version": "0.1.0", + "private": true, + "description": "Autonomous agent loop starter kit for AIBTC", + "scripts": { + "typecheck": "bun tsc --noEmit", + "test": "bun test", + "check": "bun tsc --noEmit && bun test" + }, + "devDependencies": { + "typescript": "^5.7.0", + "@types/bun": "latest" + } +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..d7e8d46 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,7 @@ +{ + "packages": { + ".": { + "release-type": "node" + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ca554ba --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": ".", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["daemon/**/*.ts", "scripts/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..8b904a7 --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,31 @@ +{ + // Loop Starter Kit — Cloudflare Workers configuration + // See: https://developers.cloudflare.com/workers/wrangler/configuration/ + "name": "loop-starter-kit", + "main": "daemon/index.ts", + "compatibility_date": "2025-12-01", + + // Service bindings — connect to other workers in the AIBTC ecosystem + "services": [ + { + // Central logging at logs.aibtc.com (issue #13) + "binding": "WORKER_LOGS", + "service": "worker-logs" + } + ], + + // Environment: staging (issue #14) + "env": { + "staging": { + "name": "loop-starter-kit-staging", + "vars": { "ENVIRONMENT": "staging" } + }, + "production": { + "name": "loop-starter-kit", + "vars": { "ENVIRONMENT": "production" }, + "routes": [ + { "pattern": "loop.drx4.xyz", "custom_domain": true } + ] + } + } +}