Initialize repository with harness code and assets
Initial import of all source code, config, and README assets: the packages workspace (cli, core, server, web, docs, landing, skills), build scripts, tooling config, and CI workflows. Includes the data-layout revision made on this branch: the local data root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the installer keeps its binaries in ~/.penguin), and every Agent lives under <project>/agents/<agent>/ — path helpers, the three agent-enumeration scans, the system prompt, built-in Skills, tests and docs all follow the new layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# CI: build -> style (Prettier) -> typecheck (tsc) -> unit tests (vitest) -> live e2e (DeepSeek).
|
||||
# Build first: core's exports point at dist/, and cli's type resolution and runtime imports both need core's build output.
|
||||
# e2e needs the repo secret DEEPSEEK_API_KEY; when absent (e.g. forks) that step self-skips and the other checks run as usual.
|
||||
name: CI
|
||||
|
||||
# Limit triggers to avoid duplicate runs: push runs only on main/dev; PRs always run once (no target-branch filter --
|
||||
# this repo's PRs often target integration branches rather than main/dev, and a target filter would leave them with no CI).
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# pnpm version comes from package.json's packageManager field.
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build (tsup)
|
||||
run: pnpm build
|
||||
|
||||
- name: Code style (Prettier)
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Typecheck (tsc)
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Unit tests (vitest)
|
||||
run: pnpm test
|
||||
|
||||
# The secret is exposed only in this step (step-level env); earlier steps and third-party actions can't see it.
|
||||
# The secrets context can't be used in if expressions, so skip inside the shell when it's absent (e.g. forks).
|
||||
- name: E2E (live LLM via DeepSeek)
|
||||
env:
|
||||
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
||||
run: |
|
||||
if [ -z "$DEEPSEEK_API_KEY" ]; then
|
||||
echo "DEEPSEEK_API_KEY not available; skipping e2e."
|
||||
exit 0
|
||||
fi
|
||||
pnpm test:e2e
|
||||
@@ -0,0 +1,75 @@
|
||||
# Public site: build the landing page (site root) and the docs site (/docs/) with Vite
|
||||
# and deploy them as ONE artifact to GitHub Pages (scripts/build-site.mjs assembles the
|
||||
# tree — landing dist with the docs dist copied under docs/).
|
||||
# - Pull requests touching either package only BUILD (validation) — no Pages
|
||||
# configuration and no deploy, so PRs never fail on Pages availability.
|
||||
# - Pushes to main (and manual dispatch) build + deploy. BASE_PATH is derived from the
|
||||
# repository name so project-pages URLs (https://<owner>.github.io/<repo>/) resolve;
|
||||
# repos served from a custom domain / user pages can set it to "/" instead.
|
||||
# First-time setup: repository Settings -> Pages -> Source = "GitHub Actions".
|
||||
name: Deploy Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/landing/**"
|
||||
- "packages/docs/**"
|
||||
- "scripts/build-site.mjs"
|
||||
- ".github/workflows/pages.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/landing/**"
|
||||
- "packages/docs/**"
|
||||
- "scripts/build-site.mjs"
|
||||
- ".github/workflows/pages.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# One deploy at a time; let an in-flight production deploy finish rather than cancelling it.
|
||||
concurrency:
|
||||
group: pages-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# pnpm version comes from package.json's packageManager field.
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build site (landing + docs, vite)
|
||||
env:
|
||||
BASE_PATH: /${{ github.event.repository.name }}/
|
||||
run: pnpm build:site
|
||||
|
||||
# Only the deploying runs need the Pages artifact (PRs stop at the build check).
|
||||
- if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: packages/landing/dist
|
||||
|
||||
deploy:
|
||||
if: github.event_name != 'pull_request'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -0,0 +1,236 @@
|
||||
# Release: tag v* -> build the one-line install artifacts and publish a GitHub Release.
|
||||
# Two parallel jobs:
|
||||
# - release: build the monorepo -> pnpm deploy a production CLI dir -> assemble penguin/ (bin + lib + web)
|
||||
# -> four platform packages each bundling the official Node runtime + a universal package -> SHA256 files -> upload to the Release.
|
||||
# Artifacts: penguin-{linux,darwin}-{x64,arm64}.tar.gz, penguin-universal.tar.gz,
|
||||
# their .sha256 files, SHA256SUMS, and install.sh; one version per tag, multiple versions coexist.
|
||||
# - publish-npm: publish the whole chain (@prismshadow/penguin-skills -> @prismshadow/penguin-core
|
||||
# -> @prismshadow/penguin-server -> @prismshadow/penguin-cli) to npm at the tag version.
|
||||
# skills/core serve the penguin-sdk Skill's `npm install`; server ships the built web assets inside
|
||||
# the package (web-dist/, its default web dir falls back to it), so `npm install -g
|
||||
# @prismshadow/penguin-cli` alone yields a working `penguin` incl. the Web UI (needs Node >= 24).
|
||||
# The publish flow mirrors AgentHub's publish.yml: OIDC trusted publishing (environment: npm +
|
||||
# id-token: write, no token). A Trusted Publisher can only be configured in the settings page of a
|
||||
# package that ALREADY EXISTS on the registry, so a brand-new package cannot be first-published by
|
||||
# this workflow. Release checklist for a new package: (1) a maintainer bootstrap-publishes it once
|
||||
# manually with a one-off granular token (revoke it afterwards), running the same prepare steps as
|
||||
# this job (stamp versions, build, copy LICENSE + web-dist) and publishing with `pnpm publish
|
||||
# --access public --no-git-checks` -- NEVER `npm publish`, which keeps workspace:* deps unrewritten
|
||||
# and yields a package that fails to install (Unsupported URL Type "workspace:");
|
||||
# (2) configure this repo + workflow as its Trusted Publisher on npmjs; (3) subsequent tags publish
|
||||
# via OIDC. The publish step is idempotent (versions already on the registry are skipped), so a tag
|
||||
# that failed mid-chain can be re-run as-is after fixing the config.
|
||||
# npm publishing lives here rather than a separate `on: release: published` workflow: the Release is created
|
||||
# by this workflow's GITHUB_TOKEN, and GitHub won't trigger other workflows' release events from that.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag (e.g. v0.1.0)"
|
||||
required: true
|
||||
|
||||
env:
|
||||
# Bundled Node runtime version (official nodejs.org dist, aligned with engines >=24).
|
||||
NODE_RUNTIME_VERSION: v24.18.0
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
# On manual dispatch, check out the tag itself (not the selected branch HEAD): when re-uploading an existing
|
||||
# tag's artifacts this keeps them in sync with the tag's source. On tag-push, leaving ref empty is the default.
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || '' }}
|
||||
|
||||
# pnpm version comes from package.json's packageManager field.
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Inject the release tag into core's VERSION constant (the source for CLI --version and the install-complete
|
||||
# message); otherwise artifacts always carry the in-repo dev version and multiple installs can't be told apart.
|
||||
- name: Stamp release version
|
||||
run: |
|
||||
TAG="${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}"
|
||||
V="${TAG#v}"
|
||||
grep -q 'export const VERSION = "' packages/core/src/index.ts
|
||||
sed -i "s/export const VERSION = \"[^\"]*\"/export const VERSION = \"$V\"/" packages/core/src/index.ts
|
||||
|
||||
- name: Build (tsup + vite)
|
||||
run: pnpm build
|
||||
|
||||
# lib/: the CLI and its production deps (including workspace core/server/skills, all build outputs);
|
||||
# pnpm 10's deploy needs --legacy (this repo doesn't enable inject-workspace-packages).
|
||||
# bin/penguin launcher: resolve its own real path (following symlinks) -> default PENGUIN_WEB_DIST to
|
||||
# the sibling web/ -> use the bundled runtime (node/bin/node) if present, else fall back to system node.
|
||||
- name: Assemble penguin/ (lib + web + bin)
|
||||
run: |
|
||||
pnpm --filter @prismshadow/penguin-cli --prod deploy --legacy "$PWD/out/penguin/lib"
|
||||
cp -r packages/web/dist out/penguin/web
|
||||
mkdir -p out/penguin/bin
|
||||
cat > out/penguin/bin/penguin <<'EOF'
|
||||
#!/bin/sh
|
||||
SELF="$0"
|
||||
while [ -h "$SELF" ]; do
|
||||
DIR="$(cd "$(dirname "$SELF")" && pwd)"
|
||||
SELF="$(readlink "$SELF")"
|
||||
case "$SELF" in /*) ;; *) SELF="$DIR/$SELF" ;; esac
|
||||
done
|
||||
DIR="$(cd "$(dirname "$SELF")/.." && pwd)"
|
||||
export PENGUIN_WEB_DIST="${PENGUIN_WEB_DIST:-$DIR/web}"
|
||||
if [ -x "$DIR/node/bin/node" ]; then
|
||||
exec "$DIR/node/bin/node" "$DIR/lib/dist/index.js" "$@"
|
||||
fi
|
||||
exec node "$DIR/lib/dist/index.js" "$@"
|
||||
EOF
|
||||
chmod +x out/penguin/bin/penguin
|
||||
|
||||
# Platform packages: linux uses .tar.xz, darwin uses .tar.gz (nodejs.org naming);
|
||||
# node/ is only lightly trimmed (drop share/doc and share/man, keep the rest).
|
||||
- name: Package platform + universal tarballs
|
||||
run: |
|
||||
mkdir -p dist-artifacts
|
||||
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
|
||||
os="${target%%-*}"
|
||||
arch="${target#*-}"
|
||||
name="node-$NODE_RUNTIME_VERSION-$os-$arch"
|
||||
if [ "$os" = "linux" ]; then ext="tar.xz"; else ext="tar.gz"; fi
|
||||
curl -fsSL "https://nodejs.org/dist/$NODE_RUNTIME_VERSION/$name.$ext" -o "/tmp/$name.$ext"
|
||||
rm -rf /tmp/node-runtime out/penguin/node
|
||||
mkdir -p /tmp/node-runtime
|
||||
if [ "$ext" = "tar.xz" ]; then
|
||||
tar -xJf "/tmp/$name.$ext" -C /tmp/node-runtime
|
||||
else
|
||||
tar -xzf "/tmp/$name.$ext" -C /tmp/node-runtime
|
||||
fi
|
||||
mv "/tmp/node-runtime/$name" out/penguin/node
|
||||
rm -rf out/penguin/node/share/doc out/penguin/node/share/man
|
||||
tar -czf "dist-artifacts/penguin-$os-$arch.tar.gz" -C out penguin
|
||||
done
|
||||
# Universal package: no bundled runtime, requires system Node >= 24.
|
||||
rm -rf out/penguin/node
|
||||
tar -czf dist-artifacts/penguin-universal.tar.gz -C out penguin
|
||||
|
||||
# SHA256SUMS summary + a same-named .sha256 per artifact (install.sh verifies against the latter).
|
||||
- name: Generate SHA256 checksums
|
||||
run: |
|
||||
cd dist-artifacts
|
||||
sha256sum *.tar.gz > SHA256SUMS
|
||||
for f in *.tar.gz; do
|
||||
sha256sum "$f" > "$f.sha256"
|
||||
done
|
||||
|
||||
# On tag-push use the ref name; on manual dispatch use the input tag.
|
||||
- name: Publish GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||
files: |
|
||||
dist-artifacts/*.tar.gz
|
||||
dist-artifacts/*.sha256
|
||||
dist-artifacts/SHA256SUMS
|
||||
install.sh
|
||||
|
||||
publish-npm:
|
||||
name: Publish npm packages
|
||||
runs-on: ubuntu-latest
|
||||
# OIDC trusted publishing (same as AgentHub's publish.yml): no token; npmjs establishes trust via
|
||||
# environment `npm` + this workflow. A publish failure doesn't affect the release job (independent, parallel).
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
environment:
|
||||
name: npm
|
||||
url: https://www.npmjs.com/package/@prismshadow/penguin-cli
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || '' }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# npm Trusted Publishing (OIDC) requires npm CLI >= 11.5.1: Node 24 ships npm 11.x, which satisfies it;
|
||||
# this just asserts the version to guard against regressions -- pnpm publish's registry auth ultimately
|
||||
# delegates to system npm, ordinary CI doesn't exercise OIDC (so a green run won't catch it), and too old
|
||||
# a version only surfaces as an auth failure when actually publishing a tag.
|
||||
- name: Assert npm supports trusted publishing (>= 11.5.1)
|
||||
run: |
|
||||
V="$(npm --version)"
|
||||
echo "npm $V"
|
||||
node -e 'const [M, m, p] = process.argv[1].split(".").map(Number); if (M < 11 || (M === 11 && (m < 5 || (m === 5 && p < 1)))) { console.error("npm " + process.argv[1] + " < 11.5.1"); process.exit(1); }' "$V"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Version always comes from the tag: package version and core's VERSION constant are injected together (the
|
||||
# repo keeps the dev version). All published packages must bump in lockstep -- pnpm publish rewrites every
|
||||
# workspace:* dep to the dependency's current version, so the versions must match.
|
||||
- name: Stamp release version
|
||||
run: |
|
||||
TAG="${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}"
|
||||
V="${TAG#v}"
|
||||
grep -q 'export const VERSION = "' packages/core/src/index.ts
|
||||
sed -i "s/export const VERSION = \"[^\"]*\"/export const VERSION = \"$V\"/" packages/core/src/index.ts
|
||||
(cd packages/skills && npm version --no-git-tag-version "$V")
|
||||
(cd packages/core && npm version --no-git-tag-version "$V")
|
||||
(cd packages/server && npm version --no-git-tag-version "$V")
|
||||
(cd packages/cli && npm version --no-git-tag-version "$V")
|
||||
|
||||
# Dependency order: cli bundles core's dist (tsup noExternal), server/web need core's types;
|
||||
# web is built here only to be copied into the server package below.
|
||||
- name: Build and test
|
||||
run: |
|
||||
pnpm --filter @prismshadow/penguin-skills build
|
||||
pnpm --filter @prismshadow/penguin-core build
|
||||
pnpm --filter @prismshadow/penguin-server build
|
||||
pnpm --filter @prismshadow/penguin-web build
|
||||
pnpm --filter @prismshadow/penguin-cli build
|
||||
pnpm --filter @prismshadow/penguin-skills test
|
||||
pnpm --filter @prismshadow/penguin-core test
|
||||
pnpm --filter @prismshadow/penguin-server test
|
||||
pnpm --filter @prismshadow/penguin-cli test
|
||||
|
||||
# Copy LICENSE into the package dirs (the files allowlist includes it, so artifacts ship the license)
|
||||
# and the built web assets into the server package (web-dist/, in its files allowlist: an npm install
|
||||
# serves the Web UI from there without PENGUIN_WEB_DIST).
|
||||
# Idempotent: a version already on the registry is skipped. The check tests `npm view`'s output
|
||||
# rather than its exit code -- for a missing version of an existing package, older npm exits 0 and
|
||||
# newer npm exits 1, but the output is non-empty only when the version exists. Re-running the tag
|
||||
# after a mid-chain failure (e.g. Trusted Publisher not configured yet) picks up where it left off.
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
TAG="${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}"
|
||||
V="${TAG#v}"
|
||||
cp LICENSE packages/skills/LICENSE
|
||||
cp LICENSE packages/core/LICENSE
|
||||
cp LICENSE packages/server/LICENSE
|
||||
cp LICENSE packages/cli/LICENSE
|
||||
rm -rf packages/server/web-dist
|
||||
cp -r packages/web/dist packages/server/web-dist
|
||||
for pkg in skills core server cli; do
|
||||
name="@prismshadow/penguin-$pkg"
|
||||
if [ -n "$(npm view "$name@$V" version 2>/dev/null || true)" ]; then
|
||||
echo "$name@$V already on the registry, skipping."
|
||||
continue
|
||||
fi
|
||||
pnpm --filter "$name" publish --access public --no-git-checks
|
||||
done
|
||||
Reference in New Issue
Block a user