goiabaseeds 1.0.0

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.
Files changed (129) hide show
  1. package/README.md +173 -0
  2. package/bin/goiabaseeds.js +98 -0
  3. package/eslint.config.js +14 -0
  4. package/package.json +61 -0
  5. package/skills/README.md +60 -0
  6. package/skills/apify/SKILL.md +55 -0
  7. package/skills/blotato/SKILL.md +63 -0
  8. package/skills/canva/SKILL.md +60 -0
  9. package/skills/goiabaseeds-agent-creator/SKILL.md +192 -0
  10. package/skills/goiabaseeds-skill-creator/SKILL.md +407 -0
  11. package/skills/goiabaseeds-skill-creator/agents/analyzer.md +274 -0
  12. package/skills/goiabaseeds-skill-creator/agents/comparator.md +202 -0
  13. package/skills/goiabaseeds-skill-creator/agents/grader.md +223 -0
  14. package/skills/goiabaseeds-skill-creator/assets/eval_review.html +146 -0
  15. package/skills/goiabaseeds-skill-creator/eval-viewer/generate_review.py +471 -0
  16. package/skills/goiabaseeds-skill-creator/eval-viewer/viewer.html +1325 -0
  17. package/skills/goiabaseeds-skill-creator/references/schemas.md +430 -0
  18. package/skills/goiabaseeds-skill-creator/references/skill-format.md +235 -0
  19. package/skills/goiabaseeds-skill-creator/scripts/__init__.py +0 -0
  20. package/skills/goiabaseeds-skill-creator/scripts/aggregate_benchmark.py +401 -0
  21. package/skills/goiabaseeds-skill-creator/scripts/quick_validate.py +103 -0
  22. package/skills/goiabaseeds-skill-creator/scripts/run_eval.py +310 -0
  23. package/skills/goiabaseeds-skill-creator/scripts/utils.py +47 -0
  24. package/skills/image-creator/SKILL.md +155 -0
  25. package/skills/image-fetcher/SKILL.md +91 -0
  26. package/skills/image-generator/SKILL.md +124 -0
  27. package/skills/image-generator/scripts/generate.py +175 -0
  28. package/skills/instagram-publisher/SKILL.md +118 -0
  29. package/skills/instagram-publisher/scripts/publish.js +164 -0
  30. package/src/agent-session.js +110 -0
  31. package/src/agents-cli.js +158 -0
  32. package/src/agents.js +134 -0
  33. package/src/bundle-detector.js +75 -0
  34. package/src/bundle.js +286 -0
  35. package/src/context.js +142 -0
  36. package/src/export.js +52 -0
  37. package/src/i18n.js +48 -0
  38. package/src/init.js +367 -0
  39. package/src/locales/en.json +72 -0
  40. package/src/locales/es.json +71 -0
  41. package/src/locales/pt-BR.json +71 -0
  42. package/src/logger.js +38 -0
  43. package/src/models-cli.js +165 -0
  44. package/src/pipeline-runner.js +478 -0
  45. package/src/prompt.js +46 -0
  46. package/src/provider.js +156 -0
  47. package/src/readme/README.md +181 -0
  48. package/src/run.js +100 -0
  49. package/src/runs.js +90 -0
  50. package/src/skills-cli.js +157 -0
  51. package/src/skills.js +146 -0
  52. package/src/state-manager.js +280 -0
  53. package/src/tools.js +158 -0
  54. package/src/update.js +140 -0
  55. package/templates/_goiabaseeds/.goiabaseeds-version +1 -0
  56. package/templates/_goiabaseeds/_investigations/.gitkeep +0 -0
  57. package/templates/_goiabaseeds/config/playwright.config.json +11 -0
  58. package/templates/_goiabaseeds/core/architect.agent.yaml +1141 -0
  59. package/templates/_goiabaseeds/core/best-practices/_catalog.yaml +116 -0
  60. package/templates/_goiabaseeds/core/best-practices/blog-post.md +132 -0
  61. package/templates/_goiabaseeds/core/best-practices/blog-seo.md +127 -0
  62. package/templates/_goiabaseeds/core/best-practices/copywriting.md +428 -0
  63. package/templates/_goiabaseeds/core/best-practices/data-analysis.md +401 -0
  64. package/templates/_goiabaseeds/core/best-practices/email-newsletter.md +118 -0
  65. package/templates/_goiabaseeds/core/best-practices/email-sales.md +110 -0
  66. package/templates/_goiabaseeds/core/best-practices/image-design.md +349 -0
  67. package/templates/_goiabaseeds/core/best-practices/instagram-feed.md +235 -0
  68. package/templates/_goiabaseeds/core/best-practices/instagram-reels.md +112 -0
  69. package/templates/_goiabaseeds/core/best-practices/instagram-stories.md +107 -0
  70. package/templates/_goiabaseeds/core/best-practices/linkedin-article.md +116 -0
  71. package/templates/_goiabaseeds/core/best-practices/linkedin-post.md +121 -0
  72. package/templates/_goiabaseeds/core/best-practices/researching.md +347 -0
  73. package/templates/_goiabaseeds/core/best-practices/review.md +269 -0
  74. package/templates/_goiabaseeds/core/best-practices/social-networks-publishing.md +294 -0
  75. package/templates/_goiabaseeds/core/best-practices/strategist.md +344 -0
  76. package/templates/_goiabaseeds/core/best-practices/technical-writing.md +363 -0
  77. package/templates/_goiabaseeds/core/best-practices/twitter-post.md +105 -0
  78. package/templates/_goiabaseeds/core/best-practices/twitter-thread.md +122 -0
  79. package/templates/_goiabaseeds/core/best-practices/whatsapp-broadcast.md +107 -0
  80. package/templates/_goiabaseeds/core/best-practices/youtube-script.md +122 -0
  81. package/templates/_goiabaseeds/core/best-practices/youtube-shorts.md +112 -0
  82. package/templates/_goiabaseeds/core/prompts/auguste.dupin.prompt.md +1008 -0
  83. package/templates/_goiabaseeds/core/runner.pipeline.md +467 -0
  84. package/templates/_goiabaseeds/core/skills.engine.md +381 -0
  85. package/templates/dashboard/index.html +12 -0
  86. package/templates/dashboard/package-lock.json +2082 -0
  87. package/templates/dashboard/package.json +28 -0
  88. package/templates/dashboard/src/App.tsx +46 -0
  89. package/templates/dashboard/src/components/DepartmentCard.tsx +47 -0
  90. package/templates/dashboard/src/components/DepartmentSelector.tsx +61 -0
  91. package/templates/dashboard/src/components/StatusBadge.tsx +32 -0
  92. package/templates/dashboard/src/components/StatusBar.tsx +97 -0
  93. package/templates/dashboard/src/hooks/useDepartmentSocket.ts +84 -0
  94. package/templates/dashboard/src/lib/formatTime.ts +16 -0
  95. package/templates/dashboard/src/lib/normalizeState.ts +25 -0
  96. package/templates/dashboard/src/main.tsx +10 -0
  97. package/templates/dashboard/src/office/AgentDesk.tsx +151 -0
  98. package/templates/dashboard/src/office/HandoffEnvelope.tsx +108 -0
  99. package/templates/dashboard/src/office/OfficeScene.tsx +147 -0
  100. package/templates/dashboard/src/office/drawDesk.ts +263 -0
  101. package/templates/dashboard/src/office/drawFurniture.ts +129 -0
  102. package/templates/dashboard/src/office/drawRoom.ts +51 -0
  103. package/templates/dashboard/src/office/palette.ts +181 -0
  104. package/templates/dashboard/src/office/textures.ts +254 -0
  105. package/templates/dashboard/src/plugin/departmentWatcher.ts +210 -0
  106. package/templates/dashboard/src/store/useDepartmentStore.ts +56 -0
  107. package/templates/dashboard/src/styles/globals.css +36 -0
  108. package/templates/dashboard/src/types/state.ts +64 -0
  109. package/templates/dashboard/src/vite-env.d.ts +1 -0
  110. package/templates/dashboard/tsconfig.json +24 -0
  111. package/templates/dashboard/vite.config.ts +13 -0
  112. package/templates/departments/.gitkeep +0 -0
  113. package/templates/ide-templates/antigravity/.agent/rules/goiabaseeds.md +55 -0
  114. package/templates/ide-templates/antigravity/.agent/workflows/goiabaseeds.md +102 -0
  115. package/templates/ide-templates/claude-code/.claude/skills/goiabaseeds/SKILL.md +182 -0
  116. package/templates/ide-templates/claude-code/.mcp.json +8 -0
  117. package/templates/ide-templates/claude-code/CLAUDE.md +43 -0
  118. package/templates/ide-templates/codex/.agents/skills/goiabaseeds/SKILL.md +6 -0
  119. package/templates/ide-templates/codex/AGENTS.md +105 -0
  120. package/templates/ide-templates/cursor/.cursor/commands/goiabaseeds.md +9 -0
  121. package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
  122. package/templates/ide-templates/cursor/.cursor/rules/goiabaseeds.mdc +48 -0
  123. package/templates/ide-templates/cursor/.cursorignore +3 -0
  124. package/templates/ide-templates/opencode/.opencode/commands/goiabaseeds.md +9 -0
  125. package/templates/ide-templates/opencode/AGENTS.md +105 -0
  126. package/templates/ide-templates/vscode-copilot/.github/prompts/goiabaseeds.prompt.md +201 -0
  127. package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
  128. package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
  129. package/templates/package.json +8 -0
@@ -0,0 +1,181 @@
1
+ // === Office Color Palette (Gather.town-inspired modern office) ===
2
+ export const COLORS = {
3
+ // Floor (wood planks)
4
+ woodLight: 0x9a7a56,
5
+ woodBase: 0x876a48,
6
+ woodDark: 0x755a3a,
7
+ woodGap: 0x4e3a28,
8
+
9
+ // Walls (clean cream)
10
+ wallFace: 0xe4d8cc,
11
+ wallTrim: 0xa89888,
12
+ wallShadow: 0x887868,
13
+
14
+ // Desk / Workstation (light maple)
15
+ deskTop: 0xd4bf9c,
16
+ deskEdge: 0xb8a480,
17
+ deskShadow: 0x806844,
18
+ monitorFrame: 0x2a2a32,
19
+ monitorScreen: 0x1a2a3a,
20
+ monitorScreenOn: 0x4a9aff,
21
+ keyboard: 0x3a3a42,
22
+
23
+ // Office chair (top-down)
24
+ chairSeat: 0x3a3a4a,
25
+ chairBase: 0x4a4a5a,
26
+
27
+ // Furniture / Decor
28
+ bookshelfWood: 0xc4a070,
29
+ plantGreen: 0x5aaa5a,
30
+ plantDark: 0x3a7a3a,
31
+ plantPot: 0xd4a878,
32
+ whiteboardBg: 0xf5f0ea,
33
+ whiteboardFrame: 0x8a8a92,
34
+ clockFace: 0xf0ebe0,
35
+ clockFrame: 0x6a6a72,
36
+ coffeeMachine: 0x4a4a52,
37
+
38
+ // Characters
39
+ skinLight: 0xf5c5a3,
40
+ skinMedium: 0xd4a574,
41
+ skinDark: 0x8b6340,
42
+ hairBlack: 0x2a2018,
43
+ hairBrown: 0x6a4a2a,
44
+ hairBlonde: 0xd4a840,
45
+ hairRed: 0xb04020,
46
+ shirtBlue: 0x4a78b0,
47
+ shirtGreen: 0x4a8a4a,
48
+ shirtRed: 0xa84848,
49
+ shirtWhite: 0xe0d8cc,
50
+ shirtPurple: 0x7a58a0,
51
+ pantsDark: 0x3a3a4a,
52
+ shoeDark: 0x2a2018,
53
+
54
+ // Character shading (auto-derived tones)
55
+ skinLightShadow: 0xd4a883,
56
+ skinMediumShadow: 0xb48854,
57
+ skinDarkShadow: 0x6b4320,
58
+
59
+ hairBlackLight: 0x3a3028,
60
+ hairBlackDark: 0x1a1008,
61
+ hairBrownLight: 0x8a6a4a,
62
+ hairBrownDark: 0x4a2a0a,
63
+ hairBlondeLight: 0xe4b850,
64
+ hairBlondeDark: 0xb48830,
65
+ hairRedLight: 0xc05030,
66
+ hairRedDark: 0x903010,
67
+
68
+ shirtBlueLight: 0x5a8ac0,
69
+ shirtBlueDark: 0x3a6898,
70
+ shirtGreenLight: 0x5a9a5a,
71
+ shirtGreenDark: 0x3a7a3a,
72
+ shirtRedLight: 0xb85858,
73
+ shirtRedDark: 0x983838,
74
+ shirtWhiteLight: 0xf0e8dc,
75
+ shirtWhiteDark: 0xd0c8bc,
76
+ shirtPurpleLight: 0x8a68b0,
77
+ shirtPurpleDark: 0x6a4890,
78
+
79
+ pantsBase: 0x3a3a4a,
80
+ pantsShade: 0x2a2a3a, // darker shade for leg edges/inner shadow
81
+
82
+ shoeBase: 0x2a2018,
83
+ shoeLight: 0x3a3028,
84
+
85
+ // Accessories
86
+ mugBody: 0xe0e0e0,
87
+ mugRim: 0xcccccc,
88
+ mugHandle: 0xcccccc,
89
+ postItYellow: 0xffee55,
90
+ postItPink: 0xff8866,
91
+ bookRed: 0xcc4444,
92
+ bookBlue: 0x4466aa,
93
+ bookGreen: 0x44aa44,
94
+ photoFrame: 0x3a3028,
95
+ waterBottle: 0x88bbdd,
96
+ waterCap: 0x4488aa,
97
+
98
+ // Name card
99
+ nameCardBg: 0x14141c,
100
+ nameCardText: 0xffffff,
101
+
102
+ // Belt
103
+ beltBuckle: 0x8a8a6a,
104
+
105
+ // Collar
106
+ collarWhite: 0xf0f0f0,
107
+
108
+ // Status effects (high contrast)
109
+ statusIdle: 0xaaaacc,
110
+ statusWorking: 0x60b0ff,
111
+ statusDone: 0x60f080,
112
+ statusCheckpoint: 0xffbb22,
113
+ bubbleBg: 0xffffff,
114
+ bubbleBorder: 0x3a3a4a,
115
+ particleGreen: 0x60f080,
116
+
117
+ // Envelope
118
+ envelopeBody: 0xf5e6c8,
119
+ envelopeFold: 0xe0d0b0,
120
+ envelopeSeal: 0xcc3333,
121
+ } as const;
122
+
123
+ // === Layout Constants ===
124
+ export const TILE = 32;
125
+ export const CELL_W = 4 * TILE; // 128px wide per cell (spacious)
126
+ export const CELL_H = 4 * TILE; // 128px tall per cell
127
+ export const SCENE_SCALE = 3; // Integer scaling — crisp pixel art
128
+
129
+ export type CharacterColors = {
130
+ hair: number; hairLight: number; hairDark: number;
131
+ skin: number; skinShadow: number;
132
+ shirt: number; shirtLight: number; shirtDark: number;
133
+ pants: number; pantsDark: number;
134
+ shoe: number; shoeLight: number;
135
+ };
136
+
137
+ // Character variants (assigned round-robin to agents)
138
+ export const CHARACTER_VARIANTS: CharacterColors[] = [
139
+ {
140
+ hair: COLORS.hairBlack, hairLight: COLORS.hairBlackLight, hairDark: COLORS.hairBlackDark,
141
+ skin: COLORS.skinLight, skinShadow: COLORS.skinLightShadow,
142
+ shirt: COLORS.shirtBlue, shirtLight: COLORS.shirtBlueLight, shirtDark: COLORS.shirtBlueDark,
143
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
144
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
145
+ },
146
+ {
147
+ hair: COLORS.hairBrown, hairLight: COLORS.hairBrownLight, hairDark: COLORS.hairBrownDark,
148
+ skin: COLORS.skinMedium, skinShadow: COLORS.skinMediumShadow,
149
+ shirt: COLORS.shirtGreen, shirtLight: COLORS.shirtGreenLight, shirtDark: COLORS.shirtGreenDark,
150
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
151
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
152
+ },
153
+ {
154
+ hair: COLORS.hairBlonde, hairLight: COLORS.hairBlondeLight, hairDark: COLORS.hairBlondeDark,
155
+ skin: COLORS.skinLight, skinShadow: COLORS.skinLightShadow,
156
+ shirt: COLORS.shirtRed, shirtLight: COLORS.shirtRedLight, shirtDark: COLORS.shirtRedDark,
157
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
158
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
159
+ },
160
+ {
161
+ hair: COLORS.hairRed, hairLight: COLORS.hairRedLight, hairDark: COLORS.hairRedDark,
162
+ skin: COLORS.skinDark, skinShadow: COLORS.skinDarkShadow,
163
+ shirt: COLORS.shirtWhite, shirtLight: COLORS.shirtWhiteLight, shirtDark: COLORS.shirtWhiteDark,
164
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
165
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
166
+ },
167
+ {
168
+ hair: COLORS.hairBlack, hairLight: COLORS.hairBlackLight, hairDark: COLORS.hairBlackDark,
169
+ skin: COLORS.skinMedium, skinShadow: COLORS.skinMediumShadow,
170
+ shirt: COLORS.shirtPurple, shirtLight: COLORS.shirtPurpleLight, shirtDark: COLORS.shirtPurpleDark,
171
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
172
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
173
+ },
174
+ {
175
+ hair: COLORS.hairBrown, hairLight: COLORS.hairBrownLight, hairDark: COLORS.hairBrownDark,
176
+ skin: COLORS.skinLight, skinShadow: COLORS.skinLightShadow,
177
+ shirt: COLORS.shirtGreen, shirtLight: COLORS.shirtGreenLight, shirtDark: COLORS.shirtGreenDark,
178
+ pants: COLORS.pantsBase, pantsDark: COLORS.pantsShade,
179
+ shoe: COLORS.shoeBase, shoeLight: COLORS.shoeLight,
180
+ },
181
+ ];
@@ -0,0 +1,254 @@
1
+ import { Texture, CanvasSource } from "pixi.js";
2
+ import { COLORS, CharacterColors } from "./palette";
3
+
4
+ function hexToRgb(hex: number): [number, number, number] {
5
+ return [(hex >> 16) & 0xff, (hex >> 8) & 0xff, hex & 0xff];
6
+ }
7
+
8
+ function createCanvas(w: number, h: number): [HTMLCanvasElement, CanvasRenderingContext2D] {
9
+ const canvas = document.createElement("canvas");
10
+ canvas.width = w;
11
+ canvas.height = h;
12
+ const ctx = canvas.getContext("2d")!;
13
+ ctx.imageSmoothingEnabled = false;
14
+ return [canvas, ctx];
15
+ }
16
+
17
+ // 1 pixel per logical pixel — SCENE_SCALE=2 handles display scaling
18
+ function px(ctx: CanvasRenderingContext2D, x: number, y: number, color: number) {
19
+ const [r, g, b] = hexToRgb(color);
20
+ ctx.fillStyle = `rgb(${r},${g},${b})`;
21
+ ctx.fillRect(x, y, 1, 1);
22
+ }
23
+
24
+ function hspan(ctx: CanvasRenderingContext2D, x1: number, x2: number, y: number, color: number) {
25
+ for (let x = x1; x <= x2; x++) px(ctx, x, y, color);
26
+ }
27
+
28
+ type MouthVariant = "neutral" | "focused" | "smile";
29
+
30
+ function drawHead(ctx: CanvasRenderingContext2D, c: CharacterColors, mouth: MouthVariant) {
31
+ // --- HAIR (rows 2-7) ---
32
+ hspan(ctx, 16, 30, 2, c.hair);
33
+ hspan(ctx, 15, 31, 3, c.hair);
34
+ hspan(ctx, 14, 32, 4, c.hair);
35
+ hspan(ctx, 14, 32, 5, c.hair);
36
+ // Hair highlights
37
+ px(ctx, 17, 3, c.hairLight); px(ctx, 20, 3, c.hairLight); px(ctx, 24, 3, c.hairLight);
38
+ px(ctx, 25, 4, c.hairLight); px(ctx, 28, 3, c.hairLight);
39
+ px(ctx, 16, 4, c.hairLight); px(ctx, 22, 4, c.hairLight); px(ctx, 30, 4, c.hairLight);
40
+ // Hair dark edges
41
+ px(ctx, 14, 5, c.hairDark); px(ctx, 32, 5, c.hairDark);
42
+ px(ctx, 15, 4, c.hairDark); px(ctx, 31, 4, c.hairDark);
43
+ // Sideburns
44
+ px(ctx, 14, 6, c.hair); px(ctx, 14, 7, c.hair);
45
+ px(ctx, 32, 6, c.hair); px(ctx, 32, 7, c.hair);
46
+
47
+ // --- FACE (rows 6-14) with oval contour ---
48
+ hspan(ctx, 15, 31, 6, c.skin);
49
+ hspan(ctx, 15, 31, 7, c.skin);
50
+ hspan(ctx, 15, 31, 8, c.skin);
51
+ hspan(ctx, 15, 31, 9, c.skin);
52
+ hspan(ctx, 15, 31, 10, c.skin);
53
+ hspan(ctx, 16, 30, 11, c.skin);
54
+ hspan(ctx, 17, 29, 12, c.skin);
55
+ hspan(ctx, 18, 28, 13, c.skin);
56
+ hspan(ctx, 19, 27, 14, c.skin);
57
+ // Oval contour shadows — left edge
58
+ px(ctx, 15, 8, c.skinShadow); px(ctx, 15, 9, c.skinShadow); px(ctx, 15, 10, c.skinShadow);
59
+ px(ctx, 16, 11, c.skinShadow); px(ctx, 17, 12, c.skinShadow);
60
+ px(ctx, 18, 13, c.skinShadow); px(ctx, 19, 14, c.skinShadow);
61
+ // Oval contour shadows — right edge
62
+ px(ctx, 31, 8, c.skinShadow); px(ctx, 31, 9, c.skinShadow); px(ctx, 31, 10, c.skinShadow);
63
+ px(ctx, 30, 11, c.skinShadow); px(ctx, 29, 12, c.skinShadow);
64
+ px(ctx, 28, 13, c.skinShadow); px(ctx, 27, 14, c.skinShadow);
65
+ // Jaw shadow
66
+ hspan(ctx, 20, 26, 14, c.skinShadow);
67
+
68
+ // Eyebrows (4px wide, 1 row above eyes)
69
+ hspan(ctx, 17, 20, 7, c.hairDark);
70
+ hspan(ctx, 26, 29, 7, c.hairDark);
71
+
72
+ // Eyes (5px wide: white-white-pupil-white-white) — focused shifts down 1px
73
+ const eyeY = mouth === "focused" ? 10 : 9;
74
+ px(ctx, 17, eyeY, 0xf0ede8); px(ctx, 18, eyeY, 0xf0ede8);
75
+ px(ctx, 19, eyeY, 0x2a2018); px(ctx, 20, eyeY, 0x2a2018);
76
+ px(ctx, 21, eyeY, 0xf0ede8);
77
+ px(ctx, 25, eyeY, 0xf0ede8);
78
+ px(ctx, 26, eyeY, 0x2a2018); px(ctx, 27, eyeY, 0x2a2018);
79
+ px(ctx, 28, eyeY, 0xf0ede8); px(ctx, 29, eyeY, 0xf0ede8);
80
+
81
+ // Nose (L-shape, more defined)
82
+ px(ctx, 23, 10, c.skinShadow);
83
+ px(ctx, 23, 11, c.skinShadow);
84
+ px(ctx, 23, 12, c.skinShadow);
85
+ px(ctx, 24, 12, c.skinShadow);
86
+
87
+ // Mouth
88
+ if (mouth === "smile") {
89
+ // Smile: corners up + bottom curve
90
+ px(ctx, 20, 13, 0x2a2018); px(ctx, 26, 13, 0x2a2018);
91
+ hspan(ctx, 21, 25, 14, 0x2a2018);
92
+ // Lower lip highlight
93
+ hspan(ctx, 22, 24, 15, c.skinShadow);
94
+ } else {
95
+ // neutral / focused: two-row mouth with lip
96
+ hspan(ctx, 21, 25, 13, 0x2a2018);
97
+ hspan(ctx, 22, 24, 14, c.skinShadow);
98
+ }
99
+
100
+ // Ears (2px tall, with shadow)
101
+ px(ctx, 14, 8, c.skin); px(ctx, 14, 9, c.skin); px(ctx, 14, 10, c.skinShadow);
102
+ px(ctx, 32, 8, c.skin); px(ctx, 32, 9, c.skin); px(ctx, 32, 10, c.skinShadow);
103
+ }
104
+
105
+ function drawBody(ctx: CanvasRenderingContext2D, c: CharacterColors) {
106
+ // --- NECK (rows 15-16) ---
107
+ hspan(ctx, 20, 26, 15, c.skin);
108
+ hspan(ctx, 21, 25, 16, c.skin);
109
+ px(ctx, 20, 15, c.skinShadow); px(ctx, 26, 15, c.skinShadow);
110
+
111
+ // --- COLLAR (row 17) ---
112
+ hspan(ctx, 17, 29, 17, COLORS.collarWhite);
113
+ px(ctx, 22, 17, 0xe0e0e0); px(ctx, 23, 17, 0xe0e0e0); px(ctx, 24, 17, 0xe0e0e0);
114
+
115
+ // --- SHIRT (rows 18-28) ---
116
+ for (let y = 18; y <= 28; y++) {
117
+ for (let i = 13; i <= 33; i++) {
118
+ if (i <= 15) px(ctx, i, y, c.shirtDark);
119
+ else if (i >= 31) px(ctx, i, y, c.shirtDark);
120
+ else if (i >= 22 && i <= 24) px(ctx, i, y, c.shirtLight);
121
+ else px(ctx, i, y, c.shirt);
122
+ }
123
+ }
124
+
125
+ // --- BELT (row 29) ---
126
+ hspan(ctx, 13, 33, 29, c.pantsDark);
127
+ px(ctx, 22, 29, COLORS.beltBuckle); px(ctx, 23, 29, COLORS.beltBuckle); px(ctx, 24, 29, COLORS.beltBuckle);
128
+
129
+ // --- PANTS (rows 30-39) ---
130
+ for (let y = 30; y <= 39; y++) {
131
+ for (let i = 14; i <= 21; i++) px(ctx, i, y, i <= 15 ? c.pantsDark : c.pants);
132
+ for (let i = 25; i <= 32; i++) px(ctx, i, y, i >= 31 ? c.pantsDark : c.pants);
133
+ px(ctx, 21, y, c.pantsDark); px(ctx, 25, y, c.pantsDark);
134
+ }
135
+
136
+ // --- SHOES (rows 40-43) ---
137
+ for (let i = 13; i <= 22; i++) { px(ctx, i, 40, c.shoe); px(ctx, i, 41, c.shoe); }
138
+ for (let i = 13; i <= 22; i++) px(ctx, i, 42, i <= 14 ? c.shoeLight : c.shoe);
139
+ hspan(ctx, 13, 22, 43, c.shoeLight);
140
+ for (let i = 24; i <= 33; i++) { px(ctx, i, 40, c.shoe); px(ctx, i, 41, c.shoe); }
141
+ for (let i = 24; i <= 33; i++) px(ctx, i, 42, i >= 32 ? c.shoeLight : c.shoe);
142
+ hspan(ctx, 24, 33, 43, c.shoeLight);
143
+ }
144
+
145
+ function drawCharacterIdle(ctx: CanvasRenderingContext2D, c: CharacterColors) {
146
+ drawHead(ctx, c, "neutral");
147
+ drawBody(ctx, c);
148
+
149
+ // Left arm at side — sleeve (3px wide)
150
+ for (let y = 18; y <= 22; y++) { px(ctx, 10, y, c.shirtDark); px(ctx, 11, y, c.shirt); px(ctx, 12, y, c.shirt); }
151
+ // Left forearm (3px wide with shadow)
152
+ for (let y = 23; y <= 27; y++) { px(ctx, 9, y, c.skinShadow); px(ctx, 10, y, c.skin); px(ctx, 11, y, c.skin); }
153
+ // Left hand (4px wide)
154
+ px(ctx, 8, 28, c.skin); px(ctx, 9, 28, c.skin); px(ctx, 10, 28, c.skin); px(ctx, 11, 28, c.skin);
155
+ px(ctx, 8, 29, c.skinShadow); px(ctx, 9, 29, c.skinShadow); px(ctx, 10, 29, c.skin);
156
+
157
+ // Right arm at side — sleeve (3px wide)
158
+ for (let y = 18; y <= 22; y++) { px(ctx, 34, y, c.shirt); px(ctx, 35, y, c.shirt); px(ctx, 36, y, c.shirtDark); }
159
+ // Right forearm (3px wide with shadow)
160
+ for (let y = 23; y <= 27; y++) { px(ctx, 35, y, c.skin); px(ctx, 36, y, c.skin); px(ctx, 37, y, c.skinShadow); }
161
+ // Right hand (4px wide)
162
+ px(ctx, 35, 28, c.skin); px(ctx, 36, 28, c.skin); px(ctx, 37, 28, c.skin); px(ctx, 38, 28, c.skin);
163
+ px(ctx, 36, 29, c.skin); px(ctx, 37, 29, c.skinShadow); px(ctx, 38, 29, c.skinShadow);
164
+ }
165
+
166
+ function drawCharacterWorking(ctx: CanvasRenderingContext2D, c: CharacterColors, frame: 0 | 1) {
167
+ drawHead(ctx, c, "focused");
168
+ drawBody(ctx, c);
169
+
170
+ // Arms forward (typing) — 3px wide sleeves + forearms
171
+ if (frame === 0) {
172
+ // Left arm: sleeve reaching forward
173
+ for (let y = 18; y <= 20; y++) { px(ctx, 10, y, c.shirtDark); px(ctx, 11, y, c.shirt); px(ctx, 12, y, c.shirt); }
174
+ // Left forearm
175
+ for (let y = 21; y <= 24; y++) { px(ctx, 9, y, c.skinShadow); px(ctx, 10, y, c.skin); px(ctx, 11, y, c.skin); }
176
+ // Left hand on keyboard
177
+ px(ctx, 10, 25, c.skin); px(ctx, 11, 25, c.skin); px(ctx, 12, 25, c.skin); px(ctx, 13, 25, c.skin);
178
+ // Right arm
179
+ for (let y = 18; y <= 20; y++) { px(ctx, 34, y, c.shirt); px(ctx, 35, y, c.shirt); px(ctx, 36, y, c.shirtDark); }
180
+ for (let y = 21; y <= 24; y++) { px(ctx, 35, y, c.skin); px(ctx, 36, y, c.skin); px(ctx, 37, y, c.skinShadow); }
181
+ px(ctx, 33, 25, c.skin); px(ctx, 34, 25, c.skin); px(ctx, 35, 25, c.skin); px(ctx, 36, 25, c.skin);
182
+ } else {
183
+ // Left arm: slightly raised (keystroke)
184
+ for (let y = 18; y <= 20; y++) { px(ctx, 10, y, c.shirtDark); px(ctx, 11, y, c.shirt); px(ctx, 12, y, c.shirt); }
185
+ for (let y = 21; y <= 23; y++) { px(ctx, 9, y, c.skinShadow); px(ctx, 10, y, c.skin); px(ctx, 11, y, c.skin); }
186
+ px(ctx, 10, 24, c.skin); px(ctx, 11, 24, c.skin); px(ctx, 12, 24, c.skin); px(ctx, 13, 24, c.skin);
187
+ // Right arm
188
+ for (let y = 18; y <= 20; y++) { px(ctx, 34, y, c.shirt); px(ctx, 35, y, c.shirt); px(ctx, 36, y, c.shirtDark); }
189
+ for (let y = 21; y <= 23; y++) { px(ctx, 35, y, c.skin); px(ctx, 36, y, c.skin); px(ctx, 37, y, c.skinShadow); }
190
+ px(ctx, 33, 24, c.skin); px(ctx, 34, 24, c.skin); px(ctx, 35, 24, c.skin); px(ctx, 36, 24, c.skin);
191
+ }
192
+ }
193
+
194
+ function drawCharacterDone(ctx: CanvasRenderingContext2D, c: CharacterColors) {
195
+ drawHead(ctx, c, "smile");
196
+ drawBody(ctx, c);
197
+
198
+ // Arms raised (celebration) — 3px wide, diagonal up
199
+ // Left sleeve
200
+ px(ctx, 10, 18, c.shirtDark); px(ctx, 11, 18, c.shirt); px(ctx, 12, 18, c.shirt);
201
+ px(ctx, 10, 17, c.shirt); px(ctx, 11, 17, c.shirt);
202
+ // Left arm going up-left (3px wide diagonal)
203
+ px(ctx, 9, 16, c.skin); px(ctx, 10, 16, c.skin); px(ctx, 10, 15, c.skinShadow);
204
+ px(ctx, 8, 14, c.skin); px(ctx, 9, 14, c.skin); px(ctx, 9, 13, c.skinShadow);
205
+ px(ctx, 7, 12, c.skin); px(ctx, 8, 12, c.skin);
206
+ px(ctx, 6, 10, c.skin); px(ctx, 7, 10, c.skin); px(ctx, 7, 11, c.skin);
207
+ px(ctx, 5, 8, c.skin); px(ctx, 6, 8, c.skin); px(ctx, 6, 9, c.skin);
208
+
209
+ // Right sleeve
210
+ px(ctx, 34, 18, c.shirt); px(ctx, 35, 18, c.shirt); px(ctx, 36, 18, c.shirtDark);
211
+ px(ctx, 35, 17, c.shirt); px(ctx, 36, 17, c.shirt);
212
+ // Right arm going up-right
213
+ px(ctx, 36, 16, c.skin); px(ctx, 37, 16, c.skin); px(ctx, 36, 15, c.skinShadow);
214
+ px(ctx, 37, 14, c.skin); px(ctx, 38, 14, c.skin); px(ctx, 37, 13, c.skinShadow);
215
+ px(ctx, 38, 12, c.skin); px(ctx, 39, 12, c.skin);
216
+ px(ctx, 39, 10, c.skin); px(ctx, 40, 10, c.skin); px(ctx, 39, 11, c.skin);
217
+ px(ctx, 40, 8, c.skin); px(ctx, 41, 8, c.skin); px(ctx, 40, 9, c.skin);
218
+ }
219
+
220
+ export interface CharacterTextures {
221
+ idle: Texture;
222
+ working: [Texture, Texture];
223
+ done: Texture;
224
+ checkpoint: Texture;
225
+ }
226
+
227
+ export function generateCharacterTextures(colors: CharacterColors): CharacterTextures {
228
+ const size = 48;
229
+
230
+ function makeFrame(drawFn: (ctx: CanvasRenderingContext2D) => void): Texture {
231
+ const [canvas, ctx] = createCanvas(size, size);
232
+ drawFn(ctx);
233
+ return new Texture({ source: new CanvasSource({ resource: canvas, scaleMode: "nearest" }) });
234
+ }
235
+
236
+ return {
237
+ idle: makeFrame((ctx) => drawCharacterIdle(ctx, colors)),
238
+ working: [
239
+ makeFrame((ctx) => drawCharacterWorking(ctx, colors, 0)),
240
+ makeFrame((ctx) => drawCharacterWorking(ctx, colors, 1)),
241
+ ],
242
+ done: makeFrame((ctx) => drawCharacterDone(ctx, colors)),
243
+ checkpoint: makeFrame((ctx) => drawCharacterIdle(ctx, colors)),
244
+ };
245
+ }
246
+
247
+ const textureCache = new Map<number, CharacterTextures>();
248
+
249
+ export function getCharacterTextures(variantIndex: number, colors: CharacterColors): CharacterTextures {
250
+ if (!textureCache.has(variantIndex)) {
251
+ textureCache.set(variantIndex, generateCharacterTextures(colors));
252
+ }
253
+ return textureCache.get(variantIndex)!;
254
+ }
@@ -0,0 +1,210 @@
1
+ import type { Plugin, ViteDevServer } from "vite";
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ import type { Server, IncomingMessage } from "node:http";
4
+ import type { Duplex } from "node:stream";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { parse as parseYaml } from "yaml";
8
+ import type { DepartmentInfo, DepartmentState, WsMessage } from "../types/state";
9
+
10
+ function resolveDepartmentsDir(): string {
11
+ const candidates = [
12
+ path.resolve(process.cwd(), "../departments"), // started from dashboard/
13
+ path.resolve(process.cwd(), "departments"), // started from project root
14
+ ];
15
+ for (const c of candidates) {
16
+ if (fs.existsSync(c)) return c;
17
+ }
18
+ return path.resolve(process.cwd(), "../departments"); // default (will be created on demand)
19
+ }
20
+
21
+ function discoverDepartments(departmentsDir: string): DepartmentInfo[] {
22
+ if (!fs.existsSync(departmentsDir)) return [];
23
+
24
+ const entries = fs.readdirSync(departmentsDir, { withFileTypes: true });
25
+ const departments: DepartmentInfo[] = [];
26
+
27
+ for (const entry of entries) {
28
+ if (!entry.isDirectory()) continue;
29
+ if (entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
30
+
31
+ const yamlPath = path.join(departmentsDir, entry.name, "department.yaml");
32
+ if (fs.existsSync(yamlPath)) {
33
+ try {
34
+ const raw = fs.readFileSync(yamlPath, "utf-8");
35
+ const parsed = parseYaml(raw);
36
+ const s = parsed?.department;
37
+ if (s) {
38
+ departments.push({
39
+ code: typeof s.code === "string" ? s.code : entry.name,
40
+ name: typeof s.name === "string" ? s.name : entry.name,
41
+ description: typeof s.description === "string" ? s.description : "",
42
+ icon: typeof s.icon === "string" ? s.icon : "\u{1F4CB}",
43
+ agents: Array.isArray(s.agents) ? (s.agents as unknown[]).filter((a): a is string => typeof a === "string") : [],
44
+ });
45
+ continue;
46
+ }
47
+ } catch {
48
+ // Fall through to default
49
+ }
50
+ }
51
+
52
+ // No department.yaml or invalid YAML — use directory name as fallback
53
+ departments.push({
54
+ code: entry.name,
55
+ name: entry.name,
56
+ description: "",
57
+ icon: "\u{1F4CB}",
58
+ agents: [],
59
+ });
60
+ }
61
+
62
+ return departments;
63
+ }
64
+
65
+ function isValidState(data: unknown): data is DepartmentState {
66
+ if (!data || typeof data !== "object") return false;
67
+ const d = data as Record<string, unknown>;
68
+ return (
69
+ typeof d.status === "string" &&
70
+ d.step != null && typeof d.step === "object" &&
71
+ Array.isArray(d.agents)
72
+ );
73
+ }
74
+
75
+ function readActiveStates(departmentsDir: string): Record<string, DepartmentState> {
76
+ const states: Record<string, DepartmentState> = {};
77
+ if (!fs.existsSync(departmentsDir)) return states;
78
+
79
+ const entries = fs.readdirSync(departmentsDir, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ if (!entry.isDirectory()) continue;
82
+ const statePath = path.join(departmentsDir, entry.name, "state.json");
83
+ if (!fs.existsSync(statePath)) continue;
84
+
85
+ try {
86
+ const raw = fs.readFileSync(statePath, "utf-8");
87
+ const parsed = JSON.parse(raw);
88
+ if (isValidState(parsed)) {
89
+ states[entry.name] = parsed;
90
+ }
91
+ } catch {
92
+ // Skip invalid JSON
93
+ }
94
+ }
95
+
96
+ return states;
97
+ }
98
+
99
+ function buildSnapshot(departmentsDir: string): WsMessage {
100
+ return {
101
+ type: "SNAPSHOT",
102
+ departments: discoverDepartments(departmentsDir),
103
+ activeStates: readActiveStates(departmentsDir),
104
+ };
105
+ }
106
+
107
+ function broadcast(wss: WebSocketServer, msg: WsMessage) {
108
+ const data = JSON.stringify(msg);
109
+ for (const client of wss.clients) {
110
+ if (client.readyState === WebSocket.OPEN) {
111
+ client.send(data);
112
+ }
113
+ }
114
+ }
115
+
116
+ export function departmentWatcherPlugin(): Plugin {
117
+ return {
118
+ name: "department-watcher",
119
+ configureServer(server: ViteDevServer) {
120
+ const departmentsDir = resolveDepartmentsDir();
121
+ server.config.logger.info(`[department-watcher] departments dir: ${departmentsDir}`);
122
+
123
+ // Create WebSocket server with noServer to avoid intercepting Vite's HMR
124
+ const wss = new WebSocketServer({ noServer: true });
125
+ (server.httpServer as Server).on("upgrade", (req: IncomingMessage, socket: Duplex, head: Buffer) => {
126
+ if (req.url === "/__departments_ws") {
127
+ wss.handleUpgrade(req, socket, head, (ws) => {
128
+ wss.emit("connection", ws, req);
129
+ });
130
+ }
131
+ // Let Vite handle all other upgrade requests (HMR)
132
+ });
133
+
134
+ // Send snapshot on new connection
135
+ wss.on("connection", (ws) => {
136
+ ws.send(JSON.stringify(buildSnapshot(departmentsDir)));
137
+ });
138
+
139
+ // Ensure departments directory exists
140
+ if (!fs.existsSync(departmentsDir)) {
141
+ fs.mkdirSync(departmentsDir, { recursive: true });
142
+ }
143
+
144
+ // Watch state.json files using Vite's built-in chokidar watcher
145
+ const stateGlob = path.join(departmentsDir, "*/state.json").replace(/\\/g, "/");
146
+ server.watcher.add(stateGlob);
147
+
148
+ // Debounce timers per department to avoid reading partial writes
149
+ const changeTimers = new Map<string, ReturnType<typeof setTimeout>>();
150
+
151
+ // Also watch for new department.yaml files
152
+ const yamlGlob = path.join(departmentsDir, "*/department.yaml").replace(/\\/g, "/");
153
+ server.watcher.add(yamlGlob);
154
+
155
+ server.watcher.on("add", (filePath: string) => {
156
+ if (filePath.endsWith("state.json")) {
157
+ const departmentName = extractDepartmentName(filePath, departmentsDir);
158
+ if (!departmentName) return;
159
+ clearTimeout(changeTimers.get(departmentName));
160
+ changeTimers.set(departmentName, setTimeout(() => {
161
+ try {
162
+ const raw = fs.readFileSync(filePath, "utf-8");
163
+ const state: DepartmentState = JSON.parse(raw);
164
+ broadcast(wss, { type: "DEPARTMENT_ACTIVE", department: departmentName, state });
165
+ } catch { /* skip */ }
166
+ }, 50));
167
+ } else if (filePath.endsWith("department.yaml")) {
168
+ broadcast(wss, buildSnapshot(departmentsDir));
169
+ }
170
+ });
171
+
172
+ server.watcher.on("change", (filePath: string) => {
173
+ if (filePath.endsWith("state.json")) {
174
+ const departmentName = extractDepartmentName(filePath, departmentsDir);
175
+ if (!departmentName) return;
176
+ clearTimeout(changeTimers.get(departmentName));
177
+ changeTimers.set(departmentName, setTimeout(() => {
178
+ try {
179
+ const raw = fs.readFileSync(filePath, "utf-8");
180
+ const state: DepartmentState = JSON.parse(raw);
181
+ broadcast(wss, { type: "DEPARTMENT_UPDATE", department: departmentName, state });
182
+ } catch { /* skip */ }
183
+ }, 50));
184
+ } else if (filePath.endsWith("department.yaml")) {
185
+ broadcast(wss, buildSnapshot(departmentsDir));
186
+ }
187
+ });
188
+
189
+ server.watcher.on("unlink", (filePath: string) => {
190
+ if (filePath.endsWith("state.json")) {
191
+ const departmentName = extractDepartmentName(filePath, departmentsDir);
192
+ if (!departmentName) return;
193
+ clearTimeout(changeTimers.get(departmentName));
194
+ changeTimers.delete(departmentName);
195
+ broadcast(wss, { type: "DEPARTMENT_INACTIVE", department: departmentName });
196
+ } else if (filePath.endsWith("department.yaml")) {
197
+ broadcast(wss, buildSnapshot(departmentsDir));
198
+ }
199
+ });
200
+ },
201
+ };
202
+ }
203
+
204
+ function extractDepartmentName(filePath: string, departmentsDir: string): string | null {
205
+ const normalized = filePath.replace(/\\/g, "/");
206
+ const normalizedBase = departmentsDir.replace(/\\/g, "/");
207
+ const relative = normalized.replace(normalizedBase + "/", "");
208
+ const parts = relative.split("/");
209
+ return parts.length >= 2 ? parts[0] : null;
210
+ }