@aibyzero/byz 0.1.1 → 0.1.3
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/CHANGELOG.md +20 -0
- package/README.md +34 -24
- package/dist/cli.js +22 -14
- package/dist/core/export-html/template.css +1066 -0
- package/dist/core/export-html/template.html +55 -0
- package/dist/core/export-html/template.js +1864 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +78 -0
- package/dist/fast.js +64 -0
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/theme/dark.json +90 -0
- package/dist/modes/interactive/theme/light.json +89 -0
- package/dist/modes/interactive/theme/theme-schema.json +352 -0
- package/dist/runtime/bundle/chunks/{chunk-CCRJHU72.js → chunk-QY7DJQRI.js} +2 -2
- package/dist/runtime/bundle/chunks/github-copilot.js +1 -1
- package/dist/runtime/bundle/cli.js +1 -1
- package/dist/runtime/bundle/index.js +1 -1
- package/dist/runtime/bundle/rpc-entry.js +1 -1
- package/dist/runtime/core/resource-loader.d.ts +1 -0
- package/dist/runtime/core/resource-loader.d.ts.map +1 -1
- package/dist/runtime/core/resource-loader.js +8 -3
- package/dist/runtime/core/resource-loader.js.map +1 -1
- package/dist/workflows.js +4 -131
- package/package.json +2 -1
- package/workflows/cm-plugin/LICENSE +21 -0
- package/workflows/cm-plugin/README.md +195 -0
- package/workflows/cm-plugin/VERSION +1 -0
- package/workflows/cm-plugin/agents/cm-plugin-backend-agent.md +34 -0
- package/workflows/cm-plugin/agents/cm-plugin-extension-agent.md +35 -0
- package/workflows/cm-plugin/agents/cm-plugin-ui-agent.md +34 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N1-init.md +60 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N2-enter-feature.md +51 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N3-execute-task.md +32 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N4-review.md +58 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N5-mark-done.md +80 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N6-qa-eval.md +69 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N7-context.md +23 -0
- package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N8-finish.md +61 -0
- package/workflows/cm-plugin/commands/cm-plugin-prd-modes/brownfield.md +13 -0
- package/workflows/cm-plugin/commands/cm-plugin-prd-modes/change-mode.md +130 -0
- package/workflows/cm-plugin/commands/cm-plugin-prd-modes/greenfield.md +82 -0
- package/workflows/cm-plugin/commands/cm-plugin:ai.md +75 -0
- package/workflows/cm-plugin/commands/cm-plugin:check.md +66 -0
- package/workflows/cm-plugin/commands/cm-plugin:fix.md +100 -0
- package/workflows/cm-plugin/commands/cm-plugin:idea.md +29 -0
- package/workflows/cm-plugin/commands/cm-plugin:init.md +145 -0
- package/workflows/cm-plugin/commands/cm-plugin:prd.md +403 -0
- package/workflows/cm-plugin/commands/cm-plugin:refactor.md +147 -0
- package/workflows/cm-plugin/commands/cm-plugin:rewrite.md +90 -0
- package/workflows/cm-plugin/commands/cm-plugin:scout.md +202 -0
- package/workflows/cm-plugin/docs//346/265/213/350/257/225/346/214/207/345/274/225-/345/277/253/351/200/237/344/270/212/346/211/213.md +189 -0
- package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/README.md +17 -0
- package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.excalidraw +3650 -0
- package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.mp4 +0 -0
- package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.png +0 -0
- package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.spec.json +223 -0
- package/workflows/cm-plugin/package.json +33 -0
- package/workflows/cm-plugin/skills/cm-plugin-backend-engineer/SKILL.md +100 -0
- package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/NOTICE.md +14 -0
- package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/SKILL.md +120 -0
- package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/references/cws-ci-cd.md +139 -0
- package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/references/cws-submission-checklist.md +87 -0
- package/workflows/cm-plugin/skills/cm-plugin-doc-syncer/SKILL.md +149 -0
- package/workflows/cm-plugin/skills/cm-plugin-extension-engineer/SKILL.md +105 -0
- package/workflows/cm-plugin/skills/cm-plugin-product-manager/SKILL.md +83 -0
- package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/NOTICE.md +14 -0
- package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/SKILL.md +134 -0
- package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/references/cws-scan-checklist.md +136 -0
- package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/references/cws-violation-codes.md +68 -0
- package/workflows/cm-plugin/skills/cm-plugin-ui-engineer/SKILL.md +94 -0
- package/workflows/cm-plugin/skills/codebase-context/SKILL.md +489 -0
- package/workflows/cm-plugin/skills/darwin-skill/NOTICE.md +24 -0
- package/workflows/cm-plugin/skills/darwin-skill/README.md +272 -0
- package/workflows/cm-plugin/skills/darwin-skill/SKILL.md +492 -0
- package/workflows/cm-plugin/skills/darwin-skill/references/runtime-neutrality.md +68 -0
- package/workflows/cm-plugin/skills/darwin-skill/references/skilllens-evidence.md +142 -0
- package/workflows/cm-plugin/skills/darwin-skill/scripts/screenshot.mjs +71 -0
- package/workflows/cm-plugin/skills/darwin-skill/templates/result-card-dark.html +698 -0
- package/workflows/cm-plugin/skills/darwin-skill/templates/result-card-white.html +444 -0
- package/workflows/cm-plugin/skills/darwin-skill/templates/result-card.html +616 -0
- package/workflows/cm-plugin/skills/idea-to-prd/SKILL.md +289 -0
- package/workflows/cm-plugin/skills/idea-to-prd/references/domains/trading.md +97 -0
- package/workflows/cm-plugin/skills/idea-to-prd/references/example-prd.md +92 -0
- package/workflows/cm-plugin/templates/arch-reference.md +55 -0
- package/workflows/cm-plugin/templates/auto-update/cm-announce.sh +19 -0
- package/workflows/cm-plugin/templates/auto-update/cm-update.sh +251 -0
- package/workflows/cm-plugin/templates/dashboard/dashboard.html +153 -0
- package/workflows/cm-plugin/templates/dashboard/serve.sh +10 -0
- package/workflows/cm-plugin/templates/e2e/extension-harness.ts +110 -0
- package/workflows/cm-plugin/templates/e2e/smoke.spec.example.ts +51 -0
- package/workflows/cm-plugin/templates/hooks/pre-commit-cm-task-check +33 -0
- package/workflows/cm-plugin/templates/pixel/cm-pixel.html +388 -0
- package/workflows/cm-plugin/templates/pixel/cm-pixel.sh +212 -0
- package/workflows/cm-plugin/templates/pixel/dev/README.md +20 -0
- package/workflows/cm-plugin/templates/pixel/dev/atlas-preview.png +0 -0
- package/workflows/cm-plugin/templates/pixel/dev/build.py +55 -0
- package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeChar_transparent.png +0 -0
- package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeCity_tilemap.png +0 -0
- package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeIndoor_transparent.png +0 -0
- package/workflows/cm-plugin/templates/pixel/dev/template.html +388 -0
- package/workflows/cm-plugin/templates/pixel/serve.sh +12 -0
- package/workflows/cm-plugin/templates/refactor/cm-refactor-denies.json +14 -0
- package/workflows/cm-plugin/templates/rules/backend-api.md +36 -0
- package/workflows/cm-plugin/templates/rules/chrome-extension.md +53 -0
- package/workflows/cm-plugin/templates/rules/coding-style.md +44 -0
- package/workflows/cm-plugin/templates/rules/frontend.md +32 -0
- package/workflows/cm-plugin/templates/rules/git-workflow.md +25 -0
- package/workflows/cm-plugin/templates/rules/security.md +31 -0
- package/workflows/cm-plugin/templates/rules/testing.md +36 -0
- package/workflows/cm-plugin/templates/scripts/cm-plugin-codex.sh +52 -0
- package/workflows/cm-plugin/templates/scripts/cm-plugin-log.sh +41 -0
- package/workflows/cm-plugin/templates/scripts/cm-plugin-preflight.sh +60 -0
- package/workflows/cm-plugin/templates/statusline/cm-plugin-statusline.sh +73 -0
- package/workflows.lock.json +4 -3
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>cm 工作流 · 像素办公室</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
9
|
+
body { background:#131120; color:#e8e2d0; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
10
|
+
min-height:100vh; display:flex; flex-direction:column; align-items:center; padding:18px 12px; }
|
|
11
|
+
#wrap { width:min(980px, 100%); }
|
|
12
|
+
#hud { display:flex; align-items:center; gap:12px; padding:8px 2px; font-size:14px; }
|
|
13
|
+
#logo { background:#f0b03c; color:#1c1a14; font-weight:800; padding:3px 9px; border-radius:3px;
|
|
14
|
+
letter-spacing:2px; box-shadow:0 3px 0 #8a6210; }
|
|
15
|
+
#phase { background:#232033; padding:4px 12px; border-radius:3px; letter-spacing:1px; font-weight:700; }
|
|
16
|
+
#phase.paused { background:#f5d95c; color:#1c1a14; }
|
|
17
|
+
#phase.stale { background:#d25050; color:#fff; }
|
|
18
|
+
#task { color:#9a93b8; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; }
|
|
19
|
+
#coins { color:#f0b03c; font-weight:700; }
|
|
20
|
+
#stage { position:relative; border:4px solid #2a2438; border-radius:8px; overflow:hidden;
|
|
21
|
+
box-shadow:0 18px 50px rgba(0,0,0,.55), inset 0 0 0 2px #0d0b16; background:#0d0b16; }
|
|
22
|
+
canvas { display:block; width:100%; image-rendering:pixelated; image-rendering:crisp-edges; }
|
|
23
|
+
#bubble { position:absolute; transform:translate(-50%,-115%); max-width:380px; padding:8px 12px;
|
|
24
|
+
background:#faf6ec; color:#1c1a14; border:2px solid #1c1a14; border-radius:6px;
|
|
25
|
+
font-size:13px; line-height:1.5; display:none; box-shadow:3px 4px 0 rgba(0,0,0,.4); z-index:2; }
|
|
26
|
+
#bubble.paused { background:#f5d95c; transform:translate(-12%,-115%); }
|
|
27
|
+
#bubble.paused:after { left:12%; }
|
|
28
|
+
#bubble:after { content:""; position:absolute; left:50%; bottom:-10px; margin-left:-5px;
|
|
29
|
+
border:5px solid transparent; border-top-color:#1c1a14; }
|
|
30
|
+
#note { position:absolute; right:14px; bottom:16px; width:250px; display:none;
|
|
31
|
+
background:#f7ecc0; color:#4a3c14; border:2px solid #c8b478; padding:9px 11px;
|
|
32
|
+
font-size:12px; line-height:1.65; white-space:pre-wrap; transform:rotate(-1.6deg);
|
|
33
|
+
box-shadow:4px 5px 0 rgba(0,0,0,.35); border-radius:2px; }
|
|
34
|
+
#note b { color:#8a6210; }
|
|
35
|
+
#barrow { display:flex; align-items:center; gap:10px; margin-top:10px; }
|
|
36
|
+
#bar { flex:1; height:16px; background:#232033; border:2px solid #2a2438; border-radius:4px; overflow:hidden; }
|
|
37
|
+
#fill { height:100%; width:0; transition:width .6s;
|
|
38
|
+
background:repeating-linear-gradient(45deg,#f0b03c 0 10px,#d99a28 10px 20px); }
|
|
39
|
+
#count { font-size:13px; color:#f0b03c; min-width:44px; text-align:right; font-weight:700; }
|
|
40
|
+
#hint { margin-top:8px; font-size:12px; color:#6f6890; }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<div id="wrap">
|
|
45
|
+
<div id="hud">
|
|
46
|
+
<span id="logo">CM</span><span id="phase">连接中…</span>
|
|
47
|
+
<span id="task"></span><span id="coins"></span>
|
|
48
|
+
</div>
|
|
49
|
+
<div id="stage">
|
|
50
|
+
<canvas id="cv" width="744" height="420"></canvas>
|
|
51
|
+
<div id="bubble"></div>
|
|
52
|
+
<div id="note"></div>
|
|
53
|
+
</div>
|
|
54
|
+
<div id="barrow"><div id="bar"><div id="fill"></div></div><span id="count"></span></div>
|
|
55
|
+
<div id="hint"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<script>
|
|
58
|
+
const ATLAS = "data:image/png;base64,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";
|
|
59
|
+
const T = {"wall": [0, 0], "wall2": [1, 0], "wallB": [2, 0], "flr": [3, 0], "flr2": [4, 0], "flr3": [5, 0], "desk": [6, 0], "chairL": [7, 0], "chairR": [0, 1], "mon": [1, 1], "mon2": [2, 1], "sofaL": [3, 1], "sofaM": [4, 1], "sofaR": [5, 1], "rugL": [6, 1], "rugM": [7, 1], "rugM2": [0, 2], "rugR": [1, 2], "plant": [2, 2], "plant2": [3, 2], "art1": [4, 2], "art2": [5, 2], "art3": [6, 2], "gold": [7, 2], "mirror": [0, 3], "shelfA": [1, 3], "shelfB": [2, 3], "dresser": [3, 3], "vend": [4, 3], "fridge": [5, 3], "bin": [6, 3], "crate": [7, 3], "lamp": [0, 4], "lamp2": [1, 4], "cntL": [2, 4], "cntM": [3, 4], "cntR": [4, 4], "brick": [5, 4], "brick2": [6, 4], "wood": [7, 4], "wood2": [0, 5], "wood3": [1, 5], "rgA": [2, 5], "rgB": [3, 5], "rgC": [4, 5], "rgD": [5, 5], "chairF": [6, 5], "tblO1": [7, 5], "tblO2": [0, 6], "worker": [1, 6], "codex": [2, 6], "sleep": [3, 6], "celA": [4, 6], "celB": [5, 6], "celC": [6, 6]};
|
|
60
|
+
const IMG = new Image(); let loaded = false;
|
|
61
|
+
IMG.onload = () => loaded = true; IMG.src = ATLAS;
|
|
62
|
+
|
|
63
|
+
const SKY = {
|
|
64
|
+
dawn:["#c98d78","#dba98a","#e8c49a"], day:["#7fa8c8","#93b9d4","#a9cbdd"],
|
|
65
|
+
dusk:["#8a5f7a","#b07a6a","#d0996a"], night:["#242138","#2e2a48","#3a3560"],
|
|
66
|
+
};
|
|
67
|
+
const TINT = { dawn:"rgba(230,150,100,.09)", day:"rgba(255,180,90,.05)",
|
|
68
|
+
dusk:"rgba(210,120,70,.12)", night:"rgba(30,34,90,.17)" };
|
|
69
|
+
const NODES = ["N1","N2","N3","N4","N5","N6","N7","N8"];
|
|
70
|
+
const PHASE = { N1:"启动准备", N2:"规划任务", N3:"开发中", N4:"检查代码",
|
|
71
|
+
N5:"保存进度", N6:"质量检测", N7:"整理现场", N8:"收尾总结" };
|
|
72
|
+
const ZONE = i => i<2 ? "dawn" : i<5 ? "day" : i<7 ? "dusk" : "night";
|
|
73
|
+
const PROPS = { 0:"plant2", 1:"crate", 2:"bin", 4:"dresser", 5:"fridge", 6:"shelfB", 7:"plant" };
|
|
74
|
+
|
|
75
|
+
const cv = document.getElementById("cv"), cx = cv.getContext("2d");
|
|
76
|
+
cx.imageSmoothingEnabled = false;
|
|
77
|
+
const W = cv.width, H = cv.height, TOPW = 48; // 顶墙高度
|
|
78
|
+
// 工位:两排四列,像真办公室
|
|
79
|
+
const POD = []; for (let i=0;i<8;i++)
|
|
80
|
+
POD.push({ x: 128 + (i%4)*162, y: 168 + ((i/4)|0)*150 });
|
|
81
|
+
const BENCH = { x:20, y:252, cx:44 }; // 等候长椅(左墙)
|
|
82
|
+
const LOUNGE = { x:W/2, y:252 }; // 中央过道=休息区
|
|
83
|
+
const seed = n => { let x = Math.sin(n*127.1)*43758.5; return x - Math.floor(x); };
|
|
84
|
+
|
|
85
|
+
function rect(x,y,w,h,c){ cx.fillStyle = c; cx.fillRect(x|0, y|0, w, h); }
|
|
86
|
+
function tile(n,dx,dy,z=3){ const [c,r] = T[n];
|
|
87
|
+
cx.drawImage(IMG, c*16, r*16, 16, 16, dx|0, dy|0, 16*z, 16*z); }
|
|
88
|
+
function shadow(x,y,rx,ry,a=.26){ cx.fillStyle = "rgba(12,8,20,"+a+")";
|
|
89
|
+
cx.beginPath(); cx.ellipse(x,y,rx,ry,0,0,7); cx.fill(); }
|
|
90
|
+
|
|
91
|
+
let cur = { node:"N1", state:"running", detail:"", feature:"", task:"" };
|
|
92
|
+
let mode = "wait", staleRun = false, firstSnap = true;
|
|
93
|
+
let wk = { x:POD[0].x-58, y:POD[0].y+18 }; // 小人(2D 自由走动)
|
|
94
|
+
let frame = 0, confetti = [];
|
|
95
|
+
|
|
96
|
+
// ---------- 房间(俯视) ----------
|
|
97
|
+
function room(zone) {
|
|
98
|
+
for (let r=0; r<Math.ceil((H-TOPW)/48); r++) // 暖木地板
|
|
99
|
+
for (let c=0; c<Math.ceil(W/48); c++)
|
|
100
|
+
tile(seed(c*3+r*11)>.62 ? "wood2" : (seed(c*5+r)>.85 ? "wood3" : "wood"), c*48, TOPW + r*48);
|
|
101
|
+
for (let c=0; c<Math.ceil(W/48); c++) tile("brick", c*48, 0); // 红砖顶墙
|
|
102
|
+
rect(0,0,W,6,"#171310");
|
|
103
|
+
rect(0,TOPW-7,W,7,"#4a3626"); rect(0,TOPW-7,W,2,"#5f4630"); // 木梁压边
|
|
104
|
+
rect(0,TOPW,W,5,"rgba(12,8,20,.35)"); // 墙脚阴影
|
|
105
|
+
rect(0,TOPW,4,H-TOPW,"rgba(12,8,20,.30)"); // 左右墙沿
|
|
106
|
+
rect(W-4,TOPW,4,H-TOPW,"rgba(12,8,20,.30)");
|
|
107
|
+
const p = SKY[zone]; // 4 扇窗,窗外=当前进度时辰
|
|
108
|
+
for (let i=0;i<4;i++) {
|
|
109
|
+
const x = 96 + i*168, y = 7, w = 52, h = 32;
|
|
110
|
+
rect(x-3,y-3,w+6,h+6,"#241f1a");
|
|
111
|
+
for (let b=0;b<3;b++) rect(x, y+b*h/3, w, h/3+1, p[b]);
|
|
112
|
+
if (zone==="day" && i===1) { rect(x+34,y+5,9,9,"#f5e8b0"); }
|
|
113
|
+
if (zone==="dawn" && i===0) { rect(x+8,y+h-12,10,5,"#f0c890"); }
|
|
114
|
+
if (zone==="dusk" && i===2) { rect(x+30,y+h-14,11,6,"#e8a060"); }
|
|
115
|
+
if (zone==="night") { if (i===1) { rect(x+34,y+5,8,8,"#e8e2d0");
|
|
116
|
+
rect(x+35,y+6,2,2,"#c8c2b0"); rect(x+38,y+9,2,2,"#c8c2b0"); }
|
|
117
|
+
for (let s2=0;s2<3;s2++) if ((frame>>4)%3 !== s2)
|
|
118
|
+
rect(x+4+seed(s2+i)*44, y+3+seed(s2+9)*24, 2,2,"#e8e2d0"); }
|
|
119
|
+
if (zone==="day" || zone==="dawn") {
|
|
120
|
+
cx.save(); cx.beginPath(); cx.rect(x,y,w,h); cx.clip();
|
|
121
|
+
const cxp = x + ((frame*0.22 + i*53) % (w+30)) - 15;
|
|
122
|
+
rect(cxp, y+6+(i%2)*8, 14, 4, "rgba(250,246,236,.85)"); cx.restore(); }
|
|
123
|
+
rect(x-3, y+h/2-1, w+6, 2, "#241f1a");
|
|
124
|
+
if (i%2===0) tile("plant2", x+w-6, y+h-8, 1); // 窗台盆栽
|
|
125
|
+
else tile("plant", x-12, y+h-8, 1);
|
|
126
|
+
}
|
|
127
|
+
tile("art1", 40, 10, 2); tile("gold", 660, 8, 2); // 顶墙挂画
|
|
128
|
+
const ckx = 24, cky = 12; // 挂钟
|
|
129
|
+
rect(ckx-2,cky-2,16,16,"#241f1a"); rect(ckx,cky,12,12,"#e8e2d0");
|
|
130
|
+
rect(ckx+5,cky+2,2,5,"#241f1a"); rect(ckx+6,cky+6,4,2,"#241f1a");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------- 深度排序渲染(y 小在后,y 大在前=离观众近) ----------
|
|
134
|
+
function drawSorted(ents) {
|
|
135
|
+
ents.sort((a,b) => a.by - b.by);
|
|
136
|
+
for (const e of ents) e.f();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function podEnts(ents, idx, done) {
|
|
140
|
+
for (let i=0;i<8;i++) {
|
|
141
|
+
const P = POD[i], x = P.x, y = P.y,
|
|
142
|
+
active = i===idx && !done && mode==="run", passed = done || i<idx;
|
|
143
|
+
ents.push({ by: y+30, f: () => {
|
|
144
|
+
tile("rgA", x-32, y-14, 2); tile("rgB", x, y-14, 2); // 奶油地垫
|
|
145
|
+
tile("rgC", x-32, y+18, 2); tile("rgD", x, y+18, 2);
|
|
146
|
+
shadow(x, y+30, 30, 6);
|
|
147
|
+
tile("desk", x-24, y-18);
|
|
148
|
+
tile("lamp2", x+13, y-30, 1.1); // 桌灯
|
|
149
|
+
tile(active ? "mon" : "mon2", x-14, y-38, 1.75);
|
|
150
|
+
if (passed) { rect(x-11, y-35, 22, 17, "rgba(80,170,90,.85)");
|
|
151
|
+
cx.fillStyle="#eaf6ea"; cx.font="bold 11px monospace"; cx.textAlign="center";
|
|
152
|
+
cx.fillText("✓", x, y-22); }
|
|
153
|
+
else if (!active) rect(x-11, y-35, 22, 17, "rgba(24,20,34,.88)");
|
|
154
|
+
else if ((frame>>4)%4===0) rect(x-11, y-35, 22, 17, "rgba(159,216,240,.25)");
|
|
155
|
+
tile("chairF", x-14, y+16, 1.75); // 桌前椅(背对观众)
|
|
156
|
+
}});
|
|
157
|
+
if (i===3) ents.push({ by: P.y+26, f: () => { // Codex 审查员常驻 N4
|
|
158
|
+
shadow(P.x+44, P.y+26, 13, 4); tile("codex", P.x+22, P.y-22); }});
|
|
159
|
+
else if (i===5) ents.push({ by: P.y+28, f: () => { // 服务器机架(CI/测试机)
|
|
160
|
+
shadow(P.x+46, P.y+28, 13, 4);
|
|
161
|
+
tile("mon2", P.x+30, P.y-36, 2); tile("mon2", P.x+30, P.y-4, 2);
|
|
162
|
+
for (let k=0;k<4;k++) if ((frame>>3)%4!==k)
|
|
163
|
+
rect(P.x+36+k*6, P.y-30+((k%2)*32), 3, 3, k%2?"#7fdc6a":"#63b8b0"); }});
|
|
164
|
+
else if (PROPS[i]) ents.push({ by: P.y+28, f: () => {
|
|
165
|
+
shadow(P.x+46, P.y+28, 13, 4); tile(PROPS[i], P.x+30, P.y-4, 2); }});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function furnitureEnts(ents) {
|
|
170
|
+
ents.push({ by: 96, f: () => { // 咖啡台(靠顶墙)
|
|
171
|
+
shadow(608, 98, 52, 6);
|
|
172
|
+
tile("cntL", 560, 64, 2); tile("cntM", 592, 64, 2); tile("cntR", 624, 64, 2); }});
|
|
173
|
+
ents.push({ by: 98, f: () => { shadow(40, 98, 20, 5); tile("shelfA", 16, 56, 2.5); }}); // 书架
|
|
174
|
+
ents.push({ by: BENCH.y+34, f: () => { // 等候区:售货机+长椅
|
|
175
|
+
shadow(36, BENCH.y-14, 18, 5); tile("vend", 16, BENCH.y-62, 2);
|
|
176
|
+
shadow(BENCH.cx+22, BENCH.y+32, 26, 5);
|
|
177
|
+
tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
|
|
178
|
+
ents.push({ by: H-8, f: () => { // 角落绿植
|
|
179
|
+
tile("plant", 12, H-56, 2); tile("plant2", W-46, H-58, 2); }});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function person(n, x, y, bob) { // x,y = 脚底中心
|
|
183
|
+
return { by: y, f: () => { shadow(x, y+2, 14, 4, .32);
|
|
184
|
+
tile(n, x-24, y-46 + (bob ? ((frame>>3)%2) : 0)); } };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function lighting(idx, paused, done, zone) {
|
|
188
|
+
const lit = [];
|
|
189
|
+
if (mode==="run" && !done && !paused)
|
|
190
|
+
lit.push({x:POD[idx].x, y:POD[idx].y-10, c: staleRun ? "255,95,80" : "255,205,120", a:.26});
|
|
191
|
+
if (paused) lit.push({x:BENCH.cx+16, y:BENCH.y-10, c:"255,205,120", a:.24});
|
|
192
|
+
if (done || mode==="wait") lit.push({x:LOUNGE.x, y:LOUNGE.y, c:"255,205,120", a:.10});
|
|
193
|
+
cx.globalCompositeOperation = "lighter";
|
|
194
|
+
for (const L of lit) {
|
|
195
|
+
const g = cx.createRadialGradient(L.x,L.y,10, L.x,L.y,130);
|
|
196
|
+
g.addColorStop(0,"rgba("+L.c+","+L.a+")"); g.addColorStop(1,"rgba("+L.c+",0)");
|
|
197
|
+
cx.fillStyle = g; cx.fillRect(L.x-130, L.y-130, 260, 260);
|
|
198
|
+
}
|
|
199
|
+
cx.globalCompositeOperation = "source-over";
|
|
200
|
+
rect(0,0,W,H, TINT[zone]); // 时辰环境色
|
|
201
|
+
const v = cx.createRadialGradient(W/2, H*.44, H*.42, W/2, H*.52, W*.62);
|
|
202
|
+
v.addColorStop(0,"rgba(8,6,18,0)"); v.addColorStop(1,"rgba(8,6,18,.48)");
|
|
203
|
+
cx.fillStyle = v; cx.fillRect(0,0,W,H);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function labels(idx, done) {
|
|
207
|
+
cx.font = "bold 11px ui-monospace,monospace"; cx.textAlign = "center";
|
|
208
|
+
for (let i=0;i<8;i++) {
|
|
209
|
+
const P = POD[i], active = i===idx && !done && mode==="run", passed = done || i<idx;
|
|
210
|
+
const t = NODES[i]+" "+PHASE[NODES[i]].slice(0,2);
|
|
211
|
+
cx.fillStyle = "rgba(20,14,8,.75)"; cx.fillText(t, P.x+1, P.y+53);
|
|
212
|
+
cx.fillStyle = active ? "#faf6ec" : passed ? "#8fdc78" : "#b8a888";
|
|
213
|
+
cx.fillText(t, P.x, P.y+52);
|
|
214
|
+
}
|
|
215
|
+
const name = (cur.feature ? cur.feature.replace(/^\d+\./,"") : "cm 工作流") + " 的办公室";
|
|
216
|
+
cx.font = "bold 12px ui-monospace,monospace";
|
|
217
|
+
const tw = cx.measureText("★ "+name+" ★").width + 24;
|
|
218
|
+
rect(W/2-tw/2, H-24, tw, 19, "#241f1a"); rect(W/2-tw/2+2, H-22, tw-4, 15, "#3a2c1e");
|
|
219
|
+
cx.fillStyle = "#f0c05a"; cx.fillText("★ "+name+" ★", W/2, H-10);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function emote(ch, x, y, col) {
|
|
223
|
+
if ((frame>>4)%2) return;
|
|
224
|
+
cx.fillStyle = col; cx.font = "bold 17px monospace"; cx.textAlign = "center";
|
|
225
|
+
cx.fillText(ch, x, y);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function drawIdle() {
|
|
229
|
+
room("night");
|
|
230
|
+
const ents = []; podEnts(ents, 0, false); furnitureEnts(ents);
|
|
231
|
+
ents.push({ by: LOUNGE.y+34, f: () => { // 中央沙发打盹
|
|
232
|
+
shadow(LOUNGE.x, LOUNGE.y+34, 66, 7);
|
|
233
|
+
tile("sofaL", LOUNGE.x-72, LOUNGE.y-6); tile("sofaM", LOUNGE.x-24, LOUNGE.y-6);
|
|
234
|
+
tile("sofaR", LOUNGE.x+24, LOUNGE.y-6); }});
|
|
235
|
+
ents.push(person("sleep", LOUNGE.x, LOUNGE.y+2, false));
|
|
236
|
+
drawSorted(ents); labels(0, false);
|
|
237
|
+
const zi = (frame>>4)%3;
|
|
238
|
+
cx.fillStyle="#cfc8e8"; cx.font="bold 13px monospace"; cx.textAlign="left";
|
|
239
|
+
cx.fillText("z".repeat(zi+1), LOUNGE.x+26, LOUNGE.y-40-zi*4);
|
|
240
|
+
lighting(0,false,false,"night");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function draw() {
|
|
244
|
+
frame++;
|
|
245
|
+
if (!loaded) { rect(0,0,W,H,"#0d0b16"); requestAnimationFrame(draw); return; }
|
|
246
|
+
if (mode==="wait") { drawIdle(); requestAnimationFrame(draw); return; }
|
|
247
|
+
const idx = Math.max(0, NODES.indexOf(cur.node)),
|
|
248
|
+
paused = cur.state==="paused_for_human", done = mode==="done",
|
|
249
|
+
zone = done ? "night" : ZONE(idx);
|
|
250
|
+
room(zone);
|
|
251
|
+
const ents = []; podEnts(ents, idx, done); furnitureEnts(ents);
|
|
252
|
+
let sat = false;
|
|
253
|
+
if (!done) {
|
|
254
|
+
const t = paused ? {x:BENCH.cx+16, y:BENCH.y+26} : {x:POD[idx].x-52, y:POD[idx].y+20};
|
|
255
|
+
const dx = t.x-wk.x, dy = t.y-wk.y, d = Math.hypot(dx,dy);
|
|
256
|
+
const spd = isDemo ? 8 : 2.6;
|
|
257
|
+
if (d>3) { wk.x += dx/d*Math.min(spd,d); wk.y += dy/d*Math.min(spd,d); }
|
|
258
|
+
const walking = d>3;
|
|
259
|
+
sat = paused && !walking;
|
|
260
|
+
ents.push(person("worker", wk.x, wk.y, walking));
|
|
261
|
+
if (sat) ents.push({ by: wk.y+1, f: () => { // 补画椅面盖腿=坐下
|
|
262
|
+
tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
|
|
263
|
+
} else {
|
|
264
|
+
ents.push({ by: LOUNGE.y+18, f: () => { shadow(LOUNGE.x, LOUNGE.y+18, 34, 7);
|
|
265
|
+
tile("tblO1", LOUNGE.x-24, LOUNGE.y-54); tile("tblO2", LOUNGE.x-24, LOUNGE.y-8); }});
|
|
266
|
+
ents.push(person("worker", LOUNGE.x-64, LOUNGE.y+6, true));
|
|
267
|
+
ents.push(person("celA", LOUNGE.x+64, LOUNGE.y+6, true));
|
|
268
|
+
ents.push(person("celB", LOUNGE.x-40, LOUNGE.y+52, true));
|
|
269
|
+
ents.push(person("celC", LOUNGE.x+40, LOUNGE.y+52, true));
|
|
270
|
+
}
|
|
271
|
+
if (paused) { drawSorted(ents.filter(e=>true)); rect(0,0,W,H,"rgba(10,8,22,.32)");
|
|
272
|
+
const again = []; again.push(person("worker", wk.x, wk.y, false));
|
|
273
|
+
if (sat) again.push({ by: wk.y+1, f: () => {
|
|
274
|
+
tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
|
|
275
|
+
drawSorted(again);
|
|
276
|
+
if (sat) emote("!", wk.x, wk.y-56, "#f5d95c");
|
|
277
|
+
} else {
|
|
278
|
+
drawSorted(ents);
|
|
279
|
+
if (staleRun && !done) emote("?", wk.x, wk.y-56, "#f08a80");
|
|
280
|
+
}
|
|
281
|
+
labels(idx, done);
|
|
282
|
+
if (done) {
|
|
283
|
+
if (frame%3===0) confetti.push({ x:Math.random()*W, y:-6, v:1+Math.random()*1.6,
|
|
284
|
+
w:Math.random()>.5?4:3, c:["#f0b03c","#f06a3c","#63b8b0","#95c86a","#8c78c8","#faf6ec"][Math.random()*6|0] });
|
|
285
|
+
confetti = confetti.filter(p => p.y < H-8);
|
|
286
|
+
for (const p of confetti) { p.y += p.v; rect(p.x + Math.sin((p.y+p.x)/14)*5, p.y, p.w, p.w+2, p.c); }
|
|
287
|
+
rect(W/2-104, 66, 208, 34, "#faf6ec"); rect(W/2-104, 66, 208, 4, "#241f1a");
|
|
288
|
+
rect(W/2-104, 96, 208, 4, "#241f1a"); rect(W/2-104, 66, 4, 34, "#241f1a"); rect(W/2+100, 66, 4, 34, "#241f1a");
|
|
289
|
+
cx.fillStyle="#1c1a14"; cx.font="bold 19px ui-monospace,monospace"; cx.textAlign="center";
|
|
290
|
+
cx.fillText("STAGE CLEAR!", W/2, 90);
|
|
291
|
+
}
|
|
292
|
+
lighting(idx, paused, done, zone);
|
|
293
|
+
requestAnimationFrame(draw);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function hud() {
|
|
297
|
+
const paused = cur.state==="paused_for_human", done = mode==="done";
|
|
298
|
+
const ph = document.getElementById("phase");
|
|
299
|
+
if (mode==="wait") { ph.textContent = "😴 休息中(等 /cm-plugin:ai 启动)"; ph.className=""; }
|
|
300
|
+
else if (staleRun) { ph.textContent = "⚠ 可能已中断(去会话确认)"; ph.className="stale"; }
|
|
301
|
+
else { ph.textContent = done ? "STAGE CLEAR — 全部完成" :
|
|
302
|
+
(paused ? "🖐 等你确认" : "STAGE "+cur.node+" · "+(PHASE[cur.node]||"运行中"));
|
|
303
|
+
ph.className = paused ? "paused" : ""; }
|
|
304
|
+
document.getElementById("task").textContent =
|
|
305
|
+
mode==="wait" ? "" : [cur.feature.replace(/^\d+\./,""), cur.task].filter(Boolean).join(" · ");
|
|
306
|
+
const b = document.getElementById("bubble");
|
|
307
|
+
if (mode==="run" && cur.detail) {
|
|
308
|
+
b.style.display="block"; b.className = paused ? "paused" : "";
|
|
309
|
+
b.textContent = (paused ? "🖐 " : "") + cur.detail;
|
|
310
|
+
const idx = Math.max(0, NODES.indexOf(cur.node));
|
|
311
|
+
b.style.left = paused ? (BENCH.cx/W*100+"%")
|
|
312
|
+
: (Math.min(86, Math.max(14, POD[idx].x/W*100)) + "%");
|
|
313
|
+
b.style.top = paused ? ((BENCH.y-58)/H*100+"%") : ((POD[idx].y-34)/H*100 + "%");
|
|
314
|
+
} else b.style.display = "none";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 上次收工小记:读度量汇总.md 最后一节
|
|
318
|
+
async function loadNote() {
|
|
319
|
+
try {
|
|
320
|
+
const md = await (await fetch("度量汇总.md?ts="+Date.now())).text();
|
|
321
|
+
if (md.startsWith("<")) return;
|
|
322
|
+
const secs = md.trim().split(/\n(?=#)/), last = secs[secs.length-1];
|
|
323
|
+
const lines = last.split("\n").filter(l=>l.trim().startsWith("-")).slice(0,4);
|
|
324
|
+
if (!lines.length) return;
|
|
325
|
+
const n = document.getElementById("note");
|
|
326
|
+
n.innerHTML = "<b>📋 上次收工小记</b>\n" + lines.join("\n").replace(/</g,"<");
|
|
327
|
+
n.style.display = "block";
|
|
328
|
+
} catch {}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const DEMO = [
|
|
332
|
+
["N1","running","正在检查项目环境和版本控制","",""],
|
|
333
|
+
["N2","running","正在读需求、排任务","1.market-board",""],
|
|
334
|
+
["N3","running","正在开发行情数据接口","1.market-board","T-005"],
|
|
335
|
+
["N4","running","第1轮代码审查进行中","1.market-board","T-005"],
|
|
336
|
+
["N4","paused_for_human","确认一下:原型里有3个按钮点了没反应,要做吗?","1.market-board","T-006"],
|
|
337
|
+
["N5","running","保存进度:行情页面完成","1.market-board","T-006"],
|
|
338
|
+
["N6","running","质量检测和业务走查","1.market-board",""],
|
|
339
|
+
["N7","running","整理现场,准备下一个任务","1.market-board",""],
|
|
340
|
+
["N8","running","收尾:整理文档和发布清单","",""],
|
|
341
|
+
["N8","done","全部完成","",""]];
|
|
342
|
+
const q = new URLSearchParams(location.search);
|
|
343
|
+
const isDemo = q.has("demo"), isIdleDemo = q.has("idle");
|
|
344
|
+
|
|
345
|
+
async function poll() {
|
|
346
|
+
if (isIdleDemo) { mode="wait"; hud(); loadNote(); return; }
|
|
347
|
+
if (isDemo) {
|
|
348
|
+
const [node,state,detail,feature,task] = DEMO[(Math.floor(Date.now()/2600))%DEMO.length];
|
|
349
|
+
cur = {node,state,detail,feature,task};
|
|
350
|
+
mode = state==="done" ? "done" : "run"; staleRun = false; setBar(4,6);
|
|
351
|
+
} else {
|
|
352
|
+
try {
|
|
353
|
+
const res = await fetch(".cm-status.json?ts="+Date.now());
|
|
354
|
+
if (!res.ok) throw 0;
|
|
355
|
+
const s = await res.json();
|
|
356
|
+
cur = { node:s.node||"N1", state:s.state||"running", detail:s.detail||"",
|
|
357
|
+
feature:s.feature||"", task:s.task||"" };
|
|
358
|
+
mode = cur.state==="done" ? "done" : "run";
|
|
359
|
+
if (firstSnap && mode==="run") { firstSnap = false;
|
|
360
|
+
if (cur.state==="paused_for_human") { wk.x = BENCH.cx+16; wk.y = BENCH.y+26; }
|
|
361
|
+
else { const P = POD[Math.max(0,NODES.indexOf(cur.node))];
|
|
362
|
+
wk.x = P.x-52; wk.y = P.y+20; } }
|
|
363
|
+
const lm = res.headers.get("last-modified"); // 中断检测(阈值 10 分钟)
|
|
364
|
+
staleRun = !!(lm && mode==="run" && cur.state!=="paused_for_human"
|
|
365
|
+
&& (Date.now()-new Date(lm).getTime()) > 600_000);
|
|
366
|
+
if (cur.feature && mode!=="wait") {
|
|
367
|
+
const md = await (await fetch(cur.feature+"/tasks.md?ts="+Date.now())).text();
|
|
368
|
+
const items = md.match(/^- \[( |x)\] T-\d+/gm)||[];
|
|
369
|
+
setBar(items.filter(l=>l.includes("[x]")).length, items.length);
|
|
370
|
+
} else setBar(0,0);
|
|
371
|
+
document.getElementById("hint").textContent = "";
|
|
372
|
+
document.getElementById("note").style.display = mode==="done" ? "" : "none";
|
|
373
|
+
if (mode==="done") loadNote();
|
|
374
|
+
} catch { mode = "wait"; setBar(0,0); loadNote();
|
|
375
|
+
document.getElementById("hint").textContent = "😴 值班中"; }
|
|
376
|
+
}
|
|
377
|
+
hud(); setTimeout(poll, isDemo?400:2000);
|
|
378
|
+
}
|
|
379
|
+
function setBar(d,t){
|
|
380
|
+
document.getElementById("fill").style.width = t ? (100*d/t)+"%" : "0";
|
|
381
|
+
document.getElementById("count").textContent = t ? d+"/"+t : "";
|
|
382
|
+
document.getElementById("coins").textContent = d ? "⛁ "+d : "";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
draw(); poll();
|
|
386
|
+
</script>
|
|
387
|
+
</body>
|
|
388
|
+
</html>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cm 像素小剧场 · 终端版
|
|
3
|
+
# 8 个像素工位对应 N1–N8,小人走到哪个工位,流水线就跑到哪一步。
|
|
4
|
+
# 纯只读消费 .cm-status.json / tasks.md,零侵入。
|
|
5
|
+
#
|
|
6
|
+
# 用法:
|
|
7
|
+
# ./cm-pixel.sh # 跟随当前 cm 运行实时播放(2s 刷新,Ctrl-C 退出)
|
|
8
|
+
# ./cm-pixel.sh --demo # 演示模式:循环播放 8 节点+暂停+完成,无需真实运行
|
|
9
|
+
# ./cm-pixel.sh --once # 只渲染一帧后退出(截图/调试用)
|
|
10
|
+
MODE="${1:-}"
|
|
11
|
+
python3 - "$HOME/.claude/cm-plugin-current-specs" "$MODE" <<'PYEOF'
|
|
12
|
+
import json, os, re, sys, time
|
|
13
|
+
|
|
14
|
+
PTR, MODE = sys.argv[1], (sys.argv[2] if len(sys.argv) > 2 else "")
|
|
15
|
+
|
|
16
|
+
# ---------- 调色板(GameBoy 绿系 + 功能色) ----------
|
|
17
|
+
P = {
|
|
18
|
+
".": None, # 透明
|
|
19
|
+
"k": (24, 28, 24), # 深底
|
|
20
|
+
"g": (48, 98, 48), # 暗绿
|
|
21
|
+
"G": (110, 170, 80), # 亮绿
|
|
22
|
+
"W": (224, 240, 208), # 米白
|
|
23
|
+
"y": (240, 200, 60), # 黄(暂停/高亮)
|
|
24
|
+
"o": (230, 140, 60), # 橙(皮肤)
|
|
25
|
+
"b": (90, 140, 220), # 蓝(工装)
|
|
26
|
+
"r": (210, 80, 80), # 红
|
|
27
|
+
"c": (100, 200, 200), # 青(屏幕)
|
|
28
|
+
"d": (60, 66, 60), # 桌灰
|
|
29
|
+
"m": (140, 120, 200), # 紫(Codex机器人)
|
|
30
|
+
# 分关卡底色(huashu 像素横版:晨/昼/暮/夜,暗场压灰)
|
|
31
|
+
"A": (54, 42, 44), "B": (34, 44, 58), "C": (48, 38, 54), "D": (24, 24, 42),
|
|
32
|
+
}
|
|
33
|
+
ZONE = lambda i: "A" if i < 2 else ("B" if i < 5 else ("C" if i < 7 else "D"))
|
|
34
|
+
|
|
35
|
+
PHASE = {
|
|
36
|
+
"N1": "启动", "N2": "规划", "N3": "开发", "N4": "审查",
|
|
37
|
+
"N5": "保存", "N6": "质检", "N7": "整理", "N8": "收尾",
|
|
38
|
+
}
|
|
39
|
+
NODES = list(PHASE)
|
|
40
|
+
|
|
41
|
+
# ---------- 精灵(字符串像素图) ----------
|
|
42
|
+
DESK = [ # 7x4 工位:显示器+桌子
|
|
43
|
+
".ccc...",
|
|
44
|
+
".ccc...",
|
|
45
|
+
"ddddddd",
|
|
46
|
+
".d...d.",
|
|
47
|
+
]
|
|
48
|
+
WORKER = [ # 5x6 小人,两帧(敲键盘手起手落)
|
|
49
|
+
[".ooo.", ".ooo.", ".bbb.", "bbbbb", ".b.b.", ".b.b."],
|
|
50
|
+
[".ooo.", ".ooo.", "bbbb.", ".bbbb", ".b.b.", ".b.b."],
|
|
51
|
+
]
|
|
52
|
+
WORKER_HAND_UP = [".ooo.", ".ooo.", "bbbbo", ".bbb.", ".b.b.", ".b.b."] # 举手(暂停)
|
|
53
|
+
ROBOT = [".mmm.", ".mcm.", "mmmmm", ".m.m."] # Codex 机器人(N4 常驻)
|
|
54
|
+
FLAG = ["G", "GG", "GGG"] # 完成小旗
|
|
55
|
+
|
|
56
|
+
CW, CH = 78, 22 # 画布尺寸(像素);半块渲染后 = 78 x 11 行
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def blank():
|
|
60
|
+
cv = []
|
|
61
|
+
for y in range(CH):
|
|
62
|
+
row = []
|
|
63
|
+
for x in range(CW):
|
|
64
|
+
i = min(7, max(0, (x - 1) // 10))
|
|
65
|
+
row.append(ZONE(i))
|
|
66
|
+
cv.append(row)
|
|
67
|
+
return cv
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def blit(cv, sprite, x, y):
|
|
71
|
+
for dy, row in enumerate(sprite):
|
|
72
|
+
for dx, ch in enumerate(row):
|
|
73
|
+
if ch != "." and 0 <= y + dy < CH and 0 <= x + dx < CW:
|
|
74
|
+
cv[y + dy][x + dx] = ch
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def render(cv, dim=False):
|
|
78
|
+
"""半块字符渲染:每两行像素合成一行字符,上像素=前景▀,下像素=背景"""
|
|
79
|
+
out = []
|
|
80
|
+
for y in range(0, CH, 2):
|
|
81
|
+
line = []
|
|
82
|
+
for x in range(CW):
|
|
83
|
+
t, b = P[cv[y][x]], P[cv[y + 1][x]]
|
|
84
|
+
if dim:
|
|
85
|
+
t = tuple(v // 2 for v in t)
|
|
86
|
+
b = tuple(v // 2 for v in b)
|
|
87
|
+
line.append(f"\033[38;2;{t[0]};{t[1]};{t[2]}m\033[48;2;{b[0]};{b[1]};{b[2]}m▀")
|
|
88
|
+
out.append("".join(line) + "\033[0m")
|
|
89
|
+
return out
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def scene(node, state, frame):
|
|
93
|
+
cv = blank()
|
|
94
|
+
# 地板
|
|
95
|
+
for x in range(CW):
|
|
96
|
+
cv[CH - 1][x] = "g"
|
|
97
|
+
cv[CH - 2][x] = "g"
|
|
98
|
+
# 天体: 昼区太阳 / 夜区星星(隔帧闪烁)
|
|
99
|
+
for dx in range(3):
|
|
100
|
+
for dy in range(2):
|
|
101
|
+
cv[1 + dy][1 + 3 * 10 + 3 + dx] = "y"
|
|
102
|
+
for j, (sx, sy) in enumerate([(72, 1), (75, 3), (73, 6), (76, 8), (71, 4)]):
|
|
103
|
+
if (frame + j) % 4:
|
|
104
|
+
cv[sy][sx] = "W"
|
|
105
|
+
idx = NODES.index(node) if node in NODES else 0
|
|
106
|
+
done_all = state == "done"
|
|
107
|
+
for i in range(8):
|
|
108
|
+
x0 = 1 + i * 10 # 每工位 10px 宽
|
|
109
|
+
blit(cv, DESK, x0, CH - 6)
|
|
110
|
+
if i == 3: # N4 工位常驻 Codex 机器人(坐在桌对面)
|
|
111
|
+
blit(cv, ROBOT, x0 + 4, CH - 10)
|
|
112
|
+
if done_all or i < idx:
|
|
113
|
+
blit(cv, ["..G", ".GG", "GGG"], x0 + 5, CH - 9) # 已过节点插小旗
|
|
114
|
+
if not done_all and i == idx:
|
|
115
|
+
sprite = WORKER_HAND_UP if state == "paused_for_human" else WORKER[frame % 2]
|
|
116
|
+
blit(cv, sprite, x0 + 1, CH - 12)
|
|
117
|
+
cv[CH - 13][x0 + 2] = "y" if state == "paused_for_human" else "W" # 头顶指示点
|
|
118
|
+
if done_all: # 烟花
|
|
119
|
+
import random
|
|
120
|
+
rnd = random.Random(frame)
|
|
121
|
+
for _ in range(26):
|
|
122
|
+
cv[rnd.randrange(2, 10)][rnd.randrange(2, CW - 2)] = rnd.choice("yWrcG")
|
|
123
|
+
return cv
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def progress(specs, feature):
|
|
127
|
+
try:
|
|
128
|
+
md = open(os.path.join(specs, feature, "tasks.md"), encoding="utf-8").read()
|
|
129
|
+
items = re.findall(r"^- \[( |x)\] (T-\d+)", md, re.M)
|
|
130
|
+
return sum(1 for c, _ in items if c == "x"), len(items)
|
|
131
|
+
except Exception:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
DEMO = [ # 演示剧本:(node, state, detail, feature)
|
|
136
|
+
("N1", "running", "正在检查项目环境和版本控制", ""),
|
|
137
|
+
("N2", "running", "正在读需求、排任务", "1.market-board"),
|
|
138
|
+
("N3", "running", "正在开发行情数据接口", "1.market-board"),
|
|
139
|
+
("N4", "running", "第1轮代码审查进行中", "1.market-board"),
|
|
140
|
+
("N4", "paused_for_human", "确认一下:这个按钮点了以后要跳到哪个页面?", "1.market-board"),
|
|
141
|
+
("N5", "running", "保存进度:数据接口完成", "1.market-board"),
|
|
142
|
+
("N6", "running", "质量检测和业务走查", "1.market-board"),
|
|
143
|
+
("N7", "running", "整理现场,准备下一个任务", "1.market-board"),
|
|
144
|
+
("N8", "running", "收尾:整理文档和发布清单", ""),
|
|
145
|
+
("N8", "done", "全部完成", ""),
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
BAR_W = 30
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def draw(frame):
|
|
152
|
+
if MODE == "--demo":
|
|
153
|
+
node, state, detail, feature = DEMO[(frame // 3) % len(DEMO)]
|
|
154
|
+
prog, task, age = (3, 6), "T-005", 0
|
|
155
|
+
else:
|
|
156
|
+
try:
|
|
157
|
+
specs = open(PTR).read().strip()
|
|
158
|
+
s = json.load(open(os.path.join(specs, ".cm-status.json")))
|
|
159
|
+
age = time.time() - os.path.getmtime(os.path.join(specs, ".cm-status.json"))
|
|
160
|
+
except Exception:
|
|
161
|
+
return ["", " ⏳ 没有运行中的 cm 工作流(等待 /cm-plugin:ai 启动…)", ""]
|
|
162
|
+
node, state = s.get("node", "N1"), s.get("state", "running")
|
|
163
|
+
detail, feature, task = s.get("detail", ""), s.get("feature", ""), s.get("task", "")
|
|
164
|
+
prog = progress(specs, feature) if feature else None
|
|
165
|
+
|
|
166
|
+
dim = state == "paused_for_human"
|
|
167
|
+
lines = [" \033[1mcm 像素流水线\033[0m" + (" \033[33m⏸ 等人\033[0m" if dim else "")]
|
|
168
|
+
lines += [" " + l for l in render(scene(node, state, frame), dim=False)]
|
|
169
|
+
# 工位标签
|
|
170
|
+
lab = []
|
|
171
|
+
for i, n in enumerate(NODES):
|
|
172
|
+
name = f"{n}{PHASE[n]}"
|
|
173
|
+
if state == "done" or (n != node and i < NODES.index(node) if node in NODES else False):
|
|
174
|
+
lab.append(f"\033[32m{name}\033[0m")
|
|
175
|
+
elif n == node and state != "done":
|
|
176
|
+
lab.append(f"\033[1;33m{name}\033[0m" if dim else f"\033[1;97m{name}\033[0m")
|
|
177
|
+
else:
|
|
178
|
+
lab.append(f"\033[2m{name}\033[0m")
|
|
179
|
+
lines.append(" " + " ".join(lab))
|
|
180
|
+
# 台词气泡(大白话,与状态条同源)
|
|
181
|
+
icon = "🖐" if dim else ("🎉" if state == "done" else "💬")
|
|
182
|
+
tag = f" \033[2m{task}\033[0m" if task else ""
|
|
183
|
+
lines.append(f" {icon} {detail[:56]}{tag}")
|
|
184
|
+
# 进度条
|
|
185
|
+
if prog:
|
|
186
|
+
d, t = prog
|
|
187
|
+
fill = int(BAR_W * d / t) if t else 0
|
|
188
|
+
lines.append(f" 进度 \033[32m{'█'*fill}\033[2m{'░'*(BAR_W-fill)}\033[0m {d}/{t}"
|
|
189
|
+
+ (f" \033[2m{feature}\033[0m" if feature else ""))
|
|
190
|
+
if MODE != "--demo" and state not in ("done", "paused_for_human") and age > 600:
|
|
191
|
+
lines.append(f" \033[33m⚠ 可能已中断\033[0m(最后更新 {int(age//60)} 分钟前,去会话确认)")
|
|
192
|
+
return lines
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if MODE == "--once":
|
|
196
|
+
print("\n".join(draw(0)))
|
|
197
|
+
sys.exit(0)
|
|
198
|
+
|
|
199
|
+
sys.stdout.write("\033[?1049h\033[?25l") # 备用屏 + 藏光标
|
|
200
|
+
try:
|
|
201
|
+
f = 0
|
|
202
|
+
while True:
|
|
203
|
+
frame_txt = "\n".join(draw(f))
|
|
204
|
+
sys.stdout.write("\033[H\033[2J" + frame_txt + "\n\n \033[2mCtrl-C 退出\033[0m\n")
|
|
205
|
+
sys.stdout.flush()
|
|
206
|
+
time.sleep(1 if MODE == "--demo" else 2)
|
|
207
|
+
f += 1
|
|
208
|
+
except KeyboardInterrupt:
|
|
209
|
+
pass
|
|
210
|
+
finally:
|
|
211
|
+
sys.stdout.write("\033[?25h\033[?1049l")
|
|
212
|
+
PYEOF
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# cm-pixel.html 构建工具
|
|
2
|
+
|
|
3
|
+
`cm-pixel.html` 由 `template.html` + Kenney CC0 素材表自动生成——不要直接改 cm-pixel.html,改模板后重新构建。
|
|
4
|
+
|
|
5
|
+
## 构建
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python3 build.py # 需要 Pillow;先把脚本里的路径改成本目录
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
build.py 从三张素材表抠出用到的 tile 打成一张小图集(约 5KB),
|
|
12
|
+
以 base64 内嵌进模板的 @B64@ 占位符,tile 名称坐标表内嵌进 @TMAP@。
|
|
13
|
+
|
|
14
|
+
## 素材来源(全部 CC0,可商用)
|
|
15
|
+
|
|
16
|
+
- sheets/roguelikeIndoor_transparent.png — Kenney "Roguelike Indoors"
|
|
17
|
+
- sheets/roguelikeCity_tilemap.png — Kenney "Roguelike Modern City"
|
|
18
|
+
- sheets/roguelikeChar_transparent.png — Kenney "Roguelike Characters"(纸娃娃:身体+裤子+衣服+发型叠加)
|
|
19
|
+
|
|
20
|
+
下载页: https://kenney.nl/assets (CC0 1.0,无需署名)
|
|
Binary file
|