@topy-ai/maggie 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh-TW.md +2 -2
- package/README.md +2 -2
- package/bundled-references/agent-goal-design.md +92 -0
- package/bundled-skills/catalog.json +4 -0
- package/bundled-skills/maggie-deployment/SKILL.md +21 -1
- package/bundled-skills/maggie-design/SKILL.md +29 -1
- package/bundled-skills/maggie-spirit/SKILL.md +128 -0
- package/bundled-tools/clis/maggie_dash.py +12 -4
- package/bundled-tools/clis/maggie_spirit.py +143 -0
- package/bundled-tools/runtime/maggie_dash_pg.py +126 -0
- package/bundled-tools/runtime/maggie_dash_store.py +17 -5
- package/bundled-tools/runtime/spirit_agent.py +262 -0
- package/bundled-tools/runtime/spirit_base.py +256 -0
- package/bundled-tools/runtime/spirit_face.py +222 -0
- package/package.json +1 -1
- package/references/agent-goal-design.md +92 -0
package/README-zh-TW.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English README](README.md) · [完整繁體中文 README](https://github.com/TOPY-AI-LTD/ai-cmo-skills/blob/main/README.zh-TW.md)
|
|
4
4
|
|
|
5
|
-
`@topy-ai/maggie` 提供
|
|
5
|
+
`@topy-ai/maggie` 提供 24 個可安裝的 AI website workflow skills,支援
|
|
6
6
|
Codex、Claude Code 與相容的 coding agents。
|
|
7
7
|
|
|
8
8
|
## 安裝
|
|
@@ -143,7 +143,7 @@ structure drift 或重複 treatment product 時 fail closed。
|
|
|
143
143
|
deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
|
|
144
144
|
publish 與 production deployment 需要明確確認。
|
|
145
145
|
|
|
146
|
-
目前 npm 版本為 `0.8.0
|
|
146
|
+
目前 npm 版本為 `0.8.1`,新增 maggie-spirit(為 AI 品牌生成平面像素 spirit/sprite 系統:logo mark 與各 agent avatar 皆以共用的 32 狀態基礎集播放動畫)與 goal-led AI agent 設計參考,並補上 ONEMAN.APP in-place 開發的部署與設計筆記(靜態站先 build 再部署、Astro 執行期注入節點樣式、in-place DESIGN.md 擷取)。先前 0.8.0 為安全優化 roadmap(scan、image-patch、標頭評分、SSVC、GDPR 日誌規則、新 MCP 工具),含 fix-plan 修補風險分級 T1–T4(判斷補丁是否安全套用)與 host-impact 補丁影響面量測(哪些服務需重啟、是否需重開機)、public-check、Maggie Security、batch 31 排程 agent routines、報告收件匣與無人值守 agent 限制、PHP runtime 盤點與主機鎖、pull 網站改寫、query 點子與社群佇列檢查,batch 30 MaggieDash 先裝核心、release privacy gate、feedback 備援、社群連結與圖片檢查、大量頁面產生與 app 檢查,batch 29 預約規則、部署、app QA、助理與社群功能,以及 batch 28 產品 app、assistant tools、mobile、product release、skills catalog 與 bridge host 功能、batch 26、27 feedback 修正與 idempotent `maggie update`與 Booking email-template 合約/CLI validator 同步。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
|
|
147
147
|
dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
|
|
148
148
|
worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
|
|
149
149
|
camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
|
package/README.md
CHANGED
|
@@ -87,9 +87,9 @@ Then invoke the installed skills from your coding agent, for example:
|
|
|
87
87
|
|
|
88
88
|
Maggie keeps the existing project foundation and asks for decisions before
|
|
89
89
|
shared routes, analytics, or publishing boundaries change. The current
|
|
90
|
-
package ships
|
|
90
|
+
package ships 24 installable skills and a local-first MaggieDash foundation.
|
|
91
91
|
|
|
92
|
-
This package is `0.8.
|
|
92
|
+
This package is `0.8.1` — adds `maggie-spirit` (generate an AI brand's flat-pixel spirit/sprite system: a logo mark plus per-agent avatars animated through a shared 32-state base set) and a goal-led AI agent design reference, plus deployment and design notes from the ONEMAN.APP in-place build (static-site build-before-deploy, Astro runtime-injected DOM styling, in-place DESIGN.md extraction).
|
|
93
93
|
|
|
94
94
|
The 0.7.59 release made `maggie security host-audit` (the deep
|
|
95
95
|
lynis audit over SSH) robust on hosts where a storage test can stall: lynis runs
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Goal-led AI agent design
|
|
2
|
+
|
|
3
|
+
A reusable method for defining the agents in a multi-agent product so a single
|
|
4
|
+
human can run a company by stating intent, not by operating tools. Distilled
|
|
5
|
+
from the ONEMAN.APP cofounder build; pairs with the `maggie-spirit` skill (give
|
|
6
|
+
each designed agent a spirit/avatar) and `maggie-assistant-tools` (expose an
|
|
7
|
+
agent's capabilities safely).
|
|
8
|
+
|
|
9
|
+
## Principle
|
|
10
|
+
|
|
11
|
+
The human sets a **goal**; the agents turn it into work and keep the human in
|
|
12
|
+
the loop only for judgement and approval. Each agent owns one lane, declares the
|
|
13
|
+
**capability classes** it may use, and runs under a **risk ceiling** with
|
|
14
|
+
explicit approval gates. Nothing that is risky, public, or spends money happens
|
|
15
|
+
without a human yes.
|
|
16
|
+
|
|
17
|
+
## Define each agent with a role contract
|
|
18
|
+
|
|
19
|
+
One `role.yaml` per agent. Recommended fields:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
role: CTO # the lane
|
|
23
|
+
name: AI CTO # display name
|
|
24
|
+
persona: Turing # inspiration, not impersonation
|
|
25
|
+
spirit: Engineering # one-word identity (drives the spirit accent/motif)
|
|
26
|
+
summary: > # what it owns, in one or two sentences
|
|
27
|
+
Technical design, build, integrate, test and deploy. Production deployment is
|
|
28
|
+
gated behind human approval.
|
|
29
|
+
responsibilities: [design, build, integrate, test, deploy, observe]
|
|
30
|
+
constraints: [no prod deploy without approval, no secrets in source]
|
|
31
|
+
allowed_capability_classes: [app.scaffold, app.build, app.test, code.review,
|
|
32
|
+
deploy.release, integration.connect, ops.observe, job.execute]
|
|
33
|
+
default_risk_ceiling: L1 # L0 read -> L3 irreversible/public/paid
|
|
34
|
+
business_system: Execution # which paired system (e.g. with COO)
|
|
35
|
+
raci: { new_service_app: "A", security_verdict: I }
|
|
36
|
+
interfaces: { mcp: planned, cli: planned, api: planned }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **Capability classes**, not freeform skills, are the unit of authority. They
|
|
40
|
+
are stable `area.verb` identifiers (`content.publish`, `revenue.close`,
|
|
41
|
+
`security.scan`) that a UI can list and a gate can check. These are the
|
|
42
|
+
"skills" a product surfaces per agent.
|
|
43
|
+
- **One CEO orchestrates.** The CEO owns `goal.decompose`, `mission.manage`,
|
|
44
|
+
`plan.compose`, `priority.rank`, `outcome.accept`: it turns intent into
|
|
45
|
+
missions with acceptance criteria, delegates each to the right agent, and
|
|
46
|
+
signs off - it never bypasses security or budget approval.
|
|
47
|
+
- **Security holds a non-overridable veto.** A high-risk security verdict cannot
|
|
48
|
+
be overruled by another agent.
|
|
49
|
+
- **Finance is explain-only.** Financial facts come from deterministic data
|
|
50
|
+
(billing/usage ledger, Stripe); the CFO reconciles and advises, it is not the
|
|
51
|
+
source of truth.
|
|
52
|
+
|
|
53
|
+
## Risk ceilings and approval gates
|
|
54
|
+
|
|
55
|
+
| Level | Meaning | Gate |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| L0 | read-only | none |
|
|
58
|
+
| L1 | internal writes (drafts, plans, workflows) | logged |
|
|
59
|
+
| L2 | customer-facing or public (publish, close a deal) | human approval |
|
|
60
|
+
| L3 | irreversible / paid / production (deploy, transfer) | human approval + audit |
|
|
61
|
+
|
|
62
|
+
Every agent declares a `default_risk_ceiling`; an action above it stops for an
|
|
63
|
+
explicit human yes. Log every action; make L2/L3 produce an approval card.
|
|
64
|
+
|
|
65
|
+
## The goal loop
|
|
66
|
+
|
|
67
|
+
1. The human states what the business should do.
|
|
68
|
+
2. The CEO writes missions with acceptance criteria.
|
|
69
|
+
3. Each mission is delegated to the agent whose lane and capability classes fit.
|
|
70
|
+
4. Agents work their lane; risky/public/paid steps wait for approval.
|
|
71
|
+
5. The CEO reviews outcomes against the acceptance criteria and signs off, or
|
|
72
|
+
sends the mission back.
|
|
73
|
+
6. The human stays the only human in the loop - approve, steer, ship.
|
|
74
|
+
|
|
75
|
+
## Surfacing agents to users
|
|
76
|
+
|
|
77
|
+
Each agent reads cleanly as a profile: persona name, spirit, its capability
|
|
78
|
+
classes (its "skills"), and the service apps it manages. Give it a spirit/avatar
|
|
79
|
+
with `maggie-spirit` (shared 32-state motion vocabulary, per-agent accent) so
|
|
80
|
+
status (idle, working, deploying, success, blocked, secure) reads the same
|
|
81
|
+
across the whole team. Mark an agent "shipped" only when its product is actually
|
|
82
|
+
live.
|
|
83
|
+
|
|
84
|
+
## Checklist
|
|
85
|
+
|
|
86
|
+
- [ ] One lane per agent; no two agents own the same decision.
|
|
87
|
+
- [ ] Capability classes are `area.verb`, stable, and enumerable.
|
|
88
|
+
- [ ] A single orchestrator (CEO) owns decomposition and sign-off.
|
|
89
|
+
- [ ] Security veto and finance-is-explain-only are encoded, not assumed.
|
|
90
|
+
- [ ] Every agent has a risk ceiling; L2/L3 actions gate on human approval.
|
|
91
|
+
- [ ] Persona is inspiration, not impersonation of a real person.
|
|
92
|
+
- [ ] Each agent has a spirit/avatar from the shared 32-state base set.
|
|
@@ -90,6 +90,10 @@
|
|
|
90
90
|
"name": "maggie-social-share",
|
|
91
91
|
"description": "Turn approved AI CMO articles and content assets into platform-native social drafts, scheduled posts, and measured distribution. Use for Social Share planning, LinkedIn/Facebook copy, CTA mapping, calendars, approvals, publish retries, or social performance readback."
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"name": "maggie-spirit",
|
|
95
|
+
"description": "Generate an AI brand's flat-pixel spirit (sprite) system - a logo mark that animates through a fixed 32-state base set, and per-agent avatar faces animated through the same states. Use for agent/brand avatars, logo marks, sprite-sheet and sprite-animation assets, and a shared motion vocabulary across Core and Service agents."
|
|
96
|
+
},
|
|
93
97
|
{
|
|
94
98
|
"name": "maggie-template",
|
|
95
99
|
"description": "Discover, recommend, inspect, and apply verified Astro homepage templates from the Maggie marketplace. Use when a user wants to start a site from a selected template, compare available designs, or choose a visual direction before maggie-clone or maggie-design."
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maggie-deployment
|
|
3
3
|
description: Deploy and operate Maggie blog projects with Cloudflare Workers as the default target, while preserving an adapter boundary for VPS, GCP, and AWS.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.6.
|
|
5
|
+
version: 1.6.1
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Deployment
|
|
@@ -749,3 +749,23 @@ discovery, install-manifest writes, or file synchronization. For fixture-backed
|
|
|
749
749
|
browser QA, the host owns the isolated dev server and data source; document a
|
|
750
750
|
second port/process and fixture mode in the host runbook instead of making the
|
|
751
751
|
shared updater or deployment CLI start an unmanaged daemon.
|
|
752
|
+
|
|
753
|
+
## Static sites: build first, ship the build output only
|
|
754
|
+
|
|
755
|
+
A static-site deploy must run the framework build and ship **only** the build
|
|
756
|
+
output, never the source tree. A pipeline that copies the project directory and
|
|
757
|
+
serves it (for example `rsync webapp/ ...` then `python -m http.server`) exposes
|
|
758
|
+
the source and, with no `index.html` at the root, serves a directory listing
|
|
759
|
+
instead of the site.
|
|
760
|
+
|
|
761
|
+
The deploy step must therefore:
|
|
762
|
+
|
|
763
|
+
1. install and build in the project (`npm ci && npm run build`), then
|
|
764
|
+
2. ship only the build artifact (`dist/`, `build/`, `.output/public`, ...) to the
|
|
765
|
+
web root, and
|
|
766
|
+
3. verify the live root returns the rendered page (a `200` whose body contains
|
|
767
|
+
the expected `<title>`/`<h1>`), not an autoindex.
|
|
768
|
+
|
|
769
|
+
Treat "the root is a directory listing" or "source files (`package.json`,
|
|
770
|
+
`src/`) are reachable over HTTP" as a failed deploy. Prefer a build artifact
|
|
771
|
+
check in CI over trusting that a copy step shipped the right directory.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maggie-design
|
|
3
3
|
description: Design authorized interior pages, review rendered responsive layouts, or explicitly rebrand a packaged homepage/template. Use rebrand only with a named source brand and target brand.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 1.6.
|
|
5
|
+
version: 1.6.1
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maggie Design
|
|
@@ -853,3 +853,31 @@ without an explicit host contract. Decode HTML entities exactly once; unknown
|
|
|
853
853
|
or double-escaped entities stop the check. Optional empty attributes such as
|
|
854
854
|
`style=""` must be omitted. Codemods using compiler positions must anchor at
|
|
855
855
|
the opening `<tag` before slicing source text.
|
|
856
|
+
|
|
857
|
+
## Front-end pitfalls from in-place builds
|
|
858
|
+
|
|
859
|
+
### Runtime-injected DOM does not get scoped styles
|
|
860
|
+
|
|
861
|
+
Astro (and other scoped-style systems) add a per-component attribute
|
|
862
|
+
(`data-astro-cid-*`) only to nodes present in the template at build time. Nodes
|
|
863
|
+
you create at runtime with `innerHTML`/`createElement` never receive it, so
|
|
864
|
+
scoped `<style>` rules silently do not match them - a tab, card, or image built
|
|
865
|
+
in script renders unstyled while the identical server-rendered markup looks
|
|
866
|
+
correct. Style runtime-injected content with `<style is:global>` (scoped by an
|
|
867
|
+
id/class you control, e.g. `#my-modal .card { ... }`) or with inline styles.
|
|
868
|
+
Verify with rendered-browser evidence, not a static class scan: the static
|
|
869
|
+
selector exists, but the element it should match does not carry the scope.
|
|
870
|
+
|
|
871
|
+
### Extract DESIGN.md in-place from the project's own homepage
|
|
872
|
+
|
|
873
|
+
`maggie_design_system.py generate` builds a DESIGN.md from clone evidence, which
|
|
874
|
+
an in-place redesign does not have. When the design source of truth is the
|
|
875
|
+
project's existing homepage, author `DESIGN.md` directly from it: read the
|
|
876
|
+
`:root` tokens, fonts, composition, responsive and interaction rules from the
|
|
877
|
+
live page, write the eight required headings (`## Source and evidence`,
|
|
878
|
+
`## Design tokens`, `## Composition rules`, `## Responsive rules`,
|
|
879
|
+
`## Interaction rules`, `## Shared shell contract`, `## New page contract`,
|
|
880
|
+
`## Unknowns and approvals`), and include the `Generated by Maggie` marker line
|
|
881
|
+
so `maggie tool maggie_design_system.py validate DESIGN.md` passes. State in
|
|
882
|
+
`## Source and evidence` that it was extracted in-place with no external clone.
|
|
883
|
+
This unlocks `maggie design in-place` for a route without a clone run.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-spirit
|
|
3
|
+
description: Generate an AI brand's flat-pixel spirit (sprite) system - a logo mark that animates through a fixed 32-state base set, and per-agent avatar faces animated through the same states. Use for agent/brand avatars, logo marks, sprite-sheet and sprite-animation assets, and a shared motion vocabulary across Core and Service agents.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Spirit
|
|
9
|
+
|
|
10
|
+
Give an agent brand one flat-pixel visual language: a **logo mark** and a set of
|
|
11
|
+
**agent avatars** that all animate through the **same fixed catalog of 32
|
|
12
|
+
states** (power, work, comms, outcome, gov). Agents differ only by their
|
|
13
|
+
**motif/face** and their **accent** hue; the motion vocabulary, semantic palette
|
|
14
|
+
and status badges stay identical so a state reads the same everywhere.
|
|
15
|
+
|
|
16
|
+
Everything is deterministic (seeded by accent + persona) and self-contained
|
|
17
|
+
(Pillow only - no model calls, no per-image cost). It pairs with
|
|
18
|
+
[agent goal-led design](../../references/agent-goal-design.md): design the agent,
|
|
19
|
+
then give it a spirit.
|
|
20
|
+
|
|
21
|
+
Follow the [Maggie Memory Hook](../../references/memory-hook.md) at invocation
|
|
22
|
+
and completion.
|
|
23
|
+
|
|
24
|
+
## When to use
|
|
25
|
+
|
|
26
|
+
- You are standing up a multi-agent product (a C-suite of AI cofounders, a set
|
|
27
|
+
of Service Apps) and want a consistent avatar + logo + status-animation set.
|
|
28
|
+
- You need a logo mark that can show live state (idle, working, deploying,
|
|
29
|
+
success, warning, secure, ...) rather than a single static glyph.
|
|
30
|
+
- You need sprite sheets / animation strips + a `states.json` manifest to drive
|
|
31
|
+
a web UI (the CSS `steps()` technique), not a one-off illustration.
|
|
32
|
+
|
|
33
|
+
For page/layout design use `maggie-design`; for marketplace templates use
|
|
34
|
+
`maggie-marketplace`; this skill produces the brand's character assets only.
|
|
35
|
+
|
|
36
|
+
## The 32-state base set
|
|
37
|
+
|
|
38
|
+
Five groups, fixed across every agent so severity and status read the same:
|
|
39
|
+
|
|
40
|
+
- **power:** off, booting, idle, active, paused, offline
|
|
41
|
+
- **work:** thinking, loading, syncing, writing, streaming, queued, scanning, deploying
|
|
42
|
+
- **comms:** speaking, listening, notification, mention, recording
|
|
43
|
+
- **outcome:** success, warning, critical, blocked, error, retrying
|
|
44
|
+
- **gov:** awaiting, approved, rejected, secure, vulnerable, maintenance, celebrating
|
|
45
|
+
|
|
46
|
+
Each state carries its own frame count, fps, motion and optional badge; every
|
|
47
|
+
state animates (none is static). The per-state `[frames, fps]` are written to
|
|
48
|
+
`states.json` beside the images so a UI can play each strip exactly.
|
|
49
|
+
|
|
50
|
+
## CLI
|
|
51
|
+
|
|
52
|
+
Run through the bundled tool so it uses the current project as the working
|
|
53
|
+
directory:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# logo mark + 32-state animated base set (anim-*.png, mark-*.svg, sprite-sheet.png, states.json)
|
|
57
|
+
maggie tool maggie_spirit.py mark --out public/assets/brand --name oneman \
|
|
58
|
+
--accent "#ff1212" --title ONEMAN
|
|
59
|
+
|
|
60
|
+
# one agent avatar animated through all 32 states (+ stills + sheet + states.json)
|
|
61
|
+
maggie tool maggie_spirit.py agent --out public/assets/brand --name cto \
|
|
62
|
+
--accent "#3b82f6" --spec cto.persona.json
|
|
63
|
+
|
|
64
|
+
# a single still avatar face
|
|
65
|
+
maggie tool maggie_spirit.py avatar --out public/assets/brand/cto/face.png \
|
|
66
|
+
--accent "#3b82f6" --spec cto.persona.json
|
|
67
|
+
|
|
68
|
+
# the reference base set to copy and recolour: default mark + example cast + founder
|
|
69
|
+
maggie tool maggie_spirit.py demo --out public/assets/brand
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`persona.json` (all keys optional except implied by your design):
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"gender": "m",
|
|
77
|
+
"hair": "#2a2018",
|
|
78
|
+
"skin": "light | mid | tan | deep",
|
|
79
|
+
"hairstyle": "short|slick|spiky|buzz|longwavy|longstraight|longbob|ponytail",
|
|
80
|
+
"eyes": "round|wide|lash|happy",
|
|
81
|
+
"mouth": "flat|smile|smirk|lips|open",
|
|
82
|
+
"prop": "glasses|shades|cigar|headset|earbuds|monocle|pen|bandana",
|
|
83
|
+
"propcol": "#f2c75a",
|
|
84
|
+
"hoodie": true, "band": "#e23b3b", "mask": "#12201a", "specs": true
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Output contract
|
|
89
|
+
|
|
90
|
+
For each target (`<out>/<name>/`):
|
|
91
|
+
|
|
92
|
+
- `anim-<state>.png` - one horizontal strip per state (frames laid left to right).
|
|
93
|
+
- `still-<state>.png` - a representative still per state (agents only).
|
|
94
|
+
- `mark-<state>.svg` - a crisp SVG still per state (mark only).
|
|
95
|
+
- `sprite-sheet.png` - an overview contact sheet.
|
|
96
|
+
- `states.json` - `{ title, accent, N, scale, groups, states: { <state>: {group, frames, fps, motion, badge} } }`.
|
|
97
|
+
|
|
98
|
+
## Driving the animation in a UI
|
|
99
|
+
|
|
100
|
+
Flat-pixel, pixel-exact. Use CSS `steps()` with the per-state frame count, never
|
|
101
|
+
percentage-position stepping:
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
.sprite{
|
|
105
|
+
--fw: 48px; /* one frame = one cell */
|
|
106
|
+
width: var(--fw); height: var(--fw);
|
|
107
|
+
background: url(/assets/brand/cto/anim-active.png) no-repeat;
|
|
108
|
+
background-size: calc(var(--n) * var(--fw)) var(--fw);
|
|
109
|
+
image-rendering: pixelated;
|
|
110
|
+
animation: play calc(var(--n) / var(--fps) * 1s) steps(var(--n)) infinite;
|
|
111
|
+
}
|
|
112
|
+
@keyframes play { to { background-position-x: calc(-1 * var(--n) * var(--fw)); } }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Set `--n` and `--fps` per state from `states.json`. If you build the sprite
|
|
116
|
+
element with `innerHTML` in an Astro page, style it with `<style is:global>` -
|
|
117
|
+
Astro's scoped styles do not reach runtime-injected nodes (see `maggie-design`).
|
|
118
|
+
|
|
119
|
+
## Boundaries
|
|
120
|
+
|
|
121
|
+
- Deterministic and local: no LLM/image-model calls and no per-image billing.
|
|
122
|
+
Pillow is the only dependency.
|
|
123
|
+
- The mark is the brand's own; recolour via `--accent`, keep the semantic
|
|
124
|
+
palette (severity colours) fixed so status stays legible across agents.
|
|
125
|
+
- Do not hand-edit generated files; change the persona/accent and regenerate so
|
|
126
|
+
the whole set stays consistent.
|
|
127
|
+
- Store generated art under the host project's assets; keep source personas
|
|
128
|
+
(the `*.persona.json`) in the repo so the set is reproducible.
|
|
@@ -38,6 +38,11 @@ def project_root(args: argparse.Namespace) -> Path:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def store_for(root: Path) -> MaggieDashStore:
|
|
41
|
+
# ONEMAN forbids SQLite: when MAGGIEDASH_DATABASE_URL is set, back the store
|
|
42
|
+
# with Postgres. Otherwise fall back to the local SQLite dev store.
|
|
43
|
+
dsn = os.environ.get("MAGGIEDASH_DATABASE_URL")
|
|
44
|
+
if dsn:
|
|
45
|
+
return MaggieDashStore(dsn=dsn)
|
|
41
46
|
return MaggieDashStore(root / ".maggie" / "maggiedash.sqlite")
|
|
42
47
|
|
|
43
48
|
|
|
@@ -642,6 +647,9 @@ def command_recommend(args: argparse.Namespace) -> int:
|
|
|
642
647
|
def command_init(args: argparse.Namespace) -> int:
|
|
643
648
|
require_confirm(args)
|
|
644
649
|
root = project_root(args)
|
|
650
|
+
if str(getattr(args, "adapter", "")).endswith("postgres") and not os.environ.get("MAGGIEDASH_DATABASE_URL"):
|
|
651
|
+
print("POSTGRES_DSN_REQUIRED: adapter is postgres but MAGGIEDASH_DATABASE_URL is not set.", file=sys.stderr)
|
|
652
|
+
return 2
|
|
645
653
|
store = store_for(root)
|
|
646
654
|
try:
|
|
647
655
|
project = store.init_project(args.project_id, args.name, args.adapter, args.locale)
|
|
@@ -652,9 +660,9 @@ def command_init(args: argparse.Namespace) -> int:
|
|
|
652
660
|
"name": args.name,
|
|
653
661
|
"adapter": args.adapter,
|
|
654
662
|
"locale": args.locale,
|
|
655
|
-
"storage":
|
|
663
|
+
"storage": store.storage_label,
|
|
656
664
|
}, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
657
|
-
emit({"status": "initialized", "project": project, "contract": str(contract), "storage":
|
|
665
|
+
emit({"status": "initialized", "project": project, "contract": str(contract), "storage": store.storage_label})
|
|
658
666
|
return 0
|
|
659
667
|
finally:
|
|
660
668
|
store.close()
|
|
@@ -667,7 +675,7 @@ def command_status(args: argparse.Namespace) -> int:
|
|
|
667
675
|
try:
|
|
668
676
|
project = store.connection.execute("SELECT * FROM maggiedash_projects WHERE id=?", (project_id,)).fetchone()
|
|
669
677
|
documents = store.connection.execute("SELECT id, kind, title, slug, status, updated_at FROM maggiedash_documents WHERE project_id=? ORDER BY updated_at DESC", (project_id,)).fetchall()
|
|
670
|
-
emit({"status": "ready" if project else "not_initialized", "project": dict(project) if project else None, "documents": [dict(row) for row in documents], "storage":
|
|
678
|
+
emit({"status": "ready" if project else "not_initialized", "project": dict(project) if project else None, "documents": [dict(row) for row in documents], "storage": store.storage_label})
|
|
671
679
|
return 0 if project else 1
|
|
672
680
|
finally:
|
|
673
681
|
store.close()
|
|
@@ -680,7 +688,7 @@ def command_migrate(args: argparse.Namespace) -> int:
|
|
|
680
688
|
try:
|
|
681
689
|
store.connection.execute("PRAGMA user_version = 1")
|
|
682
690
|
store.connection.commit()
|
|
683
|
-
emit({"status": "migrated", "schemaVersion": "maggiedash-storage.v1", "storage":
|
|
691
|
+
emit({"status": "migrated", "schemaVersion": "maggiedash-storage.v1", "storage": store.storage_label})
|
|
684
692
|
return 0
|
|
685
693
|
finally:
|
|
686
694
|
store.close()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""maggie_spirit — generate an AI brand's pixel spirit (sprite) system.
|
|
3
|
+
|
|
4
|
+
One flat-pixel visual language for an agent brand:
|
|
5
|
+
* a logo "mark" that animates through a fixed 32-state base set, and
|
|
6
|
+
* per-agent avatar faces that animate through the same 32 states.
|
|
7
|
+
|
|
8
|
+
Everything is deterministic (seeded by accent + persona) and self-contained
|
|
9
|
+
(Pillow only). See skills/maggie-spirit/SKILL.md for the workflow.
|
|
10
|
+
|
|
11
|
+
Subcommands:
|
|
12
|
+
mark --out DIR [--name mark] [--accent #ff1212] [--title ONEMAN] [--scale 9]
|
|
13
|
+
Logo mark + 32-state animated base set + sprite sheet + states.json.
|
|
14
|
+
agent --out DIR --name cto --accent #3b82f6 --spec persona.json
|
|
15
|
+
One agent avatar animated through all 32 states (+ sheet + states.json).
|
|
16
|
+
avatar --out FILE.png --accent #3b82f6 --spec persona.json [--scale 10]
|
|
17
|
+
A single still avatar face (no animation).
|
|
18
|
+
demo --out DIR
|
|
19
|
+
The reference spirit base set: the default mark + 8 example agents +
|
|
20
|
+
founder — a starter kit to copy and recolour.
|
|
21
|
+
|
|
22
|
+
persona.json (agent/avatar), all keys optional except noted:
|
|
23
|
+
{"gender":"m|f","hair":"#2a2018","skin":"light|mid|tan|deep",
|
|
24
|
+
"hairstyle":"short|slick|spiky|buzz|longwavy|longstraight|longbob|ponytail",
|
|
25
|
+
"eyes":"round|wide|lash|happy","mouth":"flat|smile|smirk|lips|open",
|
|
26
|
+
"prop":"glasses|shades|cigar|headset|earbuds|monocle|pen|bandana",
|
|
27
|
+
"propcol":"#f2c75a","hoodie":true,"band":"#e23b3b","mask":"#12201a","specs":true}
|
|
28
|
+
"""
|
|
29
|
+
import argparse, json, os, sys
|
|
30
|
+
|
|
31
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
32
|
+
RUNTIME = os.path.normpath(os.path.join(HERE, "..", "runtime"))
|
|
33
|
+
if RUNTIME not in sys.path:
|
|
34
|
+
sys.path.insert(0, RUNTIME)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import spirit_base as B
|
|
38
|
+
import spirit_face as F
|
|
39
|
+
import spirit_agent as A
|
|
40
|
+
except ImportError as e: # pragma: no cover
|
|
41
|
+
sys.stderr.write(
|
|
42
|
+
"maggie_spirit needs Pillow and the spirit_* runtime modules.\n"
|
|
43
|
+
f" {e}\n pip install pillow\n")
|
|
44
|
+
raise SystemExit(2)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_spec(path):
|
|
48
|
+
if not path:
|
|
49
|
+
return {}
|
|
50
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
51
|
+
spec = json.load(fh)
|
|
52
|
+
if not isinstance(spec, dict):
|
|
53
|
+
raise SystemExit("persona spec must be a JSON object")
|
|
54
|
+
return spec
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_mark(a):
|
|
58
|
+
B.OUT_BASE = os.path.abspath(a.out)
|
|
59
|
+
os.makedirs(B.OUT_BASE, exist_ok=True)
|
|
60
|
+
meta = B.generate(B.Motif.oneman(), accent=a.accent, out=a.name,
|
|
61
|
+
title=a.title, scale=a.scale)
|
|
62
|
+
print(f"{a.title}: {len(meta['states'])} states -> {os.path.join(a.out, a.name)}/")
|
|
63
|
+
print("groups:", ", ".join(meta.get("groups", [])))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _persona(spec):
|
|
67
|
+
gender = spec.get("gender", "m")
|
|
68
|
+
params = {k: v for k, v in spec.items() if k != "gender"}
|
|
69
|
+
params.setdefault("hair", "#1c1c22")
|
|
70
|
+
params.setdefault("skin", "mid")
|
|
71
|
+
return gender, params
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_agent(a):
|
|
75
|
+
A.OUT_BASE = os.path.abspath(a.out)
|
|
76
|
+
os.makedirs(A.OUT_BASE, exist_ok=True)
|
|
77
|
+
gender, params = _persona(_load_spec(a.spec))
|
|
78
|
+
meta = A.gen_agent(a.name.lower(), a.accent, gender, params, scale=a.scale)
|
|
79
|
+
print(f"{a.name} {a.accent} {gender}: {len(meta['states'])} states -> "
|
|
80
|
+
f"{os.path.join(a.out, a.name.lower())}/")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cmd_avatar(a):
|
|
84
|
+
gender, params = _persona(_load_spec(a.spec))
|
|
85
|
+
img = F.render(F.face(a.accent, gender=gender, **params), a.scale)
|
|
86
|
+
out = os.path.abspath(a.out)
|
|
87
|
+
os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
|
|
88
|
+
img.save(out)
|
|
89
|
+
print(f"avatar -> {a.out} ({img.size[0]}x{img.size[1]})")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def cmd_demo(a):
|
|
93
|
+
base = os.path.abspath(a.out)
|
|
94
|
+
os.makedirs(base, exist_ok=True)
|
|
95
|
+
B.OUT_BASE = base
|
|
96
|
+
A.OUT_BASE = base
|
|
97
|
+
B.generate(B.Motif.oneman(), accent="#ff1212", out="oneman", title="ONEMAN")
|
|
98
|
+
made = ["oneman (mark)"]
|
|
99
|
+
for role, acc, g, p in list(F.CAST) + [F.FOUNDER]:
|
|
100
|
+
A.gen_agent(role.lower(), acc, g, p)
|
|
101
|
+
made.append(role.lower())
|
|
102
|
+
print(f"spirit base set -> {a.out}/")
|
|
103
|
+
print("generated:", ", ".join(made))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main(argv=None):
|
|
107
|
+
ap = argparse.ArgumentParser(prog="maggie_spirit", description=__doc__,
|
|
108
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
109
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
110
|
+
|
|
111
|
+
m = sub.add_parser("mark", help="logo mark + 32-state animated base set")
|
|
112
|
+
m.add_argument("--out", required=True)
|
|
113
|
+
m.add_argument("--name", default="mark")
|
|
114
|
+
m.add_argument("--accent", default="#ff1212")
|
|
115
|
+
m.add_argument("--title", default="ONEMAN")
|
|
116
|
+
m.add_argument("--scale", type=int, default=9)
|
|
117
|
+
m.set_defaults(fn=cmd_mark)
|
|
118
|
+
|
|
119
|
+
ag = sub.add_parser("agent", help="one agent avatar through all 32 states")
|
|
120
|
+
ag.add_argument("--out", required=True)
|
|
121
|
+
ag.add_argument("--name", required=True)
|
|
122
|
+
ag.add_argument("--accent", required=True)
|
|
123
|
+
ag.add_argument("--spec", help="persona.json")
|
|
124
|
+
ag.add_argument("--scale", type=int, default=8)
|
|
125
|
+
ag.set_defaults(fn=cmd_agent)
|
|
126
|
+
|
|
127
|
+
av = sub.add_parser("avatar", help="a single still avatar face")
|
|
128
|
+
av.add_argument("--out", required=True)
|
|
129
|
+
av.add_argument("--accent", required=True)
|
|
130
|
+
av.add_argument("--spec", help="persona.json")
|
|
131
|
+
av.add_argument("--scale", type=int, default=10)
|
|
132
|
+
av.set_defaults(fn=cmd_avatar)
|
|
133
|
+
|
|
134
|
+
d = sub.add_parser("demo", help="reference spirit base set (mark + examples)")
|
|
135
|
+
d.add_argument("--out", required=True)
|
|
136
|
+
d.set_defaults(fn=cmd_demo)
|
|
137
|
+
|
|
138
|
+
a = ap.parse_args(argv)
|
|
139
|
+
a.fn(a)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|