feat: refactor sgclaw around zeroclaw compat runtime
This commit is contained in:
32
third_party/zeroclaw/.github/CODEOWNERS
vendored
Normal file
32
third_party/zeroclaw/.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Default owner for all files
|
||||
* @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
|
||||
# Important functional modules
|
||||
/src/agent/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/providers/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/channels/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/tools/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/gateway/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/runtime/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/src/memory/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/Cargo.toml @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/Cargo.lock @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
|
||||
# Security / tests / CI-CD ownership
|
||||
/src/security/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/tests/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/.github/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/.github/workflows/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/.github/codeql/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/.github/dependabot.yml @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/SECURITY.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/docs/actions-source-policy.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/docs/ci-map.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
|
||||
# Docs & governance
|
||||
/docs/** @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/AGENTS.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/CLAUDE.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/CONTRIBUTING.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/docs/pr-workflow.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
/docs/reviewer-playbook.md @theonlyhennygod @JordanTheJet @SimianAstronaut7
|
||||
138
third_party/zeroclaw/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
138
third_party/zeroclaw/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: Bug Report
|
||||
description: Report a reproducible defect in ZeroClaw
|
||||
title: "[Bug]: "
|
||||
labels:
|
||||
- bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a bug.
|
||||
Please provide a minimal reproducible case so maintainers can triage quickly.
|
||||
Do not include personal/sensitive data; redact and anonymize all logs/payloads.
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Affected component
|
||||
options:
|
||||
- runtime/daemon
|
||||
- provider
|
||||
- channel
|
||||
- memory
|
||||
- security/sandbox
|
||||
- tooling/ci
|
||||
- docs
|
||||
- unknown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
options:
|
||||
- S0 - data loss / security risk
|
||||
- S1 - workflow blocked
|
||||
- S2 - degraded behavior
|
||||
- S3 - minor issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current behavior
|
||||
description: What is happening now?
|
||||
placeholder: The process exits with ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What should happen instead?
|
||||
placeholder: The daemon should stay alive and ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Please provide exact commands/config.
|
||||
placeholder: |
|
||||
1. zeroclaw onboard
|
||||
2. zeroclaw daemon
|
||||
3. Observe crash in logs
|
||||
render: bash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: impact
|
||||
attributes:
|
||||
label: Impact
|
||||
description: Who is affected, how often, and practical consequences (optional but helps triage).
|
||||
placeholder: |
|
||||
Affected users: ...
|
||||
Frequency: always/intermittent
|
||||
Consequence: ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs / stack traces
|
||||
description: Paste relevant logs (redact secrets, personal identifiers, and sensitive data).
|
||||
render: text
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: ZeroClaw version
|
||||
placeholder: v0.1.0 / commit SHA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: rust
|
||||
attributes:
|
||||
label: Rust version
|
||||
description: Required for runtime/build bugs; optional for docs/config issues.
|
||||
placeholder: rustc 1.xx.x
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
placeholder: Ubuntu 24.04 / macOS 15 / Windows 11
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: regression
|
||||
attributes:
|
||||
label: Regression?
|
||||
options:
|
||||
- Unknown
|
||||
- Yes, it worked before
|
||||
- No, first-time setup
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Pre-flight checks
|
||||
options:
|
||||
- label: I reproduced this on the latest master branch or latest release.
|
||||
required: true
|
||||
- label: I redacted secrets, tokens, and personal data from all submitted content.
|
||||
required: true
|
||||
11
third_party/zeroclaw/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
third_party/zeroclaw/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security vulnerability report
|
||||
url: https://github.com/zeroclaw-labs/zeroclaw/security/policy
|
||||
about: Please report security vulnerabilities privately via SECURITY.md policy.
|
||||
- name: Contribution guide
|
||||
url: https://github.com/zeroclaw-labs/zeroclaw/blob/master/CONTRIBUTING.md
|
||||
about: Please read contribution and PR requirements before opening an issue.
|
||||
- name: PR workflow & reviewer expectations
|
||||
url: https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/pr-workflow.md
|
||||
about: Read risk-based PR tracks, CI gates, and merge criteria before filing feature requests.
|
||||
107
third_party/zeroclaw/.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
107
third_party/zeroclaw/.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Feature Request
|
||||
description: Propose an improvement or new capability
|
||||
title: "[Feature]: "
|
||||
labels:
|
||||
- enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for sharing your idea.
|
||||
Please focus on user value, constraints, and rollout safety.
|
||||
Do not include personal/sensitive data; use neutral project-scoped placeholders.
|
||||
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One-line statement of the requested capability.
|
||||
placeholder: Add a provider-level retry budget override for long-running channels.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem statement
|
||||
description: What user pain does this solve and why is current behavior insufficient?
|
||||
placeholder: Teams operating in unstable networks cannot tune retries per provider...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe preferred behavior and interfaces.
|
||||
placeholder: Add `[provider.retry]` config and enforce bounds in config validation.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: non_goals
|
||||
attributes:
|
||||
label: Non-goals / out of scope
|
||||
description: Clarify what should not be included in the first iteration (optional but helps scope discussion).
|
||||
placeholder: No UI changes, no cross-provider dynamic adaptation in v1.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: What alternatives did you evaluate?
|
||||
placeholder: Keep current behavior, use wrapper scripts, etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: acceptance
|
||||
attributes:
|
||||
label: Acceptance criteria
|
||||
description: What outcomes would make this request complete? (optional — can be defined during triage)
|
||||
placeholder: |
|
||||
- Config key is documented and validated
|
||||
- Runtime path uses configured retry budget
|
||||
- Regression tests cover fallback and invalid config
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: architecture
|
||||
attributes:
|
||||
label: Architecture impact
|
||||
description: Which subsystem(s) are affected? (optional — maintainers will assess during triage)
|
||||
placeholder: providers/, channels/, memory/, runtime/, security/, docs/ ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: risk
|
||||
attributes:
|
||||
label: Risk and rollback
|
||||
description: Main risk + how to disable/revert quickly (optional — can be defined during planning).
|
||||
placeholder: Risk is ... rollback is ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: breaking
|
||||
attributes:
|
||||
label: Breaking change?
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: hygiene
|
||||
attributes:
|
||||
label: Data hygiene checks
|
||||
options:
|
||||
- label: I removed personal/sensitive data from examples, payloads, and logs.
|
||||
required: true
|
||||
- label: I used neutral, project-focused wording and placeholders.
|
||||
required: true
|
||||
3
third_party/zeroclaw/.github/actionlint.yaml
vendored
Normal file
3
third_party/zeroclaw/.github/actionlint.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- blacksmith-2vcpu-ubuntu-2404
|
||||
BIN
third_party/zeroclaw/.github/assets/show-tool-calls-after.png
vendored
Normal file
BIN
third_party/zeroclaw/.github/assets/show-tool-calls-after.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
third_party/zeroclaw/.github/assets/show-tool-calls-before.png
vendored
Normal file
BIN
third_party/zeroclaw/.github/assets/show-tool-calls-before.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
8
third_party/zeroclaw/.github/codeql/codeql-config.yml
vendored
Normal file
8
third_party/zeroclaw/.github/codeql/codeql-config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# CodeQL configuration for ZeroClaw
|
||||
#
|
||||
# We intentionally ignore integration tests under `tests/` because they often
|
||||
# contain security-focused fixtures (example secrets, malformed payloads, etc.)
|
||||
# that can trigger false positives in security queries.
|
||||
|
||||
paths-ignore:
|
||||
- tests/**
|
||||
52
third_party/zeroclaw/.github/dependabot.yml
vendored
Normal file
52
third_party/zeroclaw/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: master
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- "dependencies"
|
||||
groups:
|
||||
rust-all:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: master
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "ci"
|
||||
- "dependencies"
|
||||
groups:
|
||||
actions-all:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: master
|
||||
open-pull-requests-limit: 1
|
||||
labels:
|
||||
- "ci"
|
||||
- "dependencies"
|
||||
groups:
|
||||
docker-all:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
21
third_party/zeroclaw/.github/label-policy.json
vendored
Normal file
21
third_party/zeroclaw/.github/label-policy.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"contributor_tier_color": "2ED9FF",
|
||||
"contributor_tiers": [
|
||||
{
|
||||
"label": "distinguished contributor",
|
||||
"min_merged_prs": 50
|
||||
},
|
||||
{
|
||||
"label": "principal contributor",
|
||||
"min_merged_prs": 20
|
||||
},
|
||||
{
|
||||
"label": "experienced contributor",
|
||||
"min_merged_prs": 10
|
||||
},
|
||||
{
|
||||
"label": "trusted contributor",
|
||||
"min_merged_prs": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
448
third_party/zeroclaw/.github/labeler.yml
vendored
Normal file
448
third_party/zeroclaw/.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,448 @@
|
||||
"docs":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- "**/*.mdx"
|
||||
- "LICENSE"
|
||||
- ".markdownlint-cli2.yaml"
|
||||
|
||||
"dependencies":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "deny.toml"
|
||||
- ".github/dependabot.yml"
|
||||
|
||||
"ci":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".github/**"
|
||||
- ".githooks/**"
|
||||
|
||||
"core":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/*.rs"
|
||||
|
||||
"agent":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/agent/**"
|
||||
|
||||
"channel":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/**"
|
||||
|
||||
"channel:bluesky":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/bluesky.rs"
|
||||
|
||||
"channel:clawdtalk":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/clawdtalk.rs"
|
||||
|
||||
"channel:cli":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/cli.rs"
|
||||
|
||||
"channel:dingtalk":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/dingtalk.rs"
|
||||
|
||||
"channel:discord":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/discord.rs"
|
||||
- "src/channels/discord_history.rs"
|
||||
|
||||
"channel:email":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/email_channel.rs"
|
||||
- "src/channels/gmail_push.rs"
|
||||
|
||||
"channel:imessage":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/imessage.rs"
|
||||
|
||||
"channel:irc":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/irc.rs"
|
||||
|
||||
"channel:lark":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/lark.rs"
|
||||
|
||||
"channel:linq":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/linq.rs"
|
||||
|
||||
"channel:matrix":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/matrix.rs"
|
||||
|
||||
"channel:mattermost":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/mattermost.rs"
|
||||
|
||||
"channel:mochat":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/mochat.rs"
|
||||
|
||||
"channel:mqtt":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/mqtt.rs"
|
||||
|
||||
"channel:nextcloud-talk":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/nextcloud_talk.rs"
|
||||
|
||||
"channel:nostr":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/nostr.rs"
|
||||
|
||||
"channel:notion":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/notion.rs"
|
||||
|
||||
"channel:qq":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/qq.rs"
|
||||
|
||||
"channel:reddit":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/reddit.rs"
|
||||
|
||||
"channel:signal":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/signal.rs"
|
||||
|
||||
"channel:slack":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/slack.rs"
|
||||
|
||||
"channel:telegram":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/telegram.rs"
|
||||
|
||||
"channel:twitter":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/twitter.rs"
|
||||
|
||||
"channel:wati":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/wati.rs"
|
||||
|
||||
"channel:webhook":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/webhook.rs"
|
||||
|
||||
"channel:wecom":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/wecom.rs"
|
||||
|
||||
"channel:whatsapp":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/whatsapp.rs"
|
||||
- "src/channels/whatsapp_storage.rs"
|
||||
- "src/channels/whatsapp_web.rs"
|
||||
|
||||
"gateway":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/gateway/**"
|
||||
|
||||
"config":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/config/**"
|
||||
|
||||
"cron":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/cron/**"
|
||||
|
||||
"daemon":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/daemon/**"
|
||||
|
||||
"doctor":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/doctor/**"
|
||||
|
||||
"health":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/health/**"
|
||||
|
||||
"heartbeat":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/heartbeat/**"
|
||||
|
||||
"integration":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/integrations/**"
|
||||
|
||||
"memory":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/memory/**"
|
||||
|
||||
"security":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/security/**"
|
||||
|
||||
"runtime":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/runtime/**"
|
||||
|
||||
"onboard":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/onboard/**"
|
||||
|
||||
"provider":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/**"
|
||||
|
||||
"provider:anthropic":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/anthropic.rs"
|
||||
|
||||
"provider:azure-openai":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/azure_openai.rs"
|
||||
|
||||
"provider:bedrock":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/bedrock.rs"
|
||||
|
||||
"provider:claude-code":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/claude_code.rs"
|
||||
|
||||
"provider:compatible":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/compatible.rs"
|
||||
|
||||
"provider:copilot":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/copilot.rs"
|
||||
|
||||
"provider:gemini":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/gemini.rs"
|
||||
- "src/providers/gemini_cli.rs"
|
||||
|
||||
"provider:glm":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/glm.rs"
|
||||
|
||||
"provider:kilocli":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/kilocli.rs"
|
||||
|
||||
"provider:ollama":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/ollama.rs"
|
||||
|
||||
"provider:openai":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/openai.rs"
|
||||
- "src/providers/openai_codex.rs"
|
||||
|
||||
"provider:openrouter":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/openrouter.rs"
|
||||
|
||||
"provider:telnyx":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/telnyx.rs"
|
||||
|
||||
"service":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/service/**"
|
||||
|
||||
"skillforge":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/skillforge/**"
|
||||
|
||||
"skills":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/skills/**"
|
||||
|
||||
"tool":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/**"
|
||||
|
||||
"tool:browser":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/browser.rs"
|
||||
- "src/tools/browser_delegate.rs"
|
||||
- "src/tools/browser_open.rs"
|
||||
- "src/tools/text_browser.rs"
|
||||
- "src/tools/screenshot.rs"
|
||||
|
||||
"tool:composio":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/composio.rs"
|
||||
|
||||
"tool:cron":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/cron_add.rs"
|
||||
- "src/tools/cron_list.rs"
|
||||
- "src/tools/cron_remove.rs"
|
||||
- "src/tools/cron_run.rs"
|
||||
- "src/tools/cron_runs.rs"
|
||||
- "src/tools/cron_update.rs"
|
||||
|
||||
"tool:file":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/file_edit.rs"
|
||||
- "src/tools/file_read.rs"
|
||||
- "src/tools/file_write.rs"
|
||||
- "src/tools/glob_search.rs"
|
||||
- "src/tools/content_search.rs"
|
||||
|
||||
"tool:google-workspace":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/google_workspace.rs"
|
||||
|
||||
"tool:mcp":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/mcp_client.rs"
|
||||
- "src/tools/mcp_deferred.rs"
|
||||
- "src/tools/mcp_protocol.rs"
|
||||
- "src/tools/mcp_tool.rs"
|
||||
- "src/tools/mcp_transport.rs"
|
||||
|
||||
"tool:memory":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/memory_forget.rs"
|
||||
- "src/tools/memory_recall.rs"
|
||||
- "src/tools/memory_store.rs"
|
||||
|
||||
"tool:microsoft365":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/microsoft365/**"
|
||||
|
||||
"tool:shell":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/shell.rs"
|
||||
- "src/tools/node_tool.rs"
|
||||
- "src/tools/cli_discovery.rs"
|
||||
|
||||
"tool:sop":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/sop_advance.rs"
|
||||
- "src/tools/sop_approve.rs"
|
||||
- "src/tools/sop_execute.rs"
|
||||
- "src/tools/sop_list.rs"
|
||||
- "src/tools/sop_status.rs"
|
||||
|
||||
"tool:web":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/web_fetch.rs"
|
||||
- "src/tools/web_search_tool.rs"
|
||||
- "src/tools/web_search_provider_routing.rs"
|
||||
- "src/tools/http_request.rs"
|
||||
|
||||
"tool:security":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/security_ops.rs"
|
||||
- "src/tools/verifiable_intent.rs"
|
||||
|
||||
"tool:cloud":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/cloud_ops.rs"
|
||||
- "src/tools/cloud_patterns.rs"
|
||||
|
||||
"tunnel":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tunnel/**"
|
||||
|
||||
"observability":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/observability/**"
|
||||
|
||||
"tests":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "tests/**"
|
||||
|
||||
"scripts":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "scripts/**"
|
||||
|
||||
"dev":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "dev/**"
|
||||
114
third_party/zeroclaw/.github/pull_request_template.md
vendored
Normal file
114
third_party/zeroclaw/.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
## Summary
|
||||
|
||||
Describe this PR in 2-5 bullets:
|
||||
|
||||
- Base branch target (`master` for all contributions):
|
||||
- Problem:
|
||||
- Why it matters:
|
||||
- What changed:
|
||||
- What did **not** change (scope boundary):
|
||||
|
||||
## Label Snapshot (required)
|
||||
|
||||
- Risk label (`risk: low|medium|high`):
|
||||
- Size label (`size: XS|S|M|L|XL`, auto-managed/read-only):
|
||||
- Scope labels (`core|agent|channel|config|cron|daemon|doctor|gateway|health|heartbeat|integration|memory|observability|onboard|provider|runtime|security|service|skillforge|skills|tool|tunnel|docs|dependencies|ci|tests|scripts|dev`, comma-separated):
|
||||
- Module labels (`<module>: <component>`, for example `channel: telegram`, `provider: kimi`, `tool: shell`):
|
||||
- Contributor tier label (`trusted contributor|experienced contributor|principal contributor|distinguished contributor`, auto-managed/read-only; author merged PRs >=5/10/20/50):
|
||||
- If any auto-label is incorrect, note requested correction:
|
||||
|
||||
## Change Metadata
|
||||
|
||||
- Change type (`bug|feature|refactor|docs|security|chore`):
|
||||
- Primary scope (`runtime|provider|channel|memory|security|ci|docs|multi`):
|
||||
|
||||
## Linked Issue
|
||||
|
||||
- Closes #
|
||||
- Related #
|
||||
- Depends on # (if stacked)
|
||||
- Supersedes # (if replacing older PR)
|
||||
|
||||
## Supersede Attribution (required when `Supersedes #` is used)
|
||||
|
||||
- Superseded PRs + authors (`#<pr> by @<author>`, one per line):
|
||||
- Integrated scope by source PR (what was materially carried forward):
|
||||
- `Co-authored-by` trailers added for materially incorporated contributors? (`Yes/No`)
|
||||
- If `No`, explain why (for example: inspiration-only, no direct code/design carry-over):
|
||||
- Trailer format check (separate lines, no escaped `\n`): (`Pass/Fail`)
|
||||
|
||||
## Validation Evidence (required)
|
||||
|
||||
Commands and result summary:
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
- Evidence provided (test/log/trace/screenshot/perf):
|
||||
- If any command is intentionally skipped, explain why:
|
||||
|
||||
## Security Impact (required)
|
||||
|
||||
- New permissions/capabilities? (`Yes/No`)
|
||||
- New external network calls? (`Yes/No`)
|
||||
- Secrets/tokens handling changed? (`Yes/No`)
|
||||
- File system access scope changed? (`Yes/No`)
|
||||
- If any `Yes`, describe risk and mitigation:
|
||||
|
||||
## Privacy and Data Hygiene (required)
|
||||
|
||||
- Data-hygiene status (`pass|needs-follow-up`):
|
||||
- Redaction/anonymization notes:
|
||||
- Neutral wording confirmation (use ZeroClaw/project-native labels if identity-like wording is needed):
|
||||
|
||||
## Compatibility / Migration
|
||||
|
||||
- Backward compatible? (`Yes/No`)
|
||||
- Config/env changes? (`Yes/No`)
|
||||
- Migration needed? (`Yes/No`)
|
||||
- If yes, exact upgrade steps:
|
||||
|
||||
## i18n Follow-Through (required when docs or user-facing wording changes)
|
||||
|
||||
- i18n follow-through triggered? (`Yes/No`)
|
||||
- If `Yes`, locale navigation parity updated in `README*`, `docs/README*`, and `docs/SUMMARY.md` for supported locales (`en`, `zh-CN`, `ja`, `ru`, `fr`, `vi`)? (`Yes/No`)
|
||||
- If `Yes`, localized runtime-contract docs updated where equivalents exist (minimum for `fr`/`vi`: `commands-reference`, `config-reference`, `troubleshooting`)? (`Yes/No/N.A.`)
|
||||
- If `Yes`, Vietnamese canonical docs under `docs/i18n/vi/**` synced and compatibility shims under `docs/*.vi.md` validated? (`Yes/No/N.A.`)
|
||||
- If any `No`/`N.A.`, link follow-up issue/PR and explain scope decision:
|
||||
|
||||
## Human Verification (required)
|
||||
|
||||
What was personally validated beyond CI:
|
||||
|
||||
- Verified scenarios:
|
||||
- Edge cases checked:
|
||||
- What was not verified:
|
||||
|
||||
## Side Effects / Blast Radius (required)
|
||||
|
||||
- Affected subsystems/workflows:
|
||||
- Potential unintended effects:
|
||||
- Guardrails/monitoring for early detection:
|
||||
|
||||
## Agent Collaboration Notes (recommended)
|
||||
|
||||
- Agent tools used (if any):
|
||||
- Workflow/plan summary (if any):
|
||||
- Verification focus:
|
||||
- Confirmation: naming + architecture boundaries followed (`AGENTS.md` + `CONTRIBUTING.md`):
|
||||
|
||||
## Rollback Plan (required)
|
||||
|
||||
- Fast rollback command/path:
|
||||
- Feature flags or config toggles (if any):
|
||||
- Observable failure symptoms:
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
List real risks in this PR (or write `None`).
|
||||
|
||||
- Risk:
|
||||
- Mitigation:
|
||||
17
third_party/zeroclaw/.github/workflows/README.md
vendored
Normal file
17
third_party/zeroclaw/.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Workflow Directory Layout
|
||||
|
||||
GitHub Actions only loads workflow entry files from:
|
||||
|
||||
- `.github/workflows/*.yml`
|
||||
- `.github/workflows/*.yaml`
|
||||
|
||||
Subdirectories are not valid locations for workflow entry files.
|
||||
|
||||
Repository convention:
|
||||
|
||||
1. Keep runnable workflow entry files at `.github/workflows/` root.
|
||||
2. Keep cross-tooling/local CI scripts under `dev/` or `scripts/ci/` when used outside Actions.
|
||||
|
||||
Workflow behavior documentation in this directory:
|
||||
|
||||
- `.github/workflows/master-branch-flow.md`
|
||||
175
third_party/zeroclaw/.github/workflows/checks-on-pr.yml
vendored
Normal file
175
third_party/zeroclaw/.github/workflows/checks-on-pr.yml
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
name: Quality Gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: checks-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
components: rustfmt, clippy
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Install mold linker
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Install cargo-nextest
|
||||
run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
|
||||
|
||||
- name: Run tests
|
||||
run: cargo nextest run --locked
|
||||
env:
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- name: Install mold linker
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
shell: bash
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Build release
|
||||
shell: bash
|
||||
run: cargo build --profile ci --locked --target ${{ matrix.target }}
|
||||
env:
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"
|
||||
|
||||
security:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit --locked
|
||||
|
||||
- name: Install cargo-deny
|
||||
run: cargo install cargo-deny --locked
|
||||
|
||||
- name: Audit dependencies
|
||||
run: cargo audit
|
||||
|
||||
- name: Check licenses and sources
|
||||
run: cargo deny check licenses sources
|
||||
|
||||
check-32bit:
|
||||
name: "Check (32-bit)"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: i686-unknown-linux-gnu
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
- name: Install 32-bit libs
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc-multilib
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
- name: Cargo check (32-bit, no default features)
|
||||
run: cargo check --target i686-unknown-linux-gnu --no-default-features
|
||||
|
||||
# Composite status check — branch protection only needs to require this
|
||||
# single job instead of tracking every matrix leg individually.
|
||||
gate:
|
||||
name: CI Required Gate
|
||||
if: always()
|
||||
needs: [lint, test, build, security, check-32bit]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check upstream job results
|
||||
run: |
|
||||
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
|
||||
echo "::error::One or more upstream jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
security-gate:
|
||||
name: Security Required Gate
|
||||
if: always()
|
||||
needs: [security]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check security job result
|
||||
run: |
|
||||
if [[ "${{ needs.security.result }}" != "success" ]]; then
|
||||
echo "::error::Security audit failed or was cancelled"
|
||||
exit 1
|
||||
fi
|
||||
210
third_party/zeroclaw/.github/workflows/ci-run.yml
vendored
Normal file
210
third_party/zeroclaw/.github/workflows/ci-run.yml
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
components: rustfmt, clippy
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
bench-compile:
|
||||
name: Verify Benchmarks Compile
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Verify benchmarks compile
|
||||
run: cargo bench --no-run --locked
|
||||
|
||||
lint-strict-delta:
|
||||
name: Strict Delta Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Run strict delta lint gate
|
||||
run: bash scripts/ci/rust_strict_delta_gate.sh
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [lint]
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Install mold linker
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Install cargo-nextest
|
||||
run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
|
||||
|
||||
- name: Run tests
|
||||
run: cargo nextest run --locked
|
||||
env:
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
needs: [lint]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- name: Install mold linker
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
shell: bash
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Build release
|
||||
shell: bash
|
||||
run: cargo build --profile ci --locked --target ${{ matrix.target }}
|
||||
env:
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"
|
||||
|
||||
check-all-features:
|
||||
name: Check (all features)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
needs: [lint]
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
|
||||
- name: Install system dependencies
|
||||
run: sudo apt-get update -qq && sudo apt-get install -y libudev-dev
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Check all features
|
||||
run: cargo check --features ci-all --locked
|
||||
|
||||
docs-quality:
|
||||
name: Docs Quality
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: 20
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Run docs quality gate
|
||||
run: bash scripts/ci/docs_quality_gate.sh
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
|
||||
# Composite status check — branch protection requires this single job.
|
||||
gate:
|
||||
name: CI Required Gate
|
||||
if: always()
|
||||
needs: [lint, bench-compile, lint-strict-delta, test, build, docs-quality, check-all-features]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check upstream job results
|
||||
env:
|
||||
HAS_FAILURE: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
run: |
|
||||
if [[ "$HAS_FAILURE" == "true" ]]; then
|
||||
echo "::error::One or more upstream jobs failed or were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
82
third_party/zeroclaw/.github/workflows/cross-platform-build-manual.yml
vendored
Normal file
82
third_party/zeroclaw/.github/workflows/cross-platform-build-manual.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Cross-Platform Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
web:
|
||||
name: Build Web Dashboard
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: [web]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
cross_compiler: gcc-aarch64-linux-gnu
|
||||
linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER
|
||||
linker: aarch64-linux-gnu-gcc
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
cross_compiler: gcc-arm-linux-gnueabihf
|
||||
linker_env: CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER
|
||||
linker: arm-linux-gnueabihf-gcc
|
||||
- os: macos-15-intel
|
||||
target: x86_64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
|
||||
- name: Install cross compiler
|
||||
if: matrix.cross_compiler
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ${{ matrix.cross_compiler }}
|
||||
|
||||
- name: Build release
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ matrix.linker_env || '' }}" ] && [ -n "${{ matrix.linker || '' }}" ]; then
|
||||
export "${{ matrix.linker_env }}=${{ matrix.linker }}"
|
||||
fi
|
||||
cargo build --release --locked --features channel-matrix,channel-lark --target ${{ matrix.target }}
|
||||
145
third_party/zeroclaw/.github/workflows/discord-release.yml
vendored
Normal file
145
third_party/zeroclaw/.github/workflows/discord-release.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
name: Discord Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Stable release tag (e.g. v0.6.2)"
|
||||
required: true
|
||||
type: string
|
||||
release_url:
|
||||
description: "GitHub Release URL"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL:
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Release tag (e.g. v0.6.2)"
|
||||
required: true
|
||||
type: string
|
||||
release_url:
|
||||
description: "GitHub Release URL"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build Discord message
|
||||
id: msg
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
RELEASE_URL: ${{ inputs.release_url }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Find previous stable tag
|
||||
PREV_STABLE=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
|
||||
RANGE="${PREV_STABLE:+${PREV_STABLE}..}${RELEASE_TAG}"
|
||||
|
||||
# Extract features
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
# Extract fixes
|
||||
FIXES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^fix(\(|:)' \
|
||||
| sed 's/^fix(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^fix: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
FEAT_LIST=""
|
||||
if [ -n "$FEATURES" ]; then
|
||||
FEAT_LIST=$(echo "$FEATURES" | head -8 | while IFS= read -r line; do echo "🚀 ${line}"; done)
|
||||
fi
|
||||
|
||||
FIX_LIST=""
|
||||
if [ -n "$FIXES" ]; then
|
||||
FIX_LIST=$(echo "$FIXES" | head -5 | while IFS= read -r line; do echo "🔧 ${line}"; done)
|
||||
fi
|
||||
|
||||
BODY=""
|
||||
if [ -n "$FEAT_LIST" ]; then
|
||||
BODY="${FEAT_LIST}"
|
||||
fi
|
||||
if [ -n "$FIX_LIST" ]; then
|
||||
[ -n "$BODY" ] && BODY="${BODY}\n"
|
||||
BODY="${BODY}${FIX_LIST}"
|
||||
fi
|
||||
if [ -z "$BODY" ]; then
|
||||
BODY="🚀 Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "body<<MSG_EOF"
|
||||
echo -e "$BODY"
|
||||
echo "MSG_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post to Discord
|
||||
shell: bash
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
RELEASE_URL: ${{ inputs.release_url }}
|
||||
MSG_BODY: ${{ steps.msg.outputs.body }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||
echo "::warning::DISCORD_WEBHOOK_URL secret not configured — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build Discord embed payload
|
||||
PAYLOAD=$(python3 -c "
|
||||
import json, os
|
||||
tag = os.environ['RELEASE_TAG']
|
||||
url = os.environ['RELEASE_URL']
|
||||
body = os.environ['MSG_BODY']
|
||||
|
||||
embed = {
|
||||
'title': f'ZeroClaw {tag} Released',
|
||||
'description': body + '\n\nZero overhead. Zero compromise. 100% Rust.',
|
||||
'url': url,
|
||||
'color': 0xF97316,
|
||||
'footer': {'text': 'ZeroClaw Release Bot'},
|
||||
}
|
||||
|
||||
payload = {
|
||||
'username': 'ZeroClaw Releases',
|
||||
'embeds': [embed],
|
||||
}
|
||||
print(json.dumps(payload))
|
||||
")
|
||||
|
||||
HTTP_CODE=$(curl -s -o /tmp/discord_response.txt -w "%{http_code}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"$DISCORD_WEBHOOK_URL")
|
||||
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "Discord notification sent (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo "::error::Discord webhook failed (HTTP $HTTP_CODE)"
|
||||
cat /tmp/discord_response.txt
|
||||
exit 1
|
||||
fi
|
||||
130
third_party/zeroclaw/.github/workflows/master-branch-flow.md
vendored
Normal file
130
third_party/zeroclaw/.github/workflows/master-branch-flow.md
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
# Master Branch Delivery Flows
|
||||
|
||||
This document explains what runs when code is proposed to `master` and released.
|
||||
|
||||
Use this with:
|
||||
|
||||
- [`docs/ci-map.md`](../../docs/contributing/ci-map.md)
|
||||
- [`docs/pr-workflow.md`](../../docs/contributing/pr-workflow.md)
|
||||
- [`docs/release-process.md`](../../docs/contributing/release-process.md)
|
||||
|
||||
## Branching Model
|
||||
|
||||
ZeroClaw uses a single default branch: `master`. All contributor PRs target `master` directly. There is no `dev` or promotion branch.
|
||||
|
||||
Current maintainers with PR approval authority: `theonlyhennygod`, `JordanTheJet`, and `SimianAstronaut7`.
|
||||
|
||||
## Active Workflows
|
||||
|
||||
| File | Trigger | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `checks-on-pr.yml` | `pull_request` → `master` | Lint + test + build + security audit on every PR |
|
||||
| `cross-platform-build-manual.yml` | `workflow_dispatch` | Full platform build matrix (manual) |
|
||||
| `release-beta-on-push.yml` | `push` → `master` | Beta release on every master commit |
|
||||
| `release-stable-manual.yml` | `workflow_dispatch` | Stable release (manual, version-gated) |
|
||||
|
||||
## Event Summary
|
||||
|
||||
| Event | Workflows triggered |
|
||||
| --- | --- |
|
||||
| PR opened or updated against `master` | `checks-on-pr.yml` |
|
||||
| Push to `master` (including after merge) | `release-beta-on-push.yml` |
|
||||
| Manual dispatch | `cross-platform-build-manual.yml`, `release-stable-manual.yml` |
|
||||
|
||||
## Step-By-Step
|
||||
|
||||
### 1) PR → `master`
|
||||
|
||||
1. Contributor opens or updates a PR against `master`.
|
||||
2. `checks-on-pr.yml` starts:
|
||||
- `lint` job: runs `cargo fmt --check` and `cargo clippy -D warnings`.
|
||||
- `test` job: runs `cargo nextest run --locked` on `ubuntu-latest` with Rust 1.92.0 and mold linker.
|
||||
- `build` job (matrix): compiles release binary on `x86_64-unknown-linux-gnu` and `aarch64-apple-darwin`.
|
||||
- `security` job: runs `cargo audit` and `cargo deny check licenses sources`.
|
||||
- Concurrency group cancels in-progress runs for the same PR on new pushes.
|
||||
3. All jobs must pass before merge.
|
||||
4. Maintainer (`theonlyhennygod`, `JordanTheJet`, or `SimianAstronaut7`) merges PR once checks and review policy are satisfied.
|
||||
5. Merge emits a `push` event on `master` (see section 2).
|
||||
|
||||
### 2) Push to `master` (including after merge)
|
||||
|
||||
1. Commit reaches `master`.
|
||||
2. `release-beta-on-push.yml` (Release Beta) starts:
|
||||
- `version` job: computes beta tag as `v{cargo_version}-beta.{run_number}`.
|
||||
- `build` job (matrix, 4 targets): `x86_64-linux`, `aarch64-linux`, `aarch64-darwin`, `x86_64-windows`.
|
||||
- `publish` job: generates `SHA256SUMS`, creates a GitHub pre-release with all artifacts. Artifact retention: 7 days.
|
||||
- `docker` job: builds multi-platform image (`linux/amd64,linux/arm64`) and pushes to `ghcr.io` with `:beta` and the versioned beta tag.
|
||||
3. This runs on every push to `master` without filtering. Every merged PR produces a beta pre-release.
|
||||
|
||||
### 3) Stable Release (manual)
|
||||
|
||||
1. Maintainer runs `release-stable-manual.yml` via `workflow_dispatch` with a version input (e.g. `0.2.0`).
|
||||
2. `validate` job checks:
|
||||
- Input matches semver `X.Y.Z` format.
|
||||
- `Cargo.toml` version matches input exactly.
|
||||
- Tag `vX.Y.Z` does not already exist on the remote.
|
||||
3. `build` job (matrix, same 4 targets as beta): compiles release binary.
|
||||
4. `publish` job: generates `SHA256SUMS`, creates a stable GitHub Release (not pre-release). Artifact retention: 14 days.
|
||||
5. `docker` job: pushes to `ghcr.io` with `:latest` and `:vX.Y.Z`.
|
||||
|
||||
### 4) Full Platform Build (manual)
|
||||
|
||||
1. Maintainer runs `cross-platform-build-manual.yml` via `workflow_dispatch`.
|
||||
2. `build` job (matrix, 3 targets): `aarch64-linux-gnu`, `x86_64-darwin` (macOS 15 Intel), `x86_64-windows-msvc`.
|
||||
3. Build-only, no tests, no publish. Used to verify cross-compilation on platforms not covered by `checks-on-pr.yml`.
|
||||
|
||||
## Build Targets by Workflow
|
||||
|
||||
| Target | `checks-on-pr.yml` | `cross-platform-build-manual.yml` | `release-beta-on-push.yml` | `release-stable-manual.yml` |
|
||||
| --- | :---: | :---: | :---: | :---: |
|
||||
| `x86_64-unknown-linux-gnu` | ✓ | | ✓ | ✓ |
|
||||
| `aarch64-unknown-linux-gnu` | | ✓ | ✓ | ✓ |
|
||||
| `aarch64-apple-darwin` | ✓ | | ✓ | ✓ |
|
||||
| `x86_64-apple-darwin` | | ✓ | | |
|
||||
| `x86_64-pc-windows-msvc` | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
## Mermaid Diagrams
|
||||
|
||||
### PR to Master
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["PR opened or updated → master"] --> B["checks-on-pr.yml"]
|
||||
B --> B0["lint: fmt + clippy"]
|
||||
B --> B1["test: cargo nextest (ubuntu-latest)"]
|
||||
B --> B2["build: x86_64-linux + aarch64-darwin"]
|
||||
B --> B3["security: audit + deny"]
|
||||
B0 & B1 & B2 & B3 --> C{"Checks pass?"}
|
||||
C -->|No| D["PR stays open"]
|
||||
C -->|Yes| E["Maintainer merges"]
|
||||
E --> F["push event on master"]
|
||||
```
|
||||
|
||||
### Beta Release (on every master push)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Push to master"] --> B["release-beta-on-push.yml"]
|
||||
B --> B1["version: compute v{x.y.z}-beta.{N}"]
|
||||
B1 --> B2["build: 4 targets"]
|
||||
B2 --> B3["publish: GitHub pre-release + SHA256SUMS"]
|
||||
B2 --> B4["docker: push ghcr.io :beta + versioned tag"]
|
||||
```
|
||||
|
||||
### Stable Release (manual)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["workflow_dispatch: version=X.Y.Z"] --> B["release-stable-manual.yml"]
|
||||
B --> B1["validate: semver + Cargo.toml + tag uniqueness"]
|
||||
B1 --> B2["build: 4 targets"]
|
||||
B2 --> B3["publish: GitHub stable release + SHA256SUMS"]
|
||||
B2 --> B4["docker: push ghcr.io :latest + :vX.Y.Z"]
|
||||
```
|
||||
|
||||
## Quick Troubleshooting
|
||||
|
||||
1. **Quality gate failing on PR**: check `lint` job for formatting/clippy issues; check `test` job for test failures; check `build` job for compile errors; check `security` job for audit/deny failures.
|
||||
2. **Beta release not appearing**: confirm the push landed on `master` (not another branch); check `release-beta-on-push.yml` run status.
|
||||
3. **Stable release failing at validate**: ensure `Cargo.toml` version matches the input version and the tag does not already exist.
|
||||
4. **Full matrix build needed**: run `cross-platform-build-manual.yml` manually from the Actions tab.
|
||||
19
third_party/zeroclaw/.github/workflows/pr-path-labeler.yml
vendored
Normal file
19
third_party/zeroclaw/.github/workflows/pr-path-labeler.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: PR Path Labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Apply path labels
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5
|
||||
with:
|
||||
sync-labels: true
|
||||
181
third_party/zeroclaw/.github/workflows/pub-aur.yml
vendored
Normal file
181
third_party/zeroclaw/.github/workflows/pub-aur.yml
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
name: Pub AUR Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Generate PKGBUILD only (no push)"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
AUR_SSH_KEY:
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Generate PKGBUILD only (no push)"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: aur-publish-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-aur:
|
||||
name: Update AUR Package
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate and compute metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::release_tag must be vX.Y.Z format."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${RELEASE_TAG#v}"
|
||||
tarball_url="https://github.com/${GITHUB_REPOSITORY}/archive/refs/tags/${RELEASE_TAG}.tar.gz"
|
||||
tarball_sha="$(curl -fsSL "$tarball_url" | sha256sum | awk '{print $1}')"
|
||||
|
||||
if [[ -z "$tarball_sha" ]]; then
|
||||
echo "::error::Could not compute SHA256 for source tarball."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "tarball_url=$tarball_url"
|
||||
echo "tarball_sha=$tarball_sha"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "### AUR Package Metadata"
|
||||
echo "- version: \`${version}\`"
|
||||
echo "- tarball_url: \`${tarball_url}\`"
|
||||
echo "- tarball_sha: \`${tarball_sha}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Generate PKGBUILD
|
||||
id: pkgbuild
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
TARBALL_SHA: ${{ steps.meta.outputs.tarball_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
pkgbuild_file="$(mktemp)"
|
||||
sed -e "s/^pkgver=.*/pkgver=${VERSION}/" \
|
||||
-e "s/^sha256sums=.*/sha256sums=('${TARBALL_SHA}')/" \
|
||||
dist/aur/PKGBUILD > "$pkgbuild_file"
|
||||
|
||||
echo "pkgbuild_file=$pkgbuild_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "### Generated PKGBUILD" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```bash' >> "$GITHUB_STEP_SUMMARY"
|
||||
cat "$pkgbuild_file" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Generate .SRCINFO
|
||||
id: srcinfo
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
TARBALL_SHA: ${{ steps.meta.outputs.tarball_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
srcinfo_file="$(mktemp)"
|
||||
sed -e "s/pkgver = .*/pkgver = ${VERSION}/" \
|
||||
-e "s/sha256sums = .*/sha256sums = ${TARBALL_SHA}/" \
|
||||
-e "s|zeroclaw-[0-9.]*.tar.gz|zeroclaw-${VERSION}.tar.gz|g" \
|
||||
-e "s|/v[0-9.]*\.tar\.gz|/v${VERSION}.tar.gz|g" \
|
||||
dist/aur/.SRCINFO > "$srcinfo_file"
|
||||
|
||||
echo "srcinfo_file=$srcinfo_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Push to AUR
|
||||
if: inputs.dry_run == false
|
||||
shell: bash
|
||||
env:
|
||||
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
||||
PKGBUILD_FILE: ${{ steps.pkgbuild.outputs.pkgbuild_file }}
|
||||
SRCINFO_FILE: ${{ steps.srcinfo.outputs.srcinfo_file }}
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${AUR_SSH_KEY}" ]]; then
|
||||
echo "::error::Secret AUR_SSH_KEY is required for non-dry-run."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set up SSH key — normalize line endings and ensure trailing newline
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$AUR_SSH_KEY" | tr -d '\r' > ~/.ssh/aur
|
||||
chmod 600 ~/.ssh/aur
|
||||
|
||||
cat > ~/.ssh/config <<'SSH_CONFIG'
|
||||
Host aur.archlinux.org
|
||||
IdentityFile ~/.ssh/aur
|
||||
User aur
|
||||
StrictHostKeyChecking accept-new
|
||||
SSH_CONFIG
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
# Verify key is valid and print fingerprint for debugging
|
||||
echo "::group::SSH key diagnostics"
|
||||
ssh-keygen -l -f ~/.ssh/aur || { echo "::error::AUR_SSH_KEY is not a valid SSH private key"; exit 1; }
|
||||
echo "::endgroup::"
|
||||
|
||||
# Test SSH connectivity before attempting clone
|
||||
ssh -T -o BatchMode=yes -o ConnectTimeout=10 aur@aur.archlinux.org 2>&1 || true
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
git clone ssh://aur@aur.archlinux.org/zeroclaw.git "$tmp_dir/aur"
|
||||
|
||||
cp "$PKGBUILD_FILE" "$tmp_dir/aur/PKGBUILD"
|
||||
cp "$SRCINFO_FILE" "$tmp_dir/aur/.SRCINFO"
|
||||
|
||||
cd "$tmp_dir/aur"
|
||||
git config user.name "zeroclaw-bot"
|
||||
git config user.email "bot@zeroclaw.dev"
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "zeroclaw ${VERSION}"
|
||||
git push origin HEAD
|
||||
|
||||
echo "AUR package updated to ${VERSION}"
|
||||
|
||||
- name: Summary
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "Dry run complete: PKGBUILD generated, no push performed."
|
||||
else
|
||||
echo "Publish complete: AUR package pushed."
|
||||
fi
|
||||
235
third_party/zeroclaw/.github/workflows/pub-homebrew-core.yml
vendored
Normal file
235
third_party/zeroclaw/.github/workflows/pub-homebrew-core.yml
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
name: Pub Homebrew Core
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag to publish (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Patch formula only (no push/PR)"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
HOMEBREW_UPSTREAM_PR_TOKEN:
|
||||
required: false
|
||||
HOMEBREW_CORE_BOT_TOKEN:
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag to publish (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Patch formula only (no push/PR)"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: homebrew-core-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-homebrew-core:
|
||||
name: Publish Homebrew Core PR
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
UPSTREAM_REPO: Homebrew/homebrew-core
|
||||
FORMULA_PATH: Formula/z/zeroclaw.rb
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
BOT_FORK_REPO: ${{ vars.HOMEBREW_CORE_BOT_FORK_REPO }}
|
||||
BOT_EMAIL: ${{ vars.HOMEBREW_CORE_BOT_EMAIL }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate release tag and version alignment
|
||||
id: release_meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
semver_pattern='^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$'
|
||||
if [[ ! "$RELEASE_TAG" =~ $semver_pattern ]]; then
|
||||
echo "::error::release_tag must match semver-like format (vX.Y.Z[-suffix])."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git rev-parse "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1; then
|
||||
git fetch --tags origin
|
||||
fi
|
||||
|
||||
tag_version="${RELEASE_TAG#v}"
|
||||
cargo_version="$(git show "${RELEASE_TAG}:Cargo.toml" \
|
||||
| sed -n 's/^version = "\([^"]*\)"/\1/p' | head -n1)"
|
||||
if [[ -z "$cargo_version" ]]; then
|
||||
echo "::error::Unable to read Cargo.toml version from tag ${RELEASE_TAG}."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$cargo_version" != "$tag_version" ]]; then
|
||||
echo "::error::Tag ${RELEASE_TAG} does not match Cargo.toml version (${cargo_version})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tarball_url="https://github.com/${GITHUB_REPOSITORY}/archive/refs/tags/${RELEASE_TAG}.tar.gz"
|
||||
tarball_sha="$(curl -fsSL "$tarball_url" | sha256sum | awk '{print $1}')"
|
||||
|
||||
{
|
||||
echo "tag_version=$tag_version"
|
||||
echo "tarball_url=$tarball_url"
|
||||
echo "tarball_sha=$tarball_sha"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "### Release Metadata"
|
||||
echo "- release_tag: \`${RELEASE_TAG}\`"
|
||||
echo "- cargo_version: \`${cargo_version}\`"
|
||||
echo "- tarball_sha256: \`${tarball_sha}\`"
|
||||
echo "- dry_run: ${DRY_RUN}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Patch Homebrew formula
|
||||
id: patch_formula
|
||||
shell: bash
|
||||
env:
|
||||
HOMEBREW_CORE_BOT_TOKEN: ${{ secrets.HOMEBREW_UPSTREAM_PR_TOKEN || secrets.HOMEBREW_CORE_BOT_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.HOMEBREW_UPSTREAM_PR_TOKEN || secrets.HOMEBREW_CORE_BOT_TOKEN }}
|
||||
TARBALL_URL: ${{ steps.release_meta.outputs.tarball_url }}
|
||||
TARBALL_SHA: ${{ steps.release_meta.outputs.tarball_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
tmp_repo="$(mktemp -d)"
|
||||
echo "tmp_repo=$tmp_repo" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
git clone --depth=1 "https://github.com/${UPSTREAM_REPO}.git" "$tmp_repo/homebrew-core"
|
||||
else
|
||||
if [[ -z "${BOT_FORK_REPO}" ]]; then
|
||||
echo "::error::Repository variable HOMEBREW_CORE_BOT_FORK_REPO is required when dry_run=false."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${HOMEBREW_CORE_BOT_TOKEN}" ]]; then
|
||||
echo "::error::Repository secret HOMEBREW_CORE_BOT_TOKEN is required when dry_run=false."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$BOT_FORK_REPO" != */* ]]; then
|
||||
echo "::error::HOMEBREW_CORE_BOT_FORK_REPO must be in owner/repo format."
|
||||
exit 1
|
||||
fi
|
||||
if ! gh api "repos/${BOT_FORK_REPO}" >/dev/null 2>&1; then
|
||||
echo "::error::HOMEBREW_CORE_BOT_TOKEN cannot access ${BOT_FORK_REPO}."
|
||||
exit 1
|
||||
fi
|
||||
gh repo clone "${BOT_FORK_REPO}" "$tmp_repo/homebrew-core" -- --depth=1
|
||||
fi
|
||||
|
||||
repo_dir="$tmp_repo/homebrew-core"
|
||||
formula_file="$repo_dir/$FORMULA_PATH"
|
||||
if [[ ! -f "$formula_file" ]]; then
|
||||
echo "::error::Formula file not found: $FORMULA_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == "false" ]]; then
|
||||
if git -C "$repo_dir" remote get-url upstream >/dev/null 2>&1; then
|
||||
git -C "$repo_dir" remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git"
|
||||
else
|
||||
git -C "$repo_dir" remote add upstream "https://github.com/${UPSTREAM_REPO}.git"
|
||||
fi
|
||||
if git -C "$repo_dir" ls-remote --exit-code --heads upstream main >/dev/null 2>&1; then
|
||||
upstream_ref="main"
|
||||
else
|
||||
upstream_ref="master"
|
||||
fi
|
||||
git -C "$repo_dir" fetch --depth=1 upstream "$upstream_ref"
|
||||
branch_name="zeroclaw-${RELEASE_TAG}-${GITHUB_RUN_ID}"
|
||||
git -C "$repo_dir" checkout -B "$branch_name" "upstream/$upstream_ref"
|
||||
echo "branch_name=$branch_name" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
tarball_url="${TARBALL_URL}"
|
||||
tarball_sha="${TARBALL_SHA}"
|
||||
|
||||
if [[ -z "$tarball_url" || -z "$tarball_sha" ]]; then
|
||||
echo "::error::tarball_url or tarball_sha is empty — release_meta step output not propagated."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
perl -0pi -e "s|^ url \".*\"| url \"${tarball_url}\"|m" "$formula_file"
|
||||
perl -0pi -e "s|^ sha256 \".*\"| sha256 \"${tarball_sha}\"|m" "$formula_file"
|
||||
perl -0pi -e "s|^ license \".*\"| license \"Apache-2.0 OR MIT\"|m" "$formula_file"
|
||||
|
||||
# Ensure Node.js build dependency is declared so that build.rs can
|
||||
# run `npm ci && npm run build` to produce the web frontend assets.
|
||||
if ! grep -q 'depends_on "node" => :build' "$formula_file"; then
|
||||
perl -0pi -e 's|( depends_on "rust" => :build\n)|\1 depends_on "node" => :build\n|m' "$formula_file"
|
||||
fi
|
||||
|
||||
git -C "$repo_dir" diff -- "$FORMULA_PATH" > "$tmp_repo/formula.diff"
|
||||
if [[ ! -s "$tmp_repo/formula.diff" ]]; then
|
||||
echo "::error::No formula changes generated. Nothing to publish."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "### Formula Diff"
|
||||
echo '```diff'
|
||||
cat "$tmp_repo/formula.diff"
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Push branch and open Homebrew PR
|
||||
if: inputs.dry_run == false
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HOMEBREW_UPSTREAM_PR_TOKEN || secrets.HOMEBREW_CORE_BOT_TOKEN }}
|
||||
TMP_REPO: ${{ steps.patch_formula.outputs.tmp_repo }}
|
||||
BRANCH_NAME: ${{ steps.patch_formula.outputs.branch_name }}
|
||||
TAG_VERSION: ${{ steps.release_meta.outputs.tag_version }}
|
||||
TARBALL_URL: ${{ steps.release_meta.outputs.tarball_url }}
|
||||
TARBALL_SHA: ${{ steps.release_meta.outputs.tarball_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="${TMP_REPO}/homebrew-core"
|
||||
fork_owner="${BOT_FORK_REPO%%/*}"
|
||||
bot_email="${BOT_EMAIL:-${fork_owner}@users.noreply.github.com}"
|
||||
|
||||
git -C "$repo_dir" config user.name "$fork_owner"
|
||||
git -C "$repo_dir" config user.email "$bot_email"
|
||||
git -C "$repo_dir" add "$FORMULA_PATH"
|
||||
git -C "$repo_dir" commit -m "zeroclaw ${TAG_VERSION}"
|
||||
gh auth setup-git
|
||||
git -C "$repo_dir" push --set-upstream origin "$BRANCH_NAME"
|
||||
|
||||
pr_body="Automated formula bump from ZeroClaw release workflow.
|
||||
|
||||
- Release tag: ${RELEASE_TAG}
|
||||
- Source tarball: ${TARBALL_URL}
|
||||
- Source sha256: ${TARBALL_SHA}"
|
||||
|
||||
gh pr create \
|
||||
--repo "$UPSTREAM_REPO" \
|
||||
--base main \
|
||||
--head "${fork_owner}:${BRANCH_NAME}" \
|
||||
--title "zeroclaw ${TAG_VERSION}" \
|
||||
--body "$pr_body"
|
||||
|
||||
- name: Summary
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "Dry run complete: formula diff generated, no push/PR performed."
|
||||
else
|
||||
echo "Publish complete: branch pushed and PR opened from bot fork."
|
||||
fi
|
||||
165
third_party/zeroclaw/.github/workflows/pub-scoop.yml
vendored
Normal file
165
third_party/zeroclaw/.github/workflows/pub-scoop.yml
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
name: Pub Scoop Manifest
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Generate manifest only (no push)"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
SCOOP_BUCKET_TOKEN:
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Existing release tag (vX.Y.Z)"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Generate manifest only (no push)"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: scoop-publish-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-scoop:
|
||||
name: Update Scoop Manifest
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
SCOOP_BUCKET_REPO: ${{ vars.SCOOP_BUCKET_REPO }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate and compute metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::release_tag must be vX.Y.Z format."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${RELEASE_TAG#v}"
|
||||
zip_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/zeroclaw-x86_64-pc-windows-msvc.zip"
|
||||
sums_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/SHA256SUMS"
|
||||
|
||||
sha256="$(curl -fsSL "$sums_url" | grep 'zeroclaw-x86_64-pc-windows-msvc.zip' | awk '{print $1}')"
|
||||
|
||||
if [[ -z "$sha256" ]]; then
|
||||
echo "::error::Could not find Windows binary hash in SHA256SUMS for ${RELEASE_TAG}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "zip_url=$zip_url"
|
||||
echo "sha256=$sha256"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "### Scoop Manifest Metadata"
|
||||
echo "- version: \`${version}\`"
|
||||
echo "- zip_url: \`${zip_url}\`"
|
||||
echo "- sha256: \`${sha256}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Generate manifest
|
||||
id: manifest
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
ZIP_URL: ${{ steps.meta.outputs.zip_url }}
|
||||
SHA256: ${{ steps.meta.outputs.sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
manifest_file="$(mktemp)"
|
||||
cat > "$manifest_file" <<MANIFEST
|
||||
{
|
||||
"version": "${VERSION}",
|
||||
"description": "Zero overhead. Zero compromise. 100% Rust. The fastest, smallest AI assistant.",
|
||||
"homepage": "https://github.com/zeroclaw-labs/zeroclaw",
|
||||
"license": "MIT|Apache-2.0",
|
||||
"architecture": {
|
||||
"64bit": {
|
||||
"url": "${ZIP_URL}",
|
||||
"hash": "${SHA256}",
|
||||
"bin": "zeroclaw.exe"
|
||||
}
|
||||
},
|
||||
"checkver": {
|
||||
"github": "https://github.com/zeroclaw-labs/zeroclaw"
|
||||
},
|
||||
"autoupdate": {
|
||||
"architecture": {
|
||||
"64bit": {
|
||||
"url": "https://github.com/zeroclaw-labs/zeroclaw/releases/download/v\$version/zeroclaw-x86_64-pc-windows-msvc.zip"
|
||||
}
|
||||
},
|
||||
"hash": {
|
||||
"url": "https://github.com/zeroclaw-labs/zeroclaw/releases/download/v\$version/SHA256SUMS",
|
||||
"regex": "([a-f0-9]{64})\\\\s+zeroclaw-x86_64-pc-windows-msvc\\\\.zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
MANIFEST
|
||||
|
||||
jq '.' "$manifest_file" > "${manifest_file}.formatted"
|
||||
mv "${manifest_file}.formatted" "$manifest_file"
|
||||
|
||||
echo "manifest_file=$manifest_file" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "### Generated Manifest" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```json' >> "$GITHUB_STEP_SUMMARY"
|
||||
cat "$manifest_file" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Push to Scoop bucket
|
||||
if: inputs.dry_run == false
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }}
|
||||
MANIFEST_FILE: ${{ steps.manifest.outputs.manifest_file }}
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${SCOOP_BUCKET_REPO}" ]]; then
|
||||
echo "::error::Repository variable SCOOP_BUCKET_REPO is required (e.g. zeroclaw-labs/scoop-zeroclaw)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
gh repo clone "${SCOOP_BUCKET_REPO}" "$tmp_dir/bucket" -- --depth=1
|
||||
|
||||
mkdir -p "$tmp_dir/bucket/bucket"
|
||||
cp "$MANIFEST_FILE" "$tmp_dir/bucket/bucket/zeroclaw.json"
|
||||
|
||||
cd "$tmp_dir/bucket"
|
||||
git config user.name "zeroclaw-bot"
|
||||
git config user.email "bot@zeroclaw.dev"
|
||||
git add bucket/zeroclaw.json
|
||||
git commit -m "zeroclaw ${VERSION}"
|
||||
gh auth setup-git
|
||||
git push origin HEAD
|
||||
|
||||
echo "Scoop manifest updated to ${VERSION}"
|
||||
160
third_party/zeroclaw/.github/workflows/publish-crates-auto.yml
vendored
Normal file
160
third_party/zeroclaw/.github/workflows/publish-crates-auto.yml
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
name: Auto-sync crates.io
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "Cargo.toml"
|
||||
|
||||
concurrency:
|
||||
group: publish-crates-auto
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
detect-version-change:
|
||||
name: Detect Version Bump
|
||||
if: github.repository == 'zeroclaw-labs/zeroclaw'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.check.outputs.changed }}
|
||||
version: ${{ steps.check.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Check if version changed
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
current=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
previous=$(git show HEAD~1:Cargo.toml 2>/dev/null | sed -n 's/^version = "\([^"]*\)"/\1/p' | head -1 || echo "")
|
||||
|
||||
echo "Current version: ${current}"
|
||||
echo "Previous version: ${previous}"
|
||||
|
||||
# Skip if stable release workflow will handle this version
|
||||
# (indicated by an existing or imminent stable tag)
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/v${current}" >/dev/null 2>&1; then
|
||||
echo "Stable tag v${current} exists — stable release workflow handles crates.io"
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$current" != "$previous" && -n "$current" ]]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${current}" >> "$GITHUB_OUTPUT"
|
||||
echo "Version bumped from ${previous} to ${current} — will publish"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version unchanged (${current}) — skipping publish"
|
||||
fi
|
||||
|
||||
check-registry:
|
||||
name: Check if Already Published
|
||||
needs: [detect-version-change]
|
||||
if: needs.detect-version-change.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_publish: ${{ steps.check.outputs.should_publish }}
|
||||
steps:
|
||||
- name: Check crates.io for existing version
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ needs.detect-version-change.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
"https://crates.io/api/v1/crates/zeroclawlabs/${VERSION}")
|
||||
|
||||
if [[ "$status" == "200" ]]; then
|
||||
echo "Version ${VERSION} already exists on crates.io — skipping"
|
||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Version ${VERSION} not yet published — proceeding"
|
||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
needs: [detect-version-change, check-registry]
|
||||
if: needs.check-registry.outputs.should_publish == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish aardvark-sys to crates.io
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify -p aardvark-sys 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::aardvark-sys already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Wait for aardvark-sys to index
|
||||
run: sleep 15
|
||||
|
||||
- name: Publish to crates.io
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
VERSION: ${{ needs.detect-version-change.outputs.version }}
|
||||
run: |
|
||||
# Publish to crates.io; treat "already exists" as success
|
||||
# (manual publish or stable workflow may have already published)
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::zeroclawlabs@${VERSION} already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Verify published
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ needs.detect-version-change.outputs.version }}
|
||||
run: |
|
||||
echo "Waiting for crates.io to index..."
|
||||
sleep 15
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
"https://crates.io/api/v1/crates/zeroclawlabs/${VERSION}")
|
||||
if [[ "$status" == "200" ]]; then
|
||||
echo "zeroclawlabs v${VERSION} is live on crates.io"
|
||||
echo "Install: cargo install zeroclawlabs"
|
||||
else
|
||||
echo "::warning::Version may still be indexing — check https://crates.io/crates/zeroclawlabs"
|
||||
fi
|
||||
108
third_party/zeroclaw/.github/workflows/publish-crates.yml
vendored
Normal file
108
third_party/zeroclaw/.github/workflows/publish-crates.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
name: Publish to crates.io
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (e.g. 0.2.0) — must match Cargo.toml"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Dry run (validate without publishing)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: publish-crates
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check version matches Cargo.toml
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
if [[ "$cargo_version" != "$INPUT_VERSION" ]]; then
|
||||
echo "::error::Cargo.toml version (${cargo_version}) does not match input (${INPUT_VERSION})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
needs: [validate]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish aardvark-sys to crates.io
|
||||
if: "!inputs.dry_run"
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify -p aardvark-sys 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::aardvark-sys already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Wait for aardvark-sys to index
|
||||
if: "!inputs.dry_run"
|
||||
run: sleep 15
|
||||
|
||||
- name: Publish (dry run)
|
||||
if: inputs.dry_run
|
||||
run: cargo publish --dry-run --locked --allow-dirty --no-verify
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Publish to crates.io
|
||||
if: "!inputs.dry_run"
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
# Publish to crates.io; treat "already exists" as success
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::zeroclawlabs@${VERSION} already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
462
third_party/zeroclaw/.github/workflows/release-beta-on-push.yml
vendored
Normal file
462
third_party/zeroclaw/.github/workflows/release-beta-on-push.yml
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
name: Release Beta
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: release-beta
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
RELEASE_CARGO_FEATURES: channel-matrix,channel-lark,whatsapp-web
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Resolve Version
|
||||
if: github.repository == 'zeroclaw-labs/zeroclaw'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.ver.outputs.version }}
|
||||
tag: ${{ steps.ver.outputs.tag }}
|
||||
skip: ${{ steps.ver.outputs.skip }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Compute beta version
|
||||
id: ver
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
|
||||
# Skip beta if this is a version bump commit (stable release handles it)
|
||||
commit_msg=$(git log -1 --pretty=format:"%s")
|
||||
if [[ "$commit_msg" =~ ^chore:\ bump\ version ]]; then
|
||||
echo "Version bump commit detected — skipping beta release"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Skip beta if a stable tag already exists for this version
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/v${base_version}" >/dev/null 2>&1; then
|
||||
echo "Stable tag v${base_version} exists — skipping beta release"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
beta_tag="v${base_version}-beta.${GITHUB_RUN_NUMBER}"
|
||||
echo "version=${base_version}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${beta_tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Beta release: ${beta_tag}"
|
||||
|
||||
release-notes:
|
||||
name: Generate Release Notes
|
||||
needs: [version]
|
||||
if: github.repository == 'zeroclaw-labs/zeroclaw' && needs.version.outputs.skip != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
notes: ${{ steps.notes.outputs.body }}
|
||||
features: ${{ steps.notes.outputs.features }}
|
||||
contributors: ${{ steps.notes.outputs.contributors }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Use a wider range — find the previous stable tag to capture all
|
||||
# contributors across the full release cycle, not just one beta bump
|
||||
PREV_TAG=$(git tag --sort=-creatordate \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
RANGE="HEAD"
|
||||
else
|
||||
RANGE="${PREV_TAG}..HEAD"
|
||||
fi
|
||||
|
||||
# Extract features only (feat commits) — skip bug fixes for clean notes
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf \
|
||||
| while IFS= read -r line; do echo "- ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEATURES" ]; then
|
||||
FEATURES="- Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Collect ALL unique contributors: git authors + Co-Authored-By
|
||||
GIT_AUTHORS=$(git log "$RANGE" --pretty=format:"%an" --no-merges | sort -uf || true)
|
||||
CO_AUTHORS=$(git log "$RANGE" --pretty=format:"%b" --no-merges \
|
||||
| grep -ioE 'Co-Authored-By: *[^<]+' \
|
||||
| sed 's/Co-Authored-By: *//i' \
|
||||
| sed 's/ *$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
# Merge, deduplicate, and filter out bots
|
||||
ALL_CONTRIBUTORS=$(printf "%s\n%s" "$GIT_AUTHORS" "$CO_AUTHORS" \
|
||||
| sort -uf \
|
||||
| grep -v '^$' \
|
||||
| grep -viE '\[bot\]$|^dependabot|^github-actions|^copilot|^ZeroClaw Bot|^ZeroClaw Runner|^ZeroClaw Agent|^blacksmith' \
|
||||
| while IFS= read -r name; do echo "- ${name}"; done || true)
|
||||
|
||||
# Build release body
|
||||
BODY=$(cat <<NOTES_EOF
|
||||
## What's New
|
||||
|
||||
${FEATURES}
|
||||
|
||||
## Contributors
|
||||
|
||||
${ALL_CONTRIBUTORS}
|
||||
|
||||
---
|
||||
*Full changelog: ${PREV_TAG}...HEAD*
|
||||
NOTES_EOF
|
||||
)
|
||||
|
||||
# Output multiline values
|
||||
{
|
||||
echo "body<<BODY_EOF"
|
||||
echo "$BODY"
|
||||
echo "BODY_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "features<<FEAT_EOF"
|
||||
echo "$FEATURES"
|
||||
echo "FEAT_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "contributors<<CONTRIB_EOF"
|
||||
echo "$ALL_CONTRIBUTORS"
|
||||
echo "CONTRIB_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
web:
|
||||
name: Build Web Dashboard
|
||||
needs: [version]
|
||||
if: github.repository == 'zeroclaw-labs/zeroclaw' && needs.version.outputs.skip != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: [version, web]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Use ubuntu-22.04 for Linux builds to link against glibc 2.35,
|
||||
# ensuring compatibility with Ubuntu 22.04+ (#3573).
|
||||
- os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
- os: ubuntu-22.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
cross_compiler: gcc-aarch64-linux-gnu
|
||||
linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER
|
||||
linker: aarch64-linux-gnu-gcc
|
||||
- os: ubuntu-22.04
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
cross_compiler: gcc-arm-linux-gnueabihf
|
||||
linker_env: CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER
|
||||
linker: arm-linux-gnueabihf-gcc
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
ndk: true
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact: zeroclaw.exe
|
||||
ext: zip
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
with:
|
||||
prefix-key: ${{ matrix.os }}-${{ matrix.target }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
|
||||
- name: Install cross compiler
|
||||
if: matrix.cross_compiler
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ${{ matrix.cross_compiler }}
|
||||
|
||||
- name: Setup Android NDK
|
||||
if: matrix.ndk
|
||||
run: echo "$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Build release
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ matrix.linker_env || '' }}" ] && [ -n "${{ matrix.linker || '' }}" ]; then
|
||||
export "${{ matrix.linker_env }}=${{ matrix.linker }}"
|
||||
fi
|
||||
cargo build --release --locked --features "${{ env.RELEASE_CARGO_FEATURES }}" --target ${{ matrix.target }}
|
||||
|
||||
- name: Check binary size
|
||||
shell: bash
|
||||
run: bash scripts/ci/check_binary_size.sh "target/${{ matrix.target }}/release/${{ matrix.artifact }}" "${{ matrix.target }}"
|
||||
|
||||
- name: Package (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
tar czf ../../../zeroclaw-${{ matrix.target }}.${{ matrix.ext }} ${{ matrix.artifact }}
|
||||
|
||||
- name: Package (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
7z a ../../../zeroclaw-${{ matrix.target }}.${{ matrix.ext }} ${{ matrix.artifact }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: zeroclaw-${{ matrix.target }}
|
||||
path: zeroclaw-${{ matrix.target }}.${{ matrix.ext }}
|
||||
retention-days: 7
|
||||
|
||||
build-desktop:
|
||||
name: Build Desktop App (macOS Universal)
|
||||
needs: [version]
|
||||
if: needs.version.outputs.skip != 'true'
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
with:
|
||||
prefix-key: macos-tauri
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --locked
|
||||
|
||||
- name: Sync Tauri version with Cargo.toml
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
cd apps/tauri
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq --arg v "$VERSION" '.version = $v' tauri.conf.json > tmp.json && mv tmp.json tauri.conf.json
|
||||
else
|
||||
sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" tauri.conf.json
|
||||
fi
|
||||
echo "Tauri version set to: $VERSION"
|
||||
|
||||
- name: Build Tauri app (universal binary)
|
||||
working-directory: apps/tauri
|
||||
run: cargo tauri build --target universal-apple-darwin
|
||||
|
||||
- name: Prepare desktop release assets
|
||||
run: |
|
||||
mkdir -p desktop-assets
|
||||
find target -name '*.dmg' -exec cp {} desktop-assets/ZeroClaw.dmg \; 2>/dev/null || true
|
||||
find target -name '*.app.tar.gz' -exec cp {} desktop-assets/ZeroClaw-macos.app.tar.gz \; 2>/dev/null || true
|
||||
find target -name '*.app.tar.gz.sig' -exec cp {} desktop-assets/ZeroClaw-macos.app.tar.gz.sig \; 2>/dev/null || true
|
||||
echo "--- Desktop assets ---"
|
||||
ls -lh desktop-assets/
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: desktop-macos
|
||||
path: desktop-assets/*
|
||||
retention-days: 7
|
||||
|
||||
publish:
|
||||
name: Publish Beta Release
|
||||
needs: [version, release-notes, build, build-desktop]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
pattern: zeroclaw-*
|
||||
path: artifacts
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: desktop-macos
|
||||
path: artifacts/desktop-macos
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd artifacts
|
||||
find . -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.dmg' \) -exec sha256sum {} + | sed 's| \./[^/]*/| |' > SHA256SUMS
|
||||
cat SHA256SUMS
|
||||
|
||||
- name: Collect release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.dmg' -o -name 'SHA256SUMS' \) -exec cp {} release-assets/ \;
|
||||
cp install.sh release-assets/
|
||||
echo "--- Assets ---"
|
||||
ls -lh release-assets/
|
||||
|
||||
- name: Write release notes
|
||||
env:
|
||||
NOTES: ${{ needs.release-notes.outputs.notes }}
|
||||
run: printf '%s\n' "$NOTES" > release-notes.md
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
TAG: ${{ needs.version.outputs.tag }}
|
||||
run: |
|
||||
gh release create "$TAG" release-assets/* \
|
||||
--repo "${{ github.repository }}" \
|
||||
--title "$TAG" \
|
||||
--notes-file release-notes.md \
|
||||
--prerelease
|
||||
|
||||
redeploy-website:
|
||||
name: Trigger Website Redeploy
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger website redeploy
|
||||
env:
|
||||
PAT: ${{ secrets.WEBSITE_REPO_PAT }}
|
||||
run: |
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $PAT" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/zeroclaw-labs/zeroclaw-website/dispatches \
|
||||
-d '{"event_type":"new-release","client_payload":{"install_script_url":"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh"}}'
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
needs: [version, build]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: zeroclaw-x86_64-unknown-linux-gnu
|
||||
path: artifacts/
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: zeroclaw-aarch64-unknown-linux-gnu
|
||||
path: artifacts/
|
||||
|
||||
- name: Prepare Docker context with pre-built binaries
|
||||
run: |
|
||||
mkdir -p docker-ctx/bin/amd64 docker-ctx/bin/arm64
|
||||
tar xzf artifacts/zeroclaw-x86_64-unknown-linux-gnu.tar.gz -C docker-ctx/bin/amd64
|
||||
tar xzf artifacts/zeroclaw-aarch64-unknown-linux-gnu.tar.gz -C docker-ctx/bin/arm64
|
||||
|
||||
mkdir -p docker-ctx/zeroclaw-data/.zeroclaw docker-ctx/zeroclaw-data/workspace
|
||||
printf '%s\n' \
|
||||
'workspace_dir = "/zeroclaw-data/workspace"' \
|
||||
'config_path = "/zeroclaw-data/.zeroclaw/config.toml"' \
|
||||
'api_key = ""' \
|
||||
'default_provider = "openrouter"' \
|
||||
'default_model = "anthropic/claude-sonnet-4-20250514"' \
|
||||
'default_temperature = 0.7' \
|
||||
'' \
|
||||
'[gateway]' \
|
||||
'port = 42617' \
|
||||
'host = "[::]"' \
|
||||
'allow_public_bind = true' \
|
||||
> docker-ctx/zeroclaw-data/.zeroclaw/config.toml
|
||||
|
||||
cp Dockerfile.ci docker-ctx/Dockerfile
|
||||
cp Dockerfile.debian.ci docker-ctx/Dockerfile.debian
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: docker-ctx
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:beta
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push Debian compatibility image
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: docker-ctx
|
||||
file: docker-ctx/Dockerfile.debian
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}-debian
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:beta-debian
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
# Tweet removed — only stable releases should tweet (see tweet-release.yml).
|
||||
598
third_party/zeroclaw/.github/workflows/release-stable-manual.yml
vendored
Normal file
598
third_party/zeroclaw/.github/workflows/release-stable-manual.yml
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
name: Release Stable
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+" # stable tags only (no -beta suffix)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Stable version to release (e.g. 0.2.0)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: promote-release
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
RELEASE_CARGO_FEATURES: channel-matrix,channel-lark,whatsapp-web
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate Version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.check.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- name: Validate semver and Cargo.toml match
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version || '' }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
|
||||
# Resolve version from tag push or manual input
|
||||
if [[ "$EVENT_NAME" == "push" ]]; then
|
||||
# Tag push: extract version from tag name (v0.5.9 -> 0.5.9)
|
||||
input_version="${REF_NAME#v}"
|
||||
else
|
||||
input_version="$INPUT_VERSION"
|
||||
fi
|
||||
|
||||
if [[ ! "$input_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Version must be semver (X.Y.Z). Got: ${input_version}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$cargo_version" != "$input_version" ]]; then
|
||||
echo "::error::Cargo.toml version (${cargo_version}) does not match input (${input_version}). Bump Cargo.toml first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag="v${input_version}"
|
||||
|
||||
# Only check tag existence for manual dispatch (tag push means it already exists)
|
||||
if [[ "$EVENT_NAME" != "push" ]]; then
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
||||
echo "::error::Tag ${tag} already exists."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
web:
|
||||
name: Build Web Dashboard
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
retention-days: 1
|
||||
|
||||
release-notes:
|
||||
name: Generate Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
notes: ${{ steps.notes.outputs.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version || '' }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve version from tag push or manual input
|
||||
if [[ "$EVENT_NAME" == "push" ]]; then
|
||||
INPUT_VERSION="${REF_NAME#v}"
|
||||
fi
|
||||
|
||||
# Find the previous stable tag (exclude beta tags)
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -vE '\-beta\.' | grep -v "^v${INPUT_VERSION}$" | head -1 || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
RANGE="HEAD"
|
||||
else
|
||||
RANGE="${PREV_TAG}..HEAD"
|
||||
fi
|
||||
|
||||
# Extract features only — skip bug fixes for clean release notes
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf \
|
||||
| while IFS= read -r line; do echo "- ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEATURES" ]; then
|
||||
FEATURES="- Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Collect ALL unique contributors: git authors + Co-Authored-By
|
||||
GIT_AUTHORS=$(git log "$RANGE" --pretty=format:"%an" --no-merges | sort -uf || true)
|
||||
CO_AUTHORS=$(git log "$RANGE" --pretty=format:"%b" --no-merges \
|
||||
| grep -ioE 'Co-Authored-By: *[^<]+' \
|
||||
| sed 's/Co-Authored-By: *//i' \
|
||||
| sed 's/ *$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
# Merge, deduplicate, and filter out bots
|
||||
ALL_CONTRIBUTORS=$(printf "%s\n%s" "$GIT_AUTHORS" "$CO_AUTHORS" \
|
||||
| sort -uf \
|
||||
| grep -v '^$' \
|
||||
| grep -viE '\[bot\]$|^dependabot|^github-actions|^copilot|^ZeroClaw Bot|^ZeroClaw Runner|^ZeroClaw Agent|^blacksmith' \
|
||||
| while IFS= read -r name; do echo "- ${name}"; done || true)
|
||||
|
||||
BODY=$(cat <<NOTES_EOF
|
||||
## What's New
|
||||
|
||||
${FEATURES}
|
||||
|
||||
## Contributors
|
||||
|
||||
${ALL_CONTRIBUTORS}
|
||||
|
||||
---
|
||||
*Full changelog: ${PREV_TAG}...v${INPUT_VERSION}*
|
||||
NOTES_EOF
|
||||
)
|
||||
|
||||
{
|
||||
echo "body<<BODY_EOF"
|
||||
echo "$BODY"
|
||||
echo "BODY_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: [validate, web]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Use ubuntu-22.04 for Linux builds to link against glibc 2.35,
|
||||
# ensuring compatibility with Ubuntu 22.04+ (#3573).
|
||||
- os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
- os: ubuntu-22.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
cross_compiler: gcc-aarch64-linux-gnu
|
||||
linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER
|
||||
linker: aarch64-linux-gnu-gcc
|
||||
- os: ubuntu-22.04
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
cross_compiler: gcc-arm-linux-gnueabihf
|
||||
linker_env: CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER
|
||||
linker: arm-linux-gnueabihf-gcc
|
||||
skip_prometheus: true
|
||||
- os: ubuntu-22.04
|
||||
target: arm-unknown-linux-gnueabihf
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
cross_compiler: gcc-arm-linux-gnueabihf
|
||||
linker_env: CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER
|
||||
linker: arm-linux-gnueabihf-gcc
|
||||
skip_prometheus: true
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
artifact: zeroclaw
|
||||
ext: tar.gz
|
||||
ndk: true
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact: zeroclaw.exe
|
||||
ext: zip
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
with:
|
||||
prefix-key: ${{ matrix.os }}-${{ matrix.target }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: web/dist/
|
||||
|
||||
- name: Install cross compiler
|
||||
if: matrix.cross_compiler
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ${{ matrix.cross_compiler }}
|
||||
|
||||
- name: Setup Android NDK
|
||||
if: matrix.ndk
|
||||
run: echo "$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Build release
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ matrix.linker_env || '' }}" ] && [ -n "${{ matrix.linker || '' }}" ]; then
|
||||
export "${{ matrix.linker_env }}=${{ matrix.linker }}"
|
||||
fi
|
||||
# Force ARMv6 codegen for arm-unknown-linux-gnueabihf (#4556)
|
||||
# Ubuntu 22.04's gcc-arm-linux-gnueabihf defaults to ARMv7+NEON,
|
||||
# which segfaults on ARMv6 devices (e.g. Raspberry Pi Zero W).
|
||||
if [ "${{ matrix.target }}" = "arm-unknown-linux-gnueabihf" ]; then
|
||||
export CFLAGS_arm_unknown_linux_gnueabihf="-march=armv6 -mfpu=vfp -mfloat-abi=hard"
|
||||
export CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS="-C target-feature=-neon"
|
||||
fi
|
||||
if [ "${{ matrix.skip_prometheus || 'false' }}" = "true" ]; then
|
||||
cargo build --release --locked --no-default-features --features "${{ env.RELEASE_CARGO_FEATURES }},channel-nostr,skill-creation" --target ${{ matrix.target }}
|
||||
else
|
||||
cargo build --release --locked --features "${{ env.RELEASE_CARGO_FEATURES }}" --target ${{ matrix.target }}
|
||||
fi
|
||||
|
||||
- name: Check binary size
|
||||
shell: bash
|
||||
run: bash scripts/ci/check_binary_size.sh "target/${{ matrix.target }}/release/${{ matrix.artifact }}" "${{ matrix.target }}"
|
||||
|
||||
- name: Package (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
tar czf ../../../zeroclaw-${{ matrix.target }}.${{ matrix.ext }} ${{ matrix.artifact }}
|
||||
|
||||
- name: Package (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
7z a ../../../zeroclaw-${{ matrix.target }}.${{ matrix.ext }} ${{ matrix.artifact }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: zeroclaw-${{ matrix.target }}
|
||||
path: zeroclaw-${{ matrix.target }}.${{ matrix.ext }}
|
||||
retention-days: 14
|
||||
|
||||
build-desktop:
|
||||
name: Build Desktop App (macOS Universal)
|
||||
needs: [validate]
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
with:
|
||||
prefix-key: macos-tauri
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --locked
|
||||
|
||||
- name: Sync Tauri version with Cargo.toml
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
cd apps/tauri
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq --arg v "$VERSION" '.version = $v' tauri.conf.json > tmp.json && mv tmp.json tauri.conf.json
|
||||
else
|
||||
sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" tauri.conf.json
|
||||
fi
|
||||
echo "Tauri version set to: $VERSION"
|
||||
|
||||
- name: Build Tauri app (universal binary)
|
||||
working-directory: apps/tauri
|
||||
run: cargo tauri build --target universal-apple-darwin
|
||||
|
||||
- name: Prepare desktop release assets
|
||||
run: |
|
||||
mkdir -p desktop-assets
|
||||
find target -name '*.dmg' -exec cp {} desktop-assets/ZeroClaw.dmg \; 2>/dev/null || true
|
||||
find target -name '*.app.tar.gz' -exec cp {} desktop-assets/ZeroClaw-macos.app.tar.gz \; 2>/dev/null || true
|
||||
find target -name '*.app.tar.gz.sig' -exec cp {} desktop-assets/ZeroClaw-macos.app.tar.gz.sig \; 2>/dev/null || true
|
||||
echo "--- Desktop assets ---"
|
||||
ls -lh desktop-assets/
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: desktop-macos
|
||||
path: desktop-assets/*
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Publish Stable Release
|
||||
needs: [validate, release-notes, build, build-desktop]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
pattern: zeroclaw-*
|
||||
path: artifacts
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: desktop-macos
|
||||
path: artifacts/desktop-macos
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd artifacts
|
||||
find . -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.dmg' \) -exec sha256sum {} + | sed 's| \./[^/]*/| |' > SHA256SUMS
|
||||
cat SHA256SUMS
|
||||
|
||||
- name: Collect release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name '*.dmg' -o -name 'SHA256SUMS' \) -exec cp {} release-assets/ \;
|
||||
cp install.sh release-assets/
|
||||
echo "--- Assets ---"
|
||||
ls -lh release-assets/
|
||||
|
||||
- name: Write release notes
|
||||
env:
|
||||
NOTES: ${{ needs.release-notes.outputs.notes }}
|
||||
run: printf '%s\n' "$NOTES" > release-notes.md
|
||||
|
||||
- name: Create tag if manual dispatch
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TAG: ${{ needs.validate.outputs.tag }}
|
||||
run: |
|
||||
git tag -a "$TAG" -m "zeroclaw $TAG"
|
||||
git push origin "$TAG"
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
TAG: ${{ needs.validate.outputs.tag }}
|
||||
run: |
|
||||
gh release create "$TAG" release-assets/* \
|
||||
--repo "${{ github.repository }}" \
|
||||
--title "$TAG" \
|
||||
--notes-file release-notes.md \
|
||||
--latest
|
||||
|
||||
crates-io:
|
||||
name: Publish to crates.io
|
||||
needs: [validate, publish]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish aardvark-sys to crates.io
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify -p aardvark-sys 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::aardvark-sys already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
|
||||
- name: Wait for aardvark-sys to index
|
||||
run: sleep 15
|
||||
|
||||
- name: Publish to crates.io
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
# Publish to crates.io; treat "already exists" as success
|
||||
# (auto-publish workflow may have already published this version)
|
||||
CRATE_NAME=$(sed -n 's/^name = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
OUTPUT=$(cargo publish --locked --allow-dirty --no-verify 2>&1) && exit 0
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -q 'already exists'; then
|
||||
echo "::notice::${CRATE_NAME}@${VERSION} already on crates.io — skipping"
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
|
||||
redeploy-website:
|
||||
name: Trigger Website Redeploy
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger website redeploy
|
||||
env:
|
||||
PAT: ${{ secrets.WEBSITE_REPO_PAT }}
|
||||
run: |
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $PAT" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/zeroclaw-labs/zeroclaw-website/dispatches \
|
||||
-d '{"event_type":"new-release","client_payload":{"install_script_url":"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh"}}'
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
needs: [validate, build]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: zeroclaw-x86_64-unknown-linux-gnu
|
||||
path: artifacts/
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: zeroclaw-aarch64-unknown-linux-gnu
|
||||
path: artifacts/
|
||||
|
||||
- name: Prepare Docker context with pre-built binaries
|
||||
run: |
|
||||
mkdir -p docker-ctx/bin/amd64 docker-ctx/bin/arm64
|
||||
tar xzf artifacts/zeroclaw-x86_64-unknown-linux-gnu.tar.gz -C docker-ctx/bin/amd64
|
||||
tar xzf artifacts/zeroclaw-aarch64-unknown-linux-gnu.tar.gz -C docker-ctx/bin/arm64
|
||||
|
||||
mkdir -p docker-ctx/zeroclaw-data/.zeroclaw docker-ctx/zeroclaw-data/workspace
|
||||
printf '%s\n' \
|
||||
'workspace_dir = "/zeroclaw-data/workspace"' \
|
||||
'config_path = "/zeroclaw-data/.zeroclaw/config.toml"' \
|
||||
'api_key = ""' \
|
||||
'default_provider = "openrouter"' \
|
||||
'default_model = "anthropic/claude-sonnet-4-20250514"' \
|
||||
'default_temperature = 0.7' \
|
||||
'' \
|
||||
'[gateway]' \
|
||||
'port = 42617' \
|
||||
'host = "[::]"' \
|
||||
'allow_public_bind = true' \
|
||||
> docker-ctx/zeroclaw-data/.zeroclaw/config.toml
|
||||
|
||||
cp Dockerfile.ci docker-ctx/Dockerfile
|
||||
cp Dockerfile.debian.ci docker-ctx/Dockerfile.debian
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: docker-ctx
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.validate.outputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push Debian compatibility image
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: docker-ctx
|
||||
file: docker-ctx/Dockerfile.debian
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.validate.outputs.tag }}-debian
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:debian
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
# ── Post-publish: package manager auto-sync ─────────────────────────
|
||||
scoop:
|
||||
name: Update Scoop Manifest
|
||||
needs: [validate, publish]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' }}
|
||||
uses: ./.github/workflows/pub-scoop.yml
|
||||
with:
|
||||
release_tag: ${{ needs.validate.outputs.tag }}
|
||||
dry_run: false
|
||||
secrets: inherit
|
||||
|
||||
aur:
|
||||
name: Update AUR Package
|
||||
needs: [validate, publish]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' }}
|
||||
uses: ./.github/workflows/pub-aur.yml
|
||||
with:
|
||||
release_tag: ${{ needs.validate.outputs.tag }}
|
||||
dry_run: false
|
||||
secrets: inherit
|
||||
|
||||
homebrew:
|
||||
name: Update Homebrew Core
|
||||
needs: [validate, publish]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' }}
|
||||
uses: ./.github/workflows/pub-homebrew-core.yml
|
||||
with:
|
||||
release_tag: ${{ needs.validate.outputs.tag }}
|
||||
dry_run: false
|
||||
secrets: inherit
|
||||
|
||||
# ── Post-publish: announce after release + website are live ───────────
|
||||
# Docker push can be slow; don't let it block announcements.
|
||||
tweet:
|
||||
name: Tweet Release
|
||||
needs: [validate, publish, redeploy-website]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' }}
|
||||
uses: ./.github/workflows/tweet-release.yml
|
||||
with:
|
||||
release_tag: ${{ needs.validate.outputs.tag }}
|
||||
release_url: https://github.com/zeroclaw-labs/zeroclaw/releases/tag/${{ needs.validate.outputs.tag }}
|
||||
secrets: inherit
|
||||
|
||||
discord:
|
||||
name: Discord Announcement
|
||||
needs: [validate, publish, redeploy-website]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' }}
|
||||
uses: ./.github/workflows/discord-release.yml
|
||||
with:
|
||||
release_tag: ${{ needs.validate.outputs.tag }}
|
||||
release_url: https://github.com/zeroclaw-labs/zeroclaw/releases/tag/${{ needs.validate.outputs.tag }}
|
||||
secrets: inherit
|
||||
308
third_party/zeroclaw/.github/workflows/tweet-release.yml
vendored
Normal file
308
third_party/zeroclaw/.github/workflows/tweet-release.yml
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
name: Tweet Release
|
||||
|
||||
on:
|
||||
# Called by release workflows AFTER all publish steps (docker, crates, website) complete.
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Stable release tag (e.g. v0.3.0)"
|
||||
required: true
|
||||
type: string
|
||||
release_url:
|
||||
description: "GitHub Release URL"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
TWITTER_CONSUMER_API_KEY:
|
||||
required: false
|
||||
TWITTER_CONSUMER_API_SECRET_KEY:
|
||||
required: false
|
||||
TWITTER_ACCESS_TOKEN:
|
||||
required: false
|
||||
TWITTER_ACCESS_TOKEN_SECRET:
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tweet_text:
|
||||
description: "Custom tweet text (include emojis, keep it punchy)"
|
||||
required: true
|
||||
type: string
|
||||
image_url:
|
||||
description: "Optional image URL to attach (png/jpg)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
tweet:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for new features
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag || '' }}
|
||||
MANUAL_TEXT: ${{ inputs.tweet_text || '' }}
|
||||
run: |
|
||||
# Manual dispatch always proceeds
|
||||
if [ -n "$MANUAL_TEXT" ]; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stable releases (no -beta suffix) always tweet — they represent
|
||||
# the full release cycle, so skipping them loses visibility.
|
||||
if [[ ! "$RELEASE_TAG" =~ -beta\. ]]; then
|
||||
echo "Stable release ${RELEASE_TAG} — always tweet"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find the previous STABLE release tag (exclude betas) to check for new features
|
||||
PREV_TAG=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Count new feat() OR fix() commits since the previous release
|
||||
NEW_CHANGES=$(git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"%s" --no-merges \
|
||||
| grep -ciE '^(feat|fix)(\(|:)' || echo "0")
|
||||
|
||||
if [ "$NEW_CHANGES" -eq 0 ]; then
|
||||
echo "No new features or fixes since ${PREV_TAG} — skipping tweet"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "${NEW_CHANGES} new change(s) since ${PREV_TAG} — tweeting"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build tweet text
|
||||
id: tweet
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.release_tag || '' }}
|
||||
RELEASE_URL: ${{ inputs.release_url || '' }}
|
||||
MANUAL_TEXT: ${{ inputs.tweet_text || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "$MANUAL_TEXT" ]; then
|
||||
TWEET="$MANUAL_TEXT"
|
||||
else
|
||||
# Diff against the last STABLE release (exclude betas) to capture
|
||||
# ALL features accumulated across the full beta cycle
|
||||
PREV_STABLE=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
|
||||
RANGE="${PREV_STABLE:+${PREV_STABLE}..}${RELEASE_TAG}"
|
||||
|
||||
# Extract ALL features since the last stable release
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
FEAT_COUNT=$(echo "$FEATURES" | grep -c . || echo "0")
|
||||
|
||||
# Format top features with rocket emoji (limit to 6 for tweet space)
|
||||
FEAT_LIST=$(echo "$FEATURES" \
|
||||
| head -6 \
|
||||
| while IFS= read -r line; do echo "🚀 ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEAT_LIST" ]; then
|
||||
FEAT_LIST="🚀 Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Build tweet — feature-focused style
|
||||
TWEET=$(printf "🦀 ZeroClaw %s\n\n%s\n\nZero overhead. Zero compromise. 100%% Rust.\n\n#zeroclaw #rust #ai #opensource" \
|
||||
"$RELEASE_TAG" "$FEAT_LIST")
|
||||
fi
|
||||
|
||||
# X/Twitter counts any URL as 23 chars (t.co shortening).
|
||||
# Extract the URL (if present), truncate the BODY to fit, then
|
||||
# re-append the URL so it is never chopped.
|
||||
URL=""
|
||||
BODY="$TWEET"
|
||||
|
||||
# Pull URL out of existing tweet text or use RELEASE_URL
|
||||
FOUND_URL=$(echo "$TWEET" | grep -oE 'https?://[^ ]+' | tail -1 || true)
|
||||
if [ -n "$FOUND_URL" ]; then
|
||||
URL="$FOUND_URL"
|
||||
BODY=$(echo "$TWEET" | sed "s|${URL}||" | sed -e 's/[[:space:]]*$//')
|
||||
elif [ -n "$RELEASE_URL" ]; then
|
||||
URL="$RELEASE_URL"
|
||||
fi
|
||||
|
||||
if [ -n "$URL" ]; then
|
||||
# URL counts as 23 chars on X + 2 chars for \n\n separator = 25
|
||||
MAX_BODY=$((280 - 25))
|
||||
if [ ${#BODY} -gt $MAX_BODY ]; then
|
||||
BODY="${BODY:0:$((MAX_BODY - 3))}..."
|
||||
fi
|
||||
TWEET=$(printf "%s\n\n%s" "$BODY" "$URL")
|
||||
else
|
||||
if [ ${#TWEET} -gt 280 ]; then
|
||||
TWEET="${TWEET:0:277}..."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "--- Tweet preview ---"
|
||||
echo "$TWEET"
|
||||
echo "--- ${#TWEET} chars ---"
|
||||
|
||||
{
|
||||
echo "text<<TWEET_EOF"
|
||||
echo "$TWEET"
|
||||
echo "TWEET_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check for duplicate tweet
|
||||
id: dedup
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
TWEET_TEXT: ${{ steps.tweet.outputs.text }}
|
||||
run: |
|
||||
# Hash the tweet content (ignore whitespace differences)
|
||||
TWEET_HASH=$(echo "$TWEET_TEXT" | tr -s '[:space:]' | sha256sum | cut -d' ' -f1)
|
||||
echo "hash=${TWEET_HASH}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Check if we already have a cache hit for this exact tweet
|
||||
MARKER_FILE="/tmp/tweet-dedup-${TWEET_HASH}"
|
||||
echo "$TWEET_HASH" > "$MARKER_FILE"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
id: tweet-cache
|
||||
with:
|
||||
path: /tmp/tweet-dedup-${{ steps.dedup.outputs.hash }}
|
||||
key: tweet-${{ steps.dedup.outputs.hash }}
|
||||
|
||||
- name: Skip duplicate tweet
|
||||
if: steps.check.outputs.skip != 'true' && steps.tweet-cache.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
echo "::warning::Duplicate tweet detected (hash=${{ steps.dedup.outputs.hash }}) — skipping"
|
||||
echo "This exact tweet was already posted in a previous run."
|
||||
|
||||
- name: Post to X
|
||||
if: steps.check.outputs.skip != 'true' && steps.tweet-cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
|
||||
TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}
|
||||
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
TWEET_TEXT: ${{ steps.tweet.outputs.text }}
|
||||
IMAGE_URL: ${{ inputs.image_url || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Skip if Twitter secrets are not configured
|
||||
if [ -z "$TWITTER_CONSUMER_KEY" ] || [ -z "$TWITTER_ACCESS_TOKEN" ]; then
|
||||
echo "::warning::Twitter secrets not configured — skipping tweet"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pip install requests requests-oauthlib --quiet
|
||||
|
||||
python3 - <<'PYEOF'
|
||||
import os, sys, time
|
||||
from requests_oauthlib import OAuth1Session
|
||||
|
||||
consumer_key = os.environ["TWITTER_CONSUMER_KEY"]
|
||||
consumer_secret = os.environ["TWITTER_CONSUMER_SECRET"]
|
||||
access_token = os.environ["TWITTER_ACCESS_TOKEN"]
|
||||
access_token_secret = os.environ["TWITTER_ACCESS_TOKEN_SECRET"]
|
||||
tweet_text = os.environ["TWEET_TEXT"]
|
||||
image_url = os.environ.get("IMAGE_URL", "")
|
||||
|
||||
oauth = OAuth1Session(
|
||||
consumer_key,
|
||||
client_secret=consumer_secret,
|
||||
resource_owner_key=access_token,
|
||||
resource_owner_secret=access_token_secret,
|
||||
)
|
||||
|
||||
media_id = None
|
||||
|
||||
# Upload image if provided
|
||||
if image_url:
|
||||
import requests
|
||||
print(f"Downloading image: {image_url}")
|
||||
img_resp = requests.get(image_url, timeout=30)
|
||||
img_resp.raise_for_status()
|
||||
|
||||
content_type = img_resp.headers.get("content-type", "image/png")
|
||||
init_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={
|
||||
"command": "INIT",
|
||||
"total_bytes": len(img_resp.content),
|
||||
"media_type": content_type,
|
||||
},
|
||||
)
|
||||
if init_resp.status_code != 202:
|
||||
print(f"Media INIT failed: {init_resp.status_code} {init_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
media_id = init_resp.json()["media_id_string"]
|
||||
|
||||
append_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={"command": "APPEND", "media_id": media_id, "segment_index": 0},
|
||||
files={"media_data": img_resp.content},
|
||||
)
|
||||
if append_resp.status_code not in (200, 204):
|
||||
print(f"Media APPEND failed: {append_resp.status_code} {append_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
fin_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={"command": "FINALIZE", "media_id": media_id},
|
||||
)
|
||||
if fin_resp.status_code not in (200, 201):
|
||||
print(f"Media FINALIZE failed: {fin_resp.status_code} {fin_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
state = fin_resp.json().get("processing_info", {}).get("state")
|
||||
while state == "pending" or state == "in_progress":
|
||||
wait = fin_resp.json().get("processing_info", {}).get("check_after_secs", 2)
|
||||
time.sleep(wait)
|
||||
status_resp = oauth.get(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
params={"command": "STATUS", "media_id": media_id},
|
||||
)
|
||||
state = status_resp.json().get("processing_info", {}).get("state")
|
||||
fin_resp = status_resp
|
||||
|
||||
print(f"Image uploaded: media_id={media_id}")
|
||||
|
||||
# Post tweet
|
||||
payload = {"text": tweet_text}
|
||||
if media_id:
|
||||
payload["media"] = {"media_ids": [media_id]}
|
||||
|
||||
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
||||
|
||||
if resp.status_code == 201:
|
||||
data = resp.json()
|
||||
tweet_id = data["data"]["id"]
|
||||
print(f"Tweet posted: https://x.com/zeroclawlabs/status/{tweet_id}")
|
||||
else:
|
||||
print(f"Failed to post tweet: {resp.status_code}", file=sys.stderr)
|
||||
print(resp.text, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
Reference in New Issue
Block a user