create-workframe 0.1.4 → 0.1.6

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 (182) hide show
  1. package/.dockerignore +22 -22
  2. package/.gitignore +73 -73
  3. package/LICENSE +201 -201
  4. package/NOTICE +12 -12
  5. package/README.md +40 -40
  6. package/SECURITY.md +38 -38
  7. package/bin/create-workframe.js +2816 -2814
  8. package/bin/workframe.js +329 -329
  9. package/docs/workspace-instructions/WORKFRAME_DISCORD.md +20 -20
  10. package/docs/workspace-instructions/WORKFRAME_DOCUMENTS_AND_ARTIFACTS.md +20 -20
  11. package/docs/workspace-instructions/WORKFRAME_KANBAN.md +20 -20
  12. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +21 -21
  13. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +29 -29
  14. package/docs/workspace-instructions/WORKFRAME_TELEGRAM.md +19 -19
  15. package/package.json +64 -64
  16. package/profiles/README.md +15 -15
  17. package/profiles/architect/AGENTS.md +29 -29
  18. package/profiles/architect/SOUL.md +44 -44
  19. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/designer/AGENTS.md +26 -26
  21. package/profiles/designer/SOUL.md +31 -31
  22. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  23. package/profiles/dev/AGENTS.md +28 -28
  24. package/profiles/dev/SOUL.md +31 -31
  25. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  26. package/profiles/docs/AGENTS.md +27 -27
  27. package/profiles/docs/SOUL.md +30 -30
  28. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  29. package/profiles/research/AGENTS.md +26 -26
  30. package/profiles/research/SOUL.md +31 -31
  31. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  32. package/profiles/visionary/AGENTS.md +25 -25
  33. package/profiles/visionary/SOUL.md +31 -31
  34. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  35. package/profiles/workframe-agent/AGENTS.md +37 -37
  36. package/profiles/workframe-agent/SETUP.md +185 -185
  37. package/profiles/workframe-agent/SOUL.md +61 -61
  38. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  39. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  40. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  41. package/prompts/WORKFRAME_PROMPT_TEMPLATES.md +16 -16
  42. package/rules/.hermes.md +11 -11
  43. package/rules/AGENTS.md +22 -22
  44. package/rules/workspace-README.md +5 -5
  45. package/scripts/apply-update-workframe.sh +91 -21
  46. package/scripts/bundle-workframe-ui.mjs +83 -83
  47. package/scripts/create_workframe_scaffold.py +648 -648
  48. package/scripts/ensure-compose-host-paths.mjs +51 -51
  49. package/scripts/lib/install-identity.mjs +212 -212
  50. package/scripts/lib/workframe-registry.mjs +290 -290
  51. package/scripts/new-project.mjs +68 -68
  52. package/scripts/select_agent_pack.py +31 -31
  53. package/scripts/set-compose-public-url.mjs +92 -92
  54. package/scripts/sync-canonical-to-package.mjs +164 -164
  55. package/shared/WORKFRAME_AGENT_LIBRARY.md +31 -31
  56. package/shared/WORKFRAME_AGENT_OPERATIONS.md +29 -29
  57. package/shared/WORKFRAME_AGENT_PACKS.json +64 -64
  58. package/shared/WORKFRAME_AGENT_PACKS.yaml +20 -20
  59. package/shared/WORKFRAME_CHAT_PERMISSION_MODEL.md +20 -20
  60. package/shared/WORKFRAME_HANDOFF_SCHEMA.md +25 -25
  61. package/shared/WORKFRAME_SKILL_CURATION.md +27 -27
  62. package/workframe-api/Dockerfile +14 -14
  63. package/workframe-api/README.md +24 -24
  64. package/workframe-api/action_proxy.py +131 -131
  65. package/workframe-api/auth_rate_limit.py +49 -49
  66. package/workframe-api/credential_vault.py +445 -445
  67. package/workframe-api/data/avatar-catalog.json +41 -41
  68. package/workframe-api/data/logo-catalog.json +14 -14
  69. package/workframe-api/data/user-avatar-catalog.json +18 -18
  70. package/workframe-api/email_sender.py +220 -220
  71. package/workframe-api/google_auth.py +90 -90
  72. package/workframe-api/install_api.py +359 -359
  73. package/workframe-api/internal_proxy_auth.py +150 -150
  74. package/workframe-api/llm_proxy.py +280 -280
  75. package/workframe-api/oidc_jwt.py +108 -108
  76. package/workframe-api/package.json +12 -12
  77. package/workframe-api/platform_auth.py +194 -194
  78. package/workframe-api/profile_secret_policy.py +86 -86
  79. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  80. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  81. package/workframe-api/public/assets/index-DglUqFB_.js +8 -8
  82. package/workframe-api/public/index.html +12 -12
  83. package/workframe-api/requirements.txt +2 -2
  84. package/workframe-api/server.py +19866 -19774
  85. package/workframe-api/site_meta.py +271 -271
  86. package/workframe-api/stack_config.py +427 -427
  87. package/workframe-api/test_billing_provider.py +24 -0
  88. package/workframe-api/time-bind-chat.py +99 -99
  89. package/workframe-api/turn_credentials.py +226 -226
  90. package/workframe-api/updates.py +506 -506
  91. package/workframe-api/vault_kek.py +159 -159
  92. package/workframe-api/zk_auth.py +633 -633
  93. package/workframe-supervisor/Dockerfile +11 -11
  94. package/workframe-supervisor/profile_secret_policy.py +76 -76
  95. package/workframe-supervisor/server.py +860 -860
  96. package/workframe-ui/docker/nginx.conf +85 -85
  97. package/workframe-ui/public/assets/{arc-BskqxLmE.js → arc-DoYr2T5c.js} +1 -1
  98. package/workframe-ui/public/assets/architecture-7EHR7CIX-CmDi28hh.js +1 -0
  99. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B3eGdqvq.js → architectureDiagram-3BPJPVTR-Clher08G.js} +1 -1
  100. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-CzFZG7uX.js → blockDiagram-GPEHLZMM-Bdw9txrZ.js} +1 -1
  101. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BS-gGyCH.js → c4Diagram-AAUBKEIU-DlI_h_uo.js} +1 -1
  102. package/workframe-ui/public/assets/channel-DZYVoEdD.js +1 -0
  103. package/workframe-ui/public/assets/{chunk-2J33WTMH-Cjvqf7c9.js → chunk-2J33WTMH-ol-uwoeq.js} +1 -1
  104. package/workframe-ui/public/assets/{chunk-3OPIFGDE--eUuL8AK.js → chunk-3OPIFGDE-7eDhBen0.js} +1 -1
  105. package/workframe-ui/public/assets/{chunk-4BX2VUAB-s9lHUFKL.js → chunk-4BX2VUAB-xPORD7Js.js} +1 -1
  106. package/workframe-ui/public/assets/{chunk-55IACEB6-D1McA5nz.js → chunk-55IACEB6-Cwd7dr1z.js} +1 -1
  107. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DIstLfU2.js → chunk-5ZQYHXKU-Ckt4Sjy1.js} +1 -1
  108. package/workframe-ui/public/assets/{chunk-727SXJPM-Cd0GD2CO.js → chunk-727SXJPM-sMk1op_G.js} +1 -1
  109. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-gc5Ra6G4.js → chunk-AQP2D5EJ-CMgSvr6H.js} +1 -1
  110. package/workframe-ui/public/assets/{chunk-BSJP7CBP-Cg5QfihJ.js → chunk-BSJP7CBP-Dx7GN0T4.js} +1 -1
  111. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D_gobbhY.js → chunk-CSCIHK7Q--OCu86u0.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-FMBD7UC4-B6eLY9_d.js → chunk-FMBD7UC4-CejmwJpz.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-KSCS5N6A-BGroAeW2.js → chunk-KSCS5N6A-CvOj2Yvp.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-34xv7WgT.js → chunk-L5ZTLDWV-B0nGBjLU.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-LZXEDZCA-XJBctFZw.js → chunk-LZXEDZCA-Bdzvu227.js} +2 -2
  116. package/workframe-ui/public/assets/{chunk-ND2GUHAM-nTOZpln8.js → chunk-ND2GUHAM-CGR07jTY.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BBrzcaPH.js → chunk-NZK2D7GU-CBojDPNC.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-O5CBEL6O-Cntncx6L.js → chunk-O5CBEL6O-DBin5MPF.js} +1 -1
  119. package/workframe-ui/public/assets/chunk-QZHKN3VN-DwiajAaK.js +1 -0
  120. package/workframe-ui/public/assets/chunk-WU5MYG2G-Bz8vA5s7.js +1 -0
  121. package/workframe-ui/public/assets/{chunk-XPW4576I-DSbtd0cg.js → chunk-XPW4576I-TD7I7pcb.js} +1 -1
  122. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-DQiq8od0.js +1 -0
  123. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-DQiq8od0.js +1 -0
  124. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-lmYstEMQ.js → cose-bilkent-S5V4N54A-Bmm5x5xm.js} +1 -1
  125. package/workframe-ui/public/assets/{dagre-BM42HDAG-Bdkbdul2.js → dagre-BM42HDAG-zU2r0aq2.js} +1 -1
  126. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Clb9kSXe.js → diagram-2AECGRRQ-AL01qukm.js} +1 -1
  127. package/workframe-ui/public/assets/{diagram-5GNKFQAL-BApg0yec.js → diagram-5GNKFQAL-DKi1xSMg.js} +1 -1
  128. package/workframe-ui/public/assets/{diagram-KO2AKTUF-peZjDRV6.js → diagram-KO2AKTUF-DQJDQ-f-.js} +1 -1
  129. package/workframe-ui/public/assets/{diagram-LMA3HP47-DZm_1pjc.js → diagram-LMA3HP47-B1oisGUj.js} +1 -1
  130. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DP_N6fYL.js → diagram-OG6HWLK6-DjpJKffk.js} +1 -1
  131. package/workframe-ui/public/assets/{dist-CqP43hXK.js → dist-Jk20AnQG.js} +1 -1
  132. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-Blfs6kLv.js → erDiagram-TEJ5UH35-Vu8RtLUf.js} +1 -1
  133. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BXUnTe0c.js +1 -0
  134. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CcmMOkwH.js → flowDiagram-I6XJVG4X-CHAXEhbR.js} +1 -1
  135. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ByoO3dF7.js → ganttDiagram-6RSMTGT7-DhiEkfH8.js} +1 -1
  136. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-DCyTpmPs.js → gitGraph-WXDBUCRP-Cuq44XvU.js} +1 -1
  137. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-CvLYB55b.js → gitGraphDiagram-PVQCEYII-B7kgReRt.js} +1 -1
  138. package/workframe-ui/public/assets/index-72-j0sA5.js +129 -0
  139. package/workframe-ui/public/assets/index-auJ0bLMU.css +1 -0
  140. package/workframe-ui/public/assets/{info-J43DQDTF-CfSRIfOI.js → info-J43DQDTF-oV4b3M4f.js} +1 -1
  141. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-DwS44DS6.js → infoDiagram-5YYISTIA-CthN4b6o.js} +1 -1
  142. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-MRlyodyU.js → ishikawaDiagram-YF4QCWOH-oEGS3TKg.js} +1 -1
  143. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-sq7awk87.js → journeyDiagram-JHISSGLW-CXJsWmKF.js} +1 -1
  144. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CH6HbcWu.js → kanban-definition-UN3LZRKU-DTPo8yHW.js} +1 -1
  145. package/workframe-ui/public/assets/{line-DRBjFmUW.js → line-Bm9Pmcnz.js} +1 -1
  146. package/workframe-ui/public/assets/{linear-BR0LunjE.js → linear-Cn-uD6eX.js} +1 -1
  147. package/workframe-ui/public/assets/{mermaid-parser.core-Cl1GjHkT.js → mermaid-parser.core-BBVSTQZ4.js} +2 -2
  148. package/workframe-ui/public/assets/{mermaid.core-DsiKK1kE.js → mermaid.core-DE668nUy.js} +3 -3
  149. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DBvi3OpW.js → mindmap-definition-RKZ34NQL-4qVsiwCf.js} +1 -1
  150. package/workframe-ui/public/assets/{packet-YPE3B663-CPDgtUHg.js → packet-YPE3B663-CqJeoiFG.js} +1 -1
  151. package/workframe-ui/public/assets/{pie-LRSECV5Y-BXJIPPDm.js → pie-LRSECV5Y-DcOqVssg.js} +1 -1
  152. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CZwLEK-o.js → pieDiagram-4H26LBE5-CosMnpAM.js} +1 -1
  153. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-B65bEUIQ.js → quadrantDiagram-W4KKPZXB-DtsnIK98.js} +1 -1
  154. package/workframe-ui/public/assets/{radar-GUYGQ44K-vXHkZzRK.js → radar-GUYGQ44K-BqLpJuWc.js} +1 -1
  155. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-BGWdvDfG.js → requirementDiagram-4Y6WPE33-Pb7cuX8_.js} +1 -1
  156. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-BT2oC8dV.js → sankeyDiagram-5OEKKPKP-BJBlMM1m.js} +1 -1
  157. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-45yj0Wtx.js → sequenceDiagram-3UESZ5HK-i4HnjP39.js} +1 -1
  158. package/workframe-ui/public/assets/{src-D-P8U2PB.js → src-DIhmc_XG.js} +1 -1
  159. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-SBrBJBZG.js → stateDiagram-AJRCARHV-Ba2iClPT.js} +1 -1
  160. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-aUpqRAhy.js +1 -0
  161. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DegmxXXj.js → timeline-definition-PNZ67QCA-CL4X9vSF.js} +1 -1
  162. package/workframe-ui/public/assets/{treeView-BLDUP644-BVy7CsCj.js → treeView-BLDUP644-BJrjnbMj.js} +1 -1
  163. package/workframe-ui/public/assets/{treemap-LRROVOQU-BD5Kb74J.js → treemap-LRROVOQU-CN6H54RF.js} +1 -1
  164. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-hwAkNbl3.js → vennDiagram-CIIHVFJN-Cee6sbUy.js} +1 -1
  165. package/workframe-ui/public/assets/{wardley-L42UT6IY-CBMPT8Y1.js → wardley-L42UT6IY-D0cXq60c.js} +1 -1
  166. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DXqihVlE.js → wardleyDiagram-YWT4CUSO-CaH9XkCx.js} +1 -1
  167. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-Cbb4LBzj.js → xychartDiagram-2RQKCTM6-BEisgFoI.js} +1 -1
  168. package/workframe-ui/public/favicon.svg +7 -7
  169. package/workframe-ui/public/icons.svg +24 -24
  170. package/workframe-ui/public/index.html +47 -50
  171. package/workframe-ui/public/manifest.webmanifest +18 -18
  172. package/workframe-ui/public/workframe-config.json +3 -3
  173. package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +0 -1
  174. package/workframe-ui/public/assets/channel-Baa20pIs.js +0 -1
  175. package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +0 -1
  176. package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +0 -1
  177. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +0 -1
  178. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +0 -1
  179. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +0 -1
  180. package/workframe-ui/public/assets/index-BIfRGM8p.js +0 -133
  181. package/workframe-ui/public/assets/index-DpoUZAxh.css +0 -1
  182. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +0 -1
@@ -1,290 +1,290 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- export const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
5
-
6
- export function workframeDir(root) {
7
- return path.join(root, 'Agents', 'workframe');
8
- }
9
-
10
- export function agentsRegistryPath(root) {
11
- return path.join(workframeDir(root), 'agents.json');
12
- }
13
-
14
- export function avatarRegistryPath(root) {
15
- return path.join(workframeDir(root), 'avatar-registry.json');
16
- }
17
-
18
- export function ensureWorkframeDir(root) {
19
- fs.mkdirSync(workframeDir(root), { recursive: true });
20
- }
21
-
22
- export function loadAgentsRegistry(root) {
23
- const file = agentsRegistryPath(root);
24
- if (!fs.existsSync(file)) {
25
- return { version: 1, owner_profile: '', agents: {} };
26
- }
27
- return JSON.parse(fs.readFileSync(file, 'utf8'));
28
- }
29
-
30
- export function saveAgentsRegistry(root, data) {
31
- ensureWorkframeDir(root);
32
- fs.writeFileSync(agentsRegistryPath(root), `${JSON.stringify(data, null, 2)}\n`);
33
- }
34
-
35
- export function loadAvatarRegistry(root) {
36
- const file = avatarRegistryPath(root);
37
- if (!fs.existsSync(file)) {
38
- return { version: 1, weights: {}, assignments: {} };
39
- }
40
- return JSON.parse(fs.readFileSync(file, 'utf8'));
41
- }
42
-
43
- export function saveAvatarRegistry(root, data) {
44
- ensureWorkframeDir(root);
45
- fs.writeFileSync(avatarRegistryPath(root), `${JSON.stringify(data, null, 2)}\n`);
46
- }
47
-
48
- export function avatarCatalogPaths(root) {
49
- return [
50
- path.join(root, 'Files', 'packages', 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
51
- path.join(root, 'Files', 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
52
- path.join(root, 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
53
- path.join(root, 'packages', 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
54
- path.join(root, 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
55
- path.join(root, 'scripts', 'seed', 'assets', 'avatars', 'catalog.json'),
56
- path.join(root, 'workframe-ui', 'public', 'assets', 'agents', 'catalog.json'),
57
- path.join(root, 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
58
- ];
59
- }
60
-
61
- export function loadAvatarCatalog(root) {
62
- for (const candidate of avatarCatalogPaths(root)) {
63
- if (fs.existsSync(candidate)) {
64
- const data = JSON.parse(fs.readFileSync(candidate, 'utf8'));
65
- const base = String(data.public_base || '/assets/avatars').replace(/\/$/, '');
66
- const avatars = (data.avatars || []).map((row) => ({
67
- id: String(row.id),
68
- file: String(row.file || `${row.id}.png`),
69
- label: String(row.label || row.id),
70
- url: `${base}/${row.file || `${row.id}.png`}`,
71
- }));
72
- if (avatars.length) return { base, avatars };
73
- }
74
- }
75
- throw new Error('Avatar catalog not found (expected workframe-ui/public/assets/avatars/catalog.json)');
76
- }
77
-
78
- export function avatarUrlForId(catalog, avatarId) {
79
- const row = catalog.avatars.find((a) => a.id === avatarId);
80
- return row?.url ?? `${catalog.base}/${avatarId}.png`;
81
- }
82
-
83
- /** Weighted pick: prefer unassigned avatars, then lowest weight; random tie-break. */
84
- export function pickAvatarId(root, { avoidReuse = true } = {}) {
85
- const catalog = loadAvatarCatalog(root);
86
- const avatarRegistry = loadAvatarRegistry(root);
87
- const agentsRegistry = loadAgentsRegistry(root);
88
- const assigned = new Set(Object.values(avatarRegistry.assignments || {}));
89
- for (const row of Object.values(agentsRegistry.agents || {})) {
90
- if (row.avatar_id) assigned.add(row.avatar_id);
91
- }
92
-
93
- let pool = catalog.avatars;
94
- if (avoidReuse) {
95
- const unused = pool.filter((a) => !assigned.has(a.id));
96
- if (unused.length) pool = unused;
97
- }
98
-
99
- const weights = avatarRegistry.weights || {};
100
- let minWeight = Infinity;
101
- for (const avatar of pool) {
102
- const w = weights[avatar.id] ?? 0;
103
- if (w < minWeight) minWeight = w;
104
- }
105
- const candidates = pool.filter((a) => (weights[a.id] ?? 0) <= minWeight);
106
- const pick = candidates[Math.floor(Math.random() * candidates.length)];
107
- return pick.id;
108
- }
109
-
110
- export function assignAvatar(root, profile, avatarId) {
111
- const catalog = loadAvatarCatalog(root);
112
- if (!catalog.avatars.some((a) => a.id === avatarId)) {
113
- throw new Error(`Unknown avatar id: ${avatarId}`);
114
- }
115
- const avatarRegistry = loadAvatarRegistry(root);
116
- avatarRegistry.assignments = avatarRegistry.assignments || {};
117
- avatarRegistry.weights = avatarRegistry.weights || {};
118
- avatarRegistry.assignments[profile] = avatarId;
119
- avatarRegistry.weights[avatarId] = (avatarRegistry.weights[avatarId] ?? 0) + 1;
120
- saveAvatarRegistry(root, avatarRegistry);
121
- return avatarUrlForId(catalog, avatarId);
122
- }
123
-
124
- export function releaseAvatar(root, profile) {
125
- const avatarRegistry = loadAvatarRegistry(root);
126
- if (avatarRegistry.assignments?.[profile]) {
127
- delete avatarRegistry.assignments[profile];
128
- saveAvatarRegistry(root, avatarRegistry);
129
- }
130
- }
131
-
132
- export function upsertAgentRecord(root, profile, patch, ownerProfile) {
133
- const registry = loadAgentsRegistry(root);
134
- registry.owner_profile = registry.owner_profile || ownerProfile;
135
- registry.agents = registry.agents || {};
136
- const prev = registry.agents[profile] || {};
137
- const now = new Date().toISOString();
138
- registry.agents[profile] = {
139
- ...prev,
140
- ...patch,
141
- profile,
142
- owner: ownerProfile,
143
- updated_at: now,
144
- created_at: prev.created_at || now,
145
- };
146
- saveAgentsRegistry(root, registry);
147
- return registry.agents[profile];
148
- }
149
-
150
- export function removeAgentRecord(root, profile) {
151
- const registry = loadAgentsRegistry(root);
152
- if (registry.agents?.[profile]) {
153
- delete registry.agents[profile];
154
- saveAgentsRegistry(root, registry);
155
- }
156
- releaseAvatar(root, profile);
157
- }
158
-
159
- export function getAgentRecord(root, profile) {
160
- const registry = loadAgentsRegistry(root);
161
- return registry.agents?.[profile] ?? null;
162
- }
163
-
164
- export function readModelFromConfig(root, profile) {
165
- const cfgPath = path.join(root, 'Agents', 'profiles', profile, 'config.yaml');
166
- if (!fs.existsSync(cfgPath)) return null;
167
- const text = fs.readFileSync(cfgPath, 'utf8');
168
- const match = text.match(/^model:\s*\n(?:\s+.+\n)*?\s+default:\s*(.+)$/m);
169
- return match ? match[1].trim() : null;
170
- }
171
-
172
- const FORBIDDEN_CHILD_SKILLS = new Set(['botfather', 'crew-manager']);
173
-
174
- export function profileSkillsSeedDir(root, sourceSlug) {
175
- return path.join(root, 'scripts', 'seed', 'profiles', sourceSlug, 'skills');
176
- }
177
-
178
- export function profileSkillsDir(root, profile) {
179
- return path.join(root, 'Agents', 'profiles', profile, 'skills');
180
- }
181
-
182
- function copyDirRecursive(src, dest) {
183
- if (!fs.existsSync(src)) return false;
184
- fs.mkdirSync(dest, { recursive: true });
185
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
186
- const from = path.join(src, entry.name);
187
- const to = path.join(dest, entry.name);
188
- if (entry.isDirectory()) copyDirRecursive(from, to);
189
- else fs.copyFileSync(from, to);
190
- }
191
- return true;
192
- }
193
-
194
- function skillNameFromPath(skillsRoot, skillFile) {
195
- const rel = path.relative(skillsRoot, skillFile).replace(/\\/g, '/');
196
- const parts = rel.split('/');
197
- if (parts.length >= 2 && parts[parts.length - 1] === 'SKILL.md') {
198
- return parts[parts.length - 2];
199
- }
200
- return path.basename(path.dirname(skillFile));
201
- }
202
-
203
- export function listInstalledSkillIds(root, profile) {
204
- const skillsRoot = profileSkillsDir(root, profile);
205
- if (!fs.existsSync(skillsRoot)) return [];
206
- const ids = new Set();
207
- const walk = (dir) => {
208
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
209
- const full = path.join(dir, entry.name);
210
- if (entry.isDirectory()) walk(full);
211
- else if (entry.name === 'SKILL.md') ids.add(skillNameFromPath(skillsRoot, full));
212
- }
213
- };
214
- walk(skillsRoot);
215
- return [...ids].sort();
216
- }
217
-
218
- /** Copy skills from seed (or another seed slug). Never installs botfather on children. */
219
- export function installProfileSkills(root, targetProfile, { sourceSlug, skillsDir } = {}) {
220
- const dest = profileSkillsDir(root, targetProfile);
221
- let src = skillsDir ? (path.isAbsolute(skillsDir) ? skillsDir : path.join(root, skillsDir)) : null;
222
- if (!src) {
223
- const seedSlug = sourceSlug || targetProfile;
224
- src = profileSkillsSeedDir(root, seedSlug);
225
- }
226
- if (!fs.existsSync(src)) {
227
- return { installed: [], skipped: [], source: src };
228
- }
229
-
230
- const staging = path.join(dest, '.staging');
231
- fs.rmSync(staging, { recursive: true, force: true });
232
- copyDirRecursive(src, staging);
233
-
234
- const installed = [];
235
- const skipped = [];
236
- const walk = (dir) => {
237
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
238
- const full = path.join(dir, entry.name);
239
- if (entry.isDirectory()) walk(full);
240
- else if (entry.name === 'SKILL.md') {
241
- const id = skillNameFromPath(staging, full);
242
- if (FORBIDDEN_CHILD_SKILLS.has(id)) {
243
- skipped.push(id);
244
- fs.rmSync(path.dirname(full), { recursive: true, force: true });
245
- } else {
246
- installed.push(id);
247
- }
248
- }
249
- }
250
- };
251
- walk(staging);
252
-
253
- if (installed.length || skipped.length) {
254
- fs.mkdirSync(dest, { recursive: true });
255
- for (const entry of fs.readdirSync(staging, { withFileTypes: true })) {
256
- const from = path.join(staging, entry.name);
257
- const to = path.join(dest, entry.name);
258
- if (entry.isDirectory()) copyDirRecursive(from, to);
259
- else if (fs.existsSync(from)) fs.copyFileSync(from, to);
260
- }
261
- }
262
- fs.rmSync(staging, { recursive: true, force: true });
263
- return { installed: [...new Set(installed)].sort(), skipped: [...new Set(skipped)].sort(), source: src };
264
- }
265
-
266
- /** Remove botfather/crew-manager from a child profile after Hermes --clone. */
267
- export function stripForbiddenChildSkills(root, profile) {
268
- const skillsRoot = profileSkillsDir(root, profile);
269
- if (!fs.existsSync(skillsRoot)) return { removed: [] };
270
- const removed = [];
271
- const walk = (dir) => {
272
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
273
- const full = path.join(dir, entry.name);
274
- if (entry.isDirectory()) {
275
- if (entry.name === 'botfather' || entry.name === 'crew-manager') {
276
- fs.rmSync(full, { recursive: true, force: true });
277
- removed.push(entry.name);
278
- } else walk(full);
279
- } else if (entry.name === 'SKILL.md') {
280
- const id = skillNameFromPath(skillsRoot, full);
281
- if (FORBIDDEN_CHILD_SKILLS.has(id)) {
282
- fs.rmSync(path.dirname(full), { recursive: true, force: true });
283
- removed.push(id);
284
- }
285
- }
286
- }
287
- };
288
- walk(skillsRoot);
289
- return { removed: [...new Set(removed)].sort() };
290
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
5
+
6
+ export function workframeDir(root) {
7
+ return path.join(root, 'Agents', 'workframe');
8
+ }
9
+
10
+ export function agentsRegistryPath(root) {
11
+ return path.join(workframeDir(root), 'agents.json');
12
+ }
13
+
14
+ export function avatarRegistryPath(root) {
15
+ return path.join(workframeDir(root), 'avatar-registry.json');
16
+ }
17
+
18
+ export function ensureWorkframeDir(root) {
19
+ fs.mkdirSync(workframeDir(root), { recursive: true });
20
+ }
21
+
22
+ export function loadAgentsRegistry(root) {
23
+ const file = agentsRegistryPath(root);
24
+ if (!fs.existsSync(file)) {
25
+ return { version: 1, owner_profile: '', agents: {} };
26
+ }
27
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
28
+ }
29
+
30
+ export function saveAgentsRegistry(root, data) {
31
+ ensureWorkframeDir(root);
32
+ fs.writeFileSync(agentsRegistryPath(root), `${JSON.stringify(data, null, 2)}\n`);
33
+ }
34
+
35
+ export function loadAvatarRegistry(root) {
36
+ const file = avatarRegistryPath(root);
37
+ if (!fs.existsSync(file)) {
38
+ return { version: 1, weights: {}, assignments: {} };
39
+ }
40
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
41
+ }
42
+
43
+ export function saveAvatarRegistry(root, data) {
44
+ ensureWorkframeDir(root);
45
+ fs.writeFileSync(avatarRegistryPath(root), `${JSON.stringify(data, null, 2)}\n`);
46
+ }
47
+
48
+ export function avatarCatalogPaths(root) {
49
+ return [
50
+ path.join(root, 'Files', 'packages', 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
51
+ path.join(root, 'Files', 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
52
+ path.join(root, 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
53
+ path.join(root, 'packages', 'workframe-ui', 'public', 'assets', 'avatars', 'catalog.json'),
54
+ path.join(root, 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
55
+ path.join(root, 'scripts', 'seed', 'assets', 'avatars', 'catalog.json'),
56
+ path.join(root, 'workframe-ui', 'public', 'assets', 'agents', 'catalog.json'),
57
+ path.join(root, 'packages', 'create-workframe', 'shared', 'agent-avatars', 'catalog.json'),
58
+ ];
59
+ }
60
+
61
+ export function loadAvatarCatalog(root) {
62
+ for (const candidate of avatarCatalogPaths(root)) {
63
+ if (fs.existsSync(candidate)) {
64
+ const data = JSON.parse(fs.readFileSync(candidate, 'utf8'));
65
+ const base = String(data.public_base || '/assets/avatars').replace(/\/$/, '');
66
+ const avatars = (data.avatars || []).map((row) => ({
67
+ id: String(row.id),
68
+ file: String(row.file || `${row.id}.png`),
69
+ label: String(row.label || row.id),
70
+ url: `${base}/${row.file || `${row.id}.png`}`,
71
+ }));
72
+ if (avatars.length) return { base, avatars };
73
+ }
74
+ }
75
+ throw new Error('Avatar catalog not found (expected workframe-ui/public/assets/avatars/catalog.json)');
76
+ }
77
+
78
+ export function avatarUrlForId(catalog, avatarId) {
79
+ const row = catalog.avatars.find((a) => a.id === avatarId);
80
+ return row?.url ?? `${catalog.base}/${avatarId}.png`;
81
+ }
82
+
83
+ /** Weighted pick: prefer unassigned avatars, then lowest weight; random tie-break. */
84
+ export function pickAvatarId(root, { avoidReuse = true } = {}) {
85
+ const catalog = loadAvatarCatalog(root);
86
+ const avatarRegistry = loadAvatarRegistry(root);
87
+ const agentsRegistry = loadAgentsRegistry(root);
88
+ const assigned = new Set(Object.values(avatarRegistry.assignments || {}));
89
+ for (const row of Object.values(agentsRegistry.agents || {})) {
90
+ if (row.avatar_id) assigned.add(row.avatar_id);
91
+ }
92
+
93
+ let pool = catalog.avatars;
94
+ if (avoidReuse) {
95
+ const unused = pool.filter((a) => !assigned.has(a.id));
96
+ if (unused.length) pool = unused;
97
+ }
98
+
99
+ const weights = avatarRegistry.weights || {};
100
+ let minWeight = Infinity;
101
+ for (const avatar of pool) {
102
+ const w = weights[avatar.id] ?? 0;
103
+ if (w < minWeight) minWeight = w;
104
+ }
105
+ const candidates = pool.filter((a) => (weights[a.id] ?? 0) <= minWeight);
106
+ const pick = candidates[Math.floor(Math.random() * candidates.length)];
107
+ return pick.id;
108
+ }
109
+
110
+ export function assignAvatar(root, profile, avatarId) {
111
+ const catalog = loadAvatarCatalog(root);
112
+ if (!catalog.avatars.some((a) => a.id === avatarId)) {
113
+ throw new Error(`Unknown avatar id: ${avatarId}`);
114
+ }
115
+ const avatarRegistry = loadAvatarRegistry(root);
116
+ avatarRegistry.assignments = avatarRegistry.assignments || {};
117
+ avatarRegistry.weights = avatarRegistry.weights || {};
118
+ avatarRegistry.assignments[profile] = avatarId;
119
+ avatarRegistry.weights[avatarId] = (avatarRegistry.weights[avatarId] ?? 0) + 1;
120
+ saveAvatarRegistry(root, avatarRegistry);
121
+ return avatarUrlForId(catalog, avatarId);
122
+ }
123
+
124
+ export function releaseAvatar(root, profile) {
125
+ const avatarRegistry = loadAvatarRegistry(root);
126
+ if (avatarRegistry.assignments?.[profile]) {
127
+ delete avatarRegistry.assignments[profile];
128
+ saveAvatarRegistry(root, avatarRegistry);
129
+ }
130
+ }
131
+
132
+ export function upsertAgentRecord(root, profile, patch, ownerProfile) {
133
+ const registry = loadAgentsRegistry(root);
134
+ registry.owner_profile = registry.owner_profile || ownerProfile;
135
+ registry.agents = registry.agents || {};
136
+ const prev = registry.agents[profile] || {};
137
+ const now = new Date().toISOString();
138
+ registry.agents[profile] = {
139
+ ...prev,
140
+ ...patch,
141
+ profile,
142
+ owner: ownerProfile,
143
+ updated_at: now,
144
+ created_at: prev.created_at || now,
145
+ };
146
+ saveAgentsRegistry(root, registry);
147
+ return registry.agents[profile];
148
+ }
149
+
150
+ export function removeAgentRecord(root, profile) {
151
+ const registry = loadAgentsRegistry(root);
152
+ if (registry.agents?.[profile]) {
153
+ delete registry.agents[profile];
154
+ saveAgentsRegistry(root, registry);
155
+ }
156
+ releaseAvatar(root, profile);
157
+ }
158
+
159
+ export function getAgentRecord(root, profile) {
160
+ const registry = loadAgentsRegistry(root);
161
+ return registry.agents?.[profile] ?? null;
162
+ }
163
+
164
+ export function readModelFromConfig(root, profile) {
165
+ const cfgPath = path.join(root, 'Agents', 'profiles', profile, 'config.yaml');
166
+ if (!fs.existsSync(cfgPath)) return null;
167
+ const text = fs.readFileSync(cfgPath, 'utf8');
168
+ const match = text.match(/^model:\s*\n(?:\s+.+\n)*?\s+default:\s*(.+)$/m);
169
+ return match ? match[1].trim() : null;
170
+ }
171
+
172
+ const FORBIDDEN_CHILD_SKILLS = new Set(['botfather', 'crew-manager']);
173
+
174
+ export function profileSkillsSeedDir(root, sourceSlug) {
175
+ return path.join(root, 'scripts', 'seed', 'profiles', sourceSlug, 'skills');
176
+ }
177
+
178
+ export function profileSkillsDir(root, profile) {
179
+ return path.join(root, 'Agents', 'profiles', profile, 'skills');
180
+ }
181
+
182
+ function copyDirRecursive(src, dest) {
183
+ if (!fs.existsSync(src)) return false;
184
+ fs.mkdirSync(dest, { recursive: true });
185
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
186
+ const from = path.join(src, entry.name);
187
+ const to = path.join(dest, entry.name);
188
+ if (entry.isDirectory()) copyDirRecursive(from, to);
189
+ else fs.copyFileSync(from, to);
190
+ }
191
+ return true;
192
+ }
193
+
194
+ function skillNameFromPath(skillsRoot, skillFile) {
195
+ const rel = path.relative(skillsRoot, skillFile).replace(/\\/g, '/');
196
+ const parts = rel.split('/');
197
+ if (parts.length >= 2 && parts[parts.length - 1] === 'SKILL.md') {
198
+ return parts[parts.length - 2];
199
+ }
200
+ return path.basename(path.dirname(skillFile));
201
+ }
202
+
203
+ export function listInstalledSkillIds(root, profile) {
204
+ const skillsRoot = profileSkillsDir(root, profile);
205
+ if (!fs.existsSync(skillsRoot)) return [];
206
+ const ids = new Set();
207
+ const walk = (dir) => {
208
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
209
+ const full = path.join(dir, entry.name);
210
+ if (entry.isDirectory()) walk(full);
211
+ else if (entry.name === 'SKILL.md') ids.add(skillNameFromPath(skillsRoot, full));
212
+ }
213
+ };
214
+ walk(skillsRoot);
215
+ return [...ids].sort();
216
+ }
217
+
218
+ /** Copy skills from seed (or another seed slug). Never installs botfather on children. */
219
+ export function installProfileSkills(root, targetProfile, { sourceSlug, skillsDir } = {}) {
220
+ const dest = profileSkillsDir(root, targetProfile);
221
+ let src = skillsDir ? (path.isAbsolute(skillsDir) ? skillsDir : path.join(root, skillsDir)) : null;
222
+ if (!src) {
223
+ const seedSlug = sourceSlug || targetProfile;
224
+ src = profileSkillsSeedDir(root, seedSlug);
225
+ }
226
+ if (!fs.existsSync(src)) {
227
+ return { installed: [], skipped: [], source: src };
228
+ }
229
+
230
+ const staging = path.join(dest, '.staging');
231
+ fs.rmSync(staging, { recursive: true, force: true });
232
+ copyDirRecursive(src, staging);
233
+
234
+ const installed = [];
235
+ const skipped = [];
236
+ const walk = (dir) => {
237
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
238
+ const full = path.join(dir, entry.name);
239
+ if (entry.isDirectory()) walk(full);
240
+ else if (entry.name === 'SKILL.md') {
241
+ const id = skillNameFromPath(staging, full);
242
+ if (FORBIDDEN_CHILD_SKILLS.has(id)) {
243
+ skipped.push(id);
244
+ fs.rmSync(path.dirname(full), { recursive: true, force: true });
245
+ } else {
246
+ installed.push(id);
247
+ }
248
+ }
249
+ }
250
+ };
251
+ walk(staging);
252
+
253
+ if (installed.length || skipped.length) {
254
+ fs.mkdirSync(dest, { recursive: true });
255
+ for (const entry of fs.readdirSync(staging, { withFileTypes: true })) {
256
+ const from = path.join(staging, entry.name);
257
+ const to = path.join(dest, entry.name);
258
+ if (entry.isDirectory()) copyDirRecursive(from, to);
259
+ else if (fs.existsSync(from)) fs.copyFileSync(from, to);
260
+ }
261
+ }
262
+ fs.rmSync(staging, { recursive: true, force: true });
263
+ return { installed: [...new Set(installed)].sort(), skipped: [...new Set(skipped)].sort(), source: src };
264
+ }
265
+
266
+ /** Remove botfather/crew-manager from a child profile after Hermes --clone. */
267
+ export function stripForbiddenChildSkills(root, profile) {
268
+ const skillsRoot = profileSkillsDir(root, profile);
269
+ if (!fs.existsSync(skillsRoot)) return { removed: [] };
270
+ const removed = [];
271
+ const walk = (dir) => {
272
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
273
+ const full = path.join(dir, entry.name);
274
+ if (entry.isDirectory()) {
275
+ if (entry.name === 'botfather' || entry.name === 'crew-manager') {
276
+ fs.rmSync(full, { recursive: true, force: true });
277
+ removed.push(entry.name);
278
+ } else walk(full);
279
+ } else if (entry.name === 'SKILL.md') {
280
+ const id = skillNameFromPath(skillsRoot, full);
281
+ if (FORBIDDEN_CHILD_SKILLS.has(id)) {
282
+ fs.rmSync(path.dirname(full), { recursive: true, force: true });
283
+ removed.push(id);
284
+ }
285
+ }
286
+ }
287
+ };
288
+ walk(skillsRoot);
289
+ return { removed: [...new Set(removed)].sort() };
290
+ }