opc-agent 1.1.0 → 1.1.2

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 (142) hide show
  1. package/CHANGELOG.md +51 -51
  2. package/CONTRIBUTING.md +75 -75
  3. package/README.md +341 -101
  4. package/README.zh-CN.md +382 -55
  5. package/dist/channels/web.js +256 -256
  6. package/dist/cli.js +1 -32
  7. package/dist/deploy/hermes.js +22 -22
  8. package/dist/deploy/openclaw.js +31 -31
  9. package/dist/i18n/index.js +60 -9
  10. package/dist/templates/code-reviewer.js +5 -5
  11. package/dist/templates/customer-service.js +2 -2
  12. package/dist/templates/data-analyst.js +5 -5
  13. package/dist/templates/knowledge-base.js +2 -2
  14. package/dist/templates/sales-assistant.js +4 -4
  15. package/dist/templates/teacher.js +6 -6
  16. package/docs/.vitepress/config.ts +103 -92
  17. package/docs/api/cli.md +48 -48
  18. package/docs/api/oad-schema.md +64 -64
  19. package/docs/api/sdk.md +80 -80
  20. package/docs/guide/concepts.md +51 -51
  21. package/docs/guide/configuration.md +79 -79
  22. package/docs/guide/deployment.md +42 -42
  23. package/docs/guide/getting-started.md +44 -44
  24. package/docs/guide/templates.md +28 -28
  25. package/docs/guide/testing.md +84 -84
  26. package/docs/index.md +27 -27
  27. package/docs/zh/api/cli.md +54 -0
  28. package/docs/zh/api/oad-schema.md +87 -3
  29. package/docs/zh/api/sdk.md +102 -0
  30. package/docs/zh/guide/concepts.md +104 -28
  31. package/docs/zh/guide/configuration.md +135 -39
  32. package/docs/zh/guide/deployment.md +81 -3
  33. package/docs/zh/guide/getting-started.md +82 -58
  34. package/docs/zh/guide/templates.md +84 -22
  35. package/docs/zh/guide/testing.md +88 -18
  36. package/docs/zh/index.md +27 -27
  37. package/examples/customer-service-demo/README.md +90 -90
  38. package/examples/customer-service-demo/oad.yaml +107 -107
  39. package/package.json +1 -1
  40. package/src/analytics/index.ts +66 -66
  41. package/src/channels/discord.ts +192 -192
  42. package/src/channels/email.ts +177 -177
  43. package/src/channels/feishu.ts +236 -236
  44. package/src/channels/index.ts +15 -15
  45. package/src/channels/slack.ts +160 -160
  46. package/src/channels/telegram.ts +90 -90
  47. package/src/channels/voice.ts +106 -106
  48. package/src/channels/web.ts +596 -596
  49. package/src/channels/webhook.ts +199 -199
  50. package/src/channels/websocket.ts +87 -87
  51. package/src/channels/wechat.ts +149 -149
  52. package/src/cli.ts +1 -35
  53. package/src/core/a2a.ts +143 -143
  54. package/src/core/agent.ts +152 -152
  55. package/src/core/analytics-engine.ts +186 -186
  56. package/src/core/auth.ts +57 -57
  57. package/src/core/cache.ts +141 -141
  58. package/src/core/compose.ts +77 -77
  59. package/src/core/config.ts +14 -14
  60. package/src/core/errors.ts +148 -148
  61. package/src/core/hitl.ts +138 -138
  62. package/src/core/knowledge.ts +210 -210
  63. package/src/core/logger.ts +57 -57
  64. package/src/core/orchestrator.ts +215 -215
  65. package/src/core/performance.ts +187 -187
  66. package/src/core/rate-limiter.ts +128 -128
  67. package/src/core/room.ts +109 -109
  68. package/src/core/runtime.ts +152 -152
  69. package/src/core/sandbox.ts +101 -101
  70. package/src/core/security.ts +171 -171
  71. package/src/core/types.ts +68 -68
  72. package/src/core/versioning.ts +106 -106
  73. package/src/core/watch.ts +178 -178
  74. package/src/core/workflow.ts +235 -235
  75. package/src/deploy/hermes.ts +156 -156
  76. package/src/deploy/openclaw.ts +200 -200
  77. package/src/dtv/data.ts +29 -29
  78. package/src/dtv/trust.ts +43 -43
  79. package/src/dtv/value.ts +47 -47
  80. package/src/i18n/index.ts +216 -165
  81. package/src/index.ts +110 -110
  82. package/src/marketplace/index.ts +223 -223
  83. package/src/memory/deepbrain.ts +108 -108
  84. package/src/memory/index.ts +34 -34
  85. package/src/plugins/index.ts +208 -208
  86. package/src/providers/index.ts +183 -183
  87. package/src/schema/oad.ts +155 -155
  88. package/src/skills/base.ts +16 -16
  89. package/src/skills/document.ts +100 -100
  90. package/src/skills/http.ts +35 -35
  91. package/src/skills/index.ts +27 -27
  92. package/src/skills/scheduler.ts +80 -80
  93. package/src/skills/webhook-trigger.ts +59 -59
  94. package/src/templates/code-reviewer.ts +34 -34
  95. package/src/templates/customer-service.ts +80 -80
  96. package/src/templates/data-analyst.ts +70 -70
  97. package/src/templates/executive-assistant.ts +71 -71
  98. package/src/templates/financial-advisor.ts +60 -60
  99. package/src/templates/knowledge-base.ts +31 -31
  100. package/src/templates/legal-assistant.ts +71 -71
  101. package/src/templates/sales-assistant.ts +79 -79
  102. package/src/templates/teacher.ts +79 -79
  103. package/src/testing/index.ts +181 -181
  104. package/src/tools/calculator.ts +73 -73
  105. package/src/tools/datetime.ts +149 -149
  106. package/src/tools/json-transform.ts +187 -187
  107. package/src/tools/mcp.ts +76 -76
  108. package/src/tools/text-analysis.ts +116 -116
  109. package/templates/Dockerfile +15 -15
  110. package/templates/code-reviewer/README.md +27 -27
  111. package/templates/code-reviewer/oad.yaml +41 -41
  112. package/templates/customer-service/README.md +22 -22
  113. package/templates/customer-service/oad.yaml +36 -36
  114. package/templates/docker-compose.yml +21 -21
  115. package/templates/knowledge-base/README.md +28 -28
  116. package/templates/knowledge-base/oad.yaml +38 -38
  117. package/templates/sales-assistant/README.md +26 -26
  118. package/templates/sales-assistant/oad.yaml +43 -43
  119. package/tests/a2a.test.ts +66 -66
  120. package/tests/agent.test.ts +72 -72
  121. package/tests/analytics.test.ts +50 -50
  122. package/tests/channel.test.ts +39 -39
  123. package/tests/e2e.test.ts +134 -134
  124. package/tests/errors.test.ts +83 -83
  125. package/tests/hitl.test.ts +71 -71
  126. package/tests/i18n.test.ts +41 -41
  127. package/tests/mcp.test.ts +54 -54
  128. package/tests/oad.test.ts +68 -68
  129. package/tests/performance.test.ts +115 -115
  130. package/tests/plugin.test.ts +74 -74
  131. package/tests/room.test.ts +106 -106
  132. package/tests/runtime.test.ts +42 -42
  133. package/tests/sandbox.test.ts +46 -46
  134. package/tests/security.test.ts +60 -60
  135. package/tests/templates.test.ts +77 -77
  136. package/tests/v070.test.ts +76 -76
  137. package/tests/versioning.test.ts +75 -75
  138. package/tests/voice.test.ts +61 -61
  139. package/tests/webhook.test.ts +29 -29
  140. package/tests/workflow.test.ts +143 -143
  141. package/tsconfig.json +19 -19
  142. package/vitest.config.ts +9 -9
@@ -20,264 +20,264 @@ const AGENT_TEMPLATES = [
20
20
  { id: 'hr-recruiter', name: 'HR Recruiter', description: 'Resume screening, interview scheduling, candidate comms', icon: 'šŸ‘„', category: 'HR' },
21
21
  { id: 'legal-assistant', name: 'Legal Assistant', description: 'Contract review, compliance checks, legal research', icon: 'āš–ļø', category: 'Legal' },
22
22
  ];
23
- const TEMPLATES_HTML = `<!DOCTYPE html>
24
- <html lang="en">
25
- <head>
26
- <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
27
- <title>Agent Templates</title>
28
- <style>
29
- *{margin:0;padding:0;box-sizing:border-box}
30
- body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
31
- h1{font-size:28px;margin-bottom:8px;color:#fff}
32
- .sub{color:#888;margin-bottom:32px;font-size:14px}
33
- nav{margin-bottom:24px}
34
- nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
35
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
36
- .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:24px;cursor:pointer;transition:all .2s}
37
- .card:hover{border-color:#818cf8;transform:translateY(-2px)}
38
- .card .icon{font-size:32px;margin-bottom:12px}
39
- .card h3{font-size:16px;color:#fff;margin-bottom:8px}
40
- .card p{font-size:13px;color:#888;line-height:1.5}
41
- .card .cat{font-size:11px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-top:12px}
42
- .btn{display:inline-block;background:#2563eb;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-top:12px}
43
- .btn:hover{background:#1d4ed8}
44
- </style>
45
- </head>
46
- <body>
47
- <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
48
- <h1>🧩 Agent Templates</h1>
49
- <p class="sub">Create a new agent from a pre-built template in one click.</p>
50
- <div class="grid" id="grid"></div>
51
- <script>
52
- fetch('/api/templates').then(r=>r.json()).then(d=>{
53
- const g=document.getElementById('grid');
54
- d.templates.forEach(t=>{
55
- g.innerHTML+=\`<div class="card"><div class="icon">\${t.icon}</div><h3>\${t.name}</h3><p>\${t.description}</p><div class="cat">\${t.category}</div><button class="btn" onclick="alert('Creating agent from template: '+'\${t.id}'+'\\\\nRun: opc init --template \${t.id}')">Use Template</button></div>\`;
56
- });
57
- });
58
- </script>
59
- </body>
23
+ const TEMPLATES_HTML = `<!DOCTYPE html>
24
+ <html lang="en">
25
+ <head>
26
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
27
+ <title>Agent Templates</title>
28
+ <style>
29
+ *{margin:0;padding:0;box-sizing:border-box}
30
+ body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
31
+ h1{font-size:28px;margin-bottom:8px;color:#fff}
32
+ .sub{color:#888;margin-bottom:32px;font-size:14px}
33
+ nav{margin-bottom:24px}
34
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
35
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
36
+ .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:24px;cursor:pointer;transition:all .2s}
37
+ .card:hover{border-color:#818cf8;transform:translateY(-2px)}
38
+ .card .icon{font-size:32px;margin-bottom:12px}
39
+ .card h3{font-size:16px;color:#fff;margin-bottom:8px}
40
+ .card p{font-size:13px;color:#888;line-height:1.5}
41
+ .card .cat{font-size:11px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-top:12px}
42
+ .btn{display:inline-block;background:#2563eb;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-top:12px}
43
+ .btn:hover{background:#1d4ed8}
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
48
+ <h1>🧩 Agent Templates</h1>
49
+ <p class="sub">Create a new agent from a pre-built template in one click.</p>
50
+ <div class="grid" id="grid"></div>
51
+ <script>
52
+ fetch('/api/templates').then(r=>r.json()).then(d=>{
53
+ const g=document.getElementById('grid');
54
+ d.templates.forEach(t=>{
55
+ g.innerHTML+=\`<div class="card"><div class="icon">\${t.icon}</div><h3>\${t.name}</h3><p>\${t.description}</p><div class="cat">\${t.category}</div><button class="btn" onclick="alert('Creating agent from template: '+'\${t.id}'+'\\\\nRun: opc init --template \${t.id}')">Use Template</button></div>\`;
56
+ });
57
+ });
58
+ </script>
59
+ </body>
60
60
  </html>`;
61
- const CHAT_HTML = `<!DOCTYPE html>
62
- <html lang="en">
63
- <head>
64
- <meta charset="UTF-8">
65
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
66
- <title>OPC Agent</title>
67
- <style>
68
- :root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e0e0e0;--text-dim:#888;--accent:#818cf8;--accent-hover:#6366f1;--user-bg:#2563eb;--user-hover:#1d4ed8;--error-bg:#7f1d1d;--error-text:#fca5a5;--success:#22c55e;--radius:12px}
69
- *{margin:0;padding:0;box-sizing:border-box}
70
- body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
71
- header{background:var(--surface);padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;backdrop-filter:blur(12px)}
72
- header .avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#6366f1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
73
- header .info{flex:1;min-width:0}
74
- header h1{font-size:16px;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
75
- header .status{font-size:12px;color:var(--success);display:flex;align-items:center;gap:4px}
76
- header .status .dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}
77
- nav.header-nav{display:flex;gap:4px}
78
- nav.header-nav a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:4px 10px;border-radius:6px;transition:all .2s}
79
- nav.header-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
80
- @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
81
- @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
82
- @keyframes slideIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
83
- #messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
84
- #messages::-webkit-scrollbar{width:4px}
85
- #messages::-webkit-scrollbar-track{background:transparent}
86
- #messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
87
- .msg-wrap{display:flex;flex-direction:column;animation:fadeIn .3s ease-out}
88
- .msg-wrap.user{align-items:flex-end}
89
- .msg-wrap.assistant{align-items:flex-start}
90
- .msg{max-width:min(720px,85%);padding:10px 14px;border-radius:var(--radius);line-height:1.7;font-size:14px;word-break:break-word;position:relative;transition:all .2s}
91
- .msg.user{background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
92
- .msg.assistant{background:var(--surface);color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px}
93
- .msg.error{background:var(--error-bg);color:var(--error-text);border:1px solid rgba(239,68,68,.3)}
94
- .msg pre{background:rgba(0,0,0,.4);padding:12px;border-radius:8px;overflow-x:auto;margin:8px 0;font-size:13px;font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;line-height:1.5}
95
- .msg code{font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;font-size:13px;background:rgba(0,0,0,.3);padding:1px 5px;border-radius:4px}
96
- .msg pre code{background:none;padding:0}
97
- .msg .cursor{display:inline-block;width:2px;height:14px;background:var(--accent);animation:blink .6s infinite;vertical-align:text-bottom;margin-left:2px}
98
- @keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
99
- .typing{display:flex;gap:4px;padding:12px 16px;align-items:center}
100
- .typing span{width:6px;height:6px;border-radius:50%;background:var(--text-dim);animation:typingDot 1.4s infinite}
101
- .typing span:nth-child(2){animation-delay:.2s}
102
- .typing span:nth-child(3){animation-delay:.4s}
103
- @keyframes typingDot{0%,60%,100%{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
104
- .reactions{display:flex;gap:4px;margin-top:4px}
105
- .reactions button{background:rgba(255,255,255,.06);border:1px solid transparent;border-radius:16px;padding:2px 8px;font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-dim)}
106
- .reactions button:hover{background:rgba(255,255,255,.12);border-color:var(--border)}
107
- .reactions button.active{background:rgba(99,102,241,.2);border-color:var(--accent);color:var(--accent)}
108
- .msg-time{font-size:11px;color:var(--text-dim);margin-top:2px;opacity:0;transition:opacity .2s}
109
- .msg-wrap:hover .msg-time{opacity:1}
110
- .attachment{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.3);padding:8px 12px;border-radius:8px;margin-top:6px;font-size:13px}
111
- .attachment .icon{font-size:18px}
112
- #input-area{background:var(--surface);padding:12px 20px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
113
- #input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;color:#fff;font-size:14px;outline:none;resize:none;max-height:150px;min-height:42px;font-family:inherit;line-height:1.5;transition:border-color .2s}
114
- #input:focus{border-color:var(--accent)}
115
- #input::placeholder{color:var(--text-dim)}
116
- #send{background:var(--user-bg);color:#fff;border:none;border-radius:var(--radius);width:42px;height:42px;font-size:18px;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0}
117
- #send:hover{background:var(--user-hover);transform:scale(1.05)}
118
- #send:disabled{background:#334155;cursor:not-allowed;transform:none}
119
- .empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:12px;padding:40px;text-align:center}
120
- .empty-state .logo{font-size:48px;opacity:.6}
121
- .empty-state h2{color:var(--text);font-size:20px;font-weight:500}
122
- .empty-state p{font-size:14px;max-width:400px;line-height:1.6}
123
- @media(max-width:640px){
124
- header{padding:10px 14px}
125
- #messages{padding:12px}
126
- #input-area{padding:10px 14px 14px}
127
- .msg{max-width:90%;font-size:14px}
128
- nav.header-nav{display:none}
129
- }
130
- </style>
131
- </head>
132
- <body>
133
- <header>
134
- <div class="avatar" id="avatar">šŸ¤–</div>
135
- <div class="info"><h1 id="title">OPC Agent</h1><div class="status"><span class="dot"></span>Online</div></div>
136
- <nav class="header-nav"><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
137
- </header>
138
- <div id="messages">
139
- <div class="empty-state" id="empty"><div class="logo">šŸ’¬</div><h2>Start a conversation</h2><p>Type a message below to chat with your AI agent.</p></div>
140
- </div>
141
- <div id="input-area">
142
- <textarea id="input" rows="1" placeholder="Type a message…" autocomplete="off"></textarea>
143
- <button id="send" aria-label="Send">↑</button>
144
- </div>
145
- <script>
146
- const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send'),empty=document.getElementById('empty');
147
- let sessionId=crypto.randomUUID(),sending=false;
148
-
149
- function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
150
- function fmtTime(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}
151
- function renderMd(text){
152
- let h=esc(text);
153
- h=h.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,'<pre><code>$2</code></pre>');
154
- h=h.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
155
- h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
156
- h=h.replace(/\\n/g,'<br>');
157
- return h;
158
- }
159
-
160
- function addMsg(role,text,opts){
161
- if(empty)empty.remove();
162
- const wrap=document.createElement('div');
163
- wrap.className='msg-wrap '+role;
164
- const d=document.createElement('div');
165
- d.className='msg '+role;
166
- if(opts?.html)d.innerHTML=text;else if(role==='assistant'&&text)d.innerHTML=renderMd(text);else d.textContent=text;
167
- wrap.appendChild(d);
168
- const time=document.createElement('div');
169
- time.className='msg-time';
170
- time.textContent=fmtTime();
171
- wrap.appendChild(time);
172
- if(role==='assistant'&&text){
173
- const rx=document.createElement('div');rx.className='reactions';
174
- rx.innerHTML='<button data-r="šŸ‘" onclick="react(this)">šŸ‘</button><button data-r="šŸ‘Ž" onclick="react(this)">šŸ‘Ž</button>';
175
- wrap.appendChild(rx);
176
- }
177
- msgs.appendChild(wrap);
178
- msgs.scrollTop=msgs.scrollHeight;
179
- return d;
180
- }
181
-
182
- window.react=function(el){el.classList.toggle('active')};
183
-
184
- input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
185
- input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
186
- btn.addEventListener('click',send);
187
-
188
- async function send(){
189
- const text=input.value.trim();
190
- if(!text||sending)return;
191
- sending=true;btn.disabled=true;
192
- input.value='';input.style.height='auto';
193
- addMsg('user',text);
194
- const wrap=document.createElement('div');wrap.className='msg-wrap assistant';
195
- const d=document.createElement('div');d.className='msg assistant';
196
- d.innerHTML='<div class="typing"><span></span><span></span><span></span></div>';
197
- wrap.appendChild(d);
198
- const time=document.createElement('div');time.className='msg-time';time.textContent=fmtTime();
199
- wrap.appendChild(time);
200
- msgs.appendChild(wrap);msgs.scrollTop=msgs.scrollHeight;
201
- try{
202
- const res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:text,sessionId})});
203
- if(!res.ok)throw new Error('HTTP '+res.status);
204
- const reader=res.body.getReader(),dec=new TextDecoder();
205
- let full='';
206
- while(true){
207
- const{done,value}=await reader.read();
208
- if(done)break;
209
- const chunk=dec.decode(value,{stream:true});
210
- const lines=chunk.split('\\n');
211
- for(const line of lines){
212
- if(!line.startsWith('data: '))continue;
213
- const dd=line.slice(6);if(dd==='[DONE]')continue;
214
- try{const j=JSON.parse(dd);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
215
- }
216
- d.innerHTML=renderMd(full)+'<span class="cursor"></span>';
217
- msgs.scrollTop=msgs.scrollHeight;
218
- }
219
- if(!full){d.textContent='(empty response)';}else{d.innerHTML=renderMd(full);}
220
- const rx=document.createElement('div');rx.className='reactions';
221
- rx.innerHTML='<button data-r="šŸ‘" onclick="react(this)">šŸ‘</button><button data-r="šŸ‘Ž" onclick="react(this)">šŸ‘Ž</button>';
222
- wrap.appendChild(rx);
223
- }catch(e){d.className='msg error';d.textContent='Error: '+e.message;}
224
- sending=false;btn.disabled=false;input.focus();
225
- }
226
-
227
- fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById('title').textContent=d.name}).catch(()=>{});
228
- </script>
229
- </body>
61
+ const CHAT_HTML = `<!DOCTYPE html>
62
+ <html lang="en">
63
+ <head>
64
+ <meta charset="UTF-8">
65
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
66
+ <title>OPC Agent</title>
67
+ <style>
68
+ :root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e0e0e0;--text-dim:#888;--accent:#818cf8;--accent-hover:#6366f1;--user-bg:#2563eb;--user-hover:#1d4ed8;--error-bg:#7f1d1d;--error-text:#fca5a5;--success:#22c55e;--radius:12px}
69
+ *{margin:0;padding:0;box-sizing:border-box}
70
+ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
71
+ header{background:var(--surface);padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;backdrop-filter:blur(12px)}
72
+ header .avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#6366f1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
73
+ header .info{flex:1;min-width:0}
74
+ header h1{font-size:16px;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
75
+ header .status{font-size:12px;color:var(--success);display:flex;align-items:center;gap:4px}
76
+ header .status .dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}
77
+ nav.header-nav{display:flex;gap:4px}
78
+ nav.header-nav a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:4px 10px;border-radius:6px;transition:all .2s}
79
+ nav.header-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
80
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
81
+ @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
82
+ @keyframes slideIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
83
+ #messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
84
+ #messages::-webkit-scrollbar{width:4px}
85
+ #messages::-webkit-scrollbar-track{background:transparent}
86
+ #messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
87
+ .msg-wrap{display:flex;flex-direction:column;animation:fadeIn .3s ease-out}
88
+ .msg-wrap.user{align-items:flex-end}
89
+ .msg-wrap.assistant{align-items:flex-start}
90
+ .msg{max-width:min(720px,85%);padding:10px 14px;border-radius:var(--radius);line-height:1.7;font-size:14px;word-break:break-word;position:relative;transition:all .2s}
91
+ .msg.user{background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
92
+ .msg.assistant{background:var(--surface);color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px}
93
+ .msg.error{background:var(--error-bg);color:var(--error-text);border:1px solid rgba(239,68,68,.3)}
94
+ .msg pre{background:rgba(0,0,0,.4);padding:12px;border-radius:8px;overflow-x:auto;margin:8px 0;font-size:13px;font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;line-height:1.5}
95
+ .msg code{font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;font-size:13px;background:rgba(0,0,0,.3);padding:1px 5px;border-radius:4px}
96
+ .msg pre code{background:none;padding:0}
97
+ .msg .cursor{display:inline-block;width:2px;height:14px;background:var(--accent);animation:blink .6s infinite;vertical-align:text-bottom;margin-left:2px}
98
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
99
+ .typing{display:flex;gap:4px;padding:12px 16px;align-items:center}
100
+ .typing span{width:6px;height:6px;border-radius:50%;background:var(--text-dim);animation:typingDot 1.4s infinite}
101
+ .typing span:nth-child(2){animation-delay:.2s}
102
+ .typing span:nth-child(3){animation-delay:.4s}
103
+ @keyframes typingDot{0%,60%,100%{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
104
+ .reactions{display:flex;gap:4px;margin-top:4px}
105
+ .reactions button{background:rgba(255,255,255,.06);border:1px solid transparent;border-radius:16px;padding:2px 8px;font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-dim)}
106
+ .reactions button:hover{background:rgba(255,255,255,.12);border-color:var(--border)}
107
+ .reactions button.active{background:rgba(99,102,241,.2);border-color:var(--accent);color:var(--accent)}
108
+ .msg-time{font-size:11px;color:var(--text-dim);margin-top:2px;opacity:0;transition:opacity .2s}
109
+ .msg-wrap:hover .msg-time{opacity:1}
110
+ .attachment{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.3);padding:8px 12px;border-radius:8px;margin-top:6px;font-size:13px}
111
+ .attachment .icon{font-size:18px}
112
+ #input-area{background:var(--surface);padding:12px 20px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
113
+ #input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;color:#fff;font-size:14px;outline:none;resize:none;max-height:150px;min-height:42px;font-family:inherit;line-height:1.5;transition:border-color .2s}
114
+ #input:focus{border-color:var(--accent)}
115
+ #input::placeholder{color:var(--text-dim)}
116
+ #send{background:var(--user-bg);color:#fff;border:none;border-radius:var(--radius);width:42px;height:42px;font-size:18px;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0}
117
+ #send:hover{background:var(--user-hover);transform:scale(1.05)}
118
+ #send:disabled{background:#334155;cursor:not-allowed;transform:none}
119
+ .empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:12px;padding:40px;text-align:center}
120
+ .empty-state .logo{font-size:48px;opacity:.6}
121
+ .empty-state h2{color:var(--text);font-size:20px;font-weight:500}
122
+ .empty-state p{font-size:14px;max-width:400px;line-height:1.6}
123
+ @media(max-width:640px){
124
+ header{padding:10px 14px}
125
+ #messages{padding:12px}
126
+ #input-area{padding:10px 14px 14px}
127
+ .msg{max-width:90%;font-size:14px}
128
+ nav.header-nav{display:none}
129
+ }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <header>
134
+ <div class="avatar" id="avatar">šŸ¤–</div>
135
+ <div class="info"><h1 id="title">OPC Agent</h1><div class="status"><span class="dot"></span>Online</div></div>
136
+ <nav class="header-nav"><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
137
+ </header>
138
+ <div id="messages">
139
+ <div class="empty-state" id="empty"><div class="logo">šŸ’¬</div><h2>Start a conversation</h2><p>Type a message below to chat with your AI agent.</p></div>
140
+ </div>
141
+ <div id="input-area">
142
+ <textarea id="input" rows="1" placeholder="Type a message…" autocomplete="off"></textarea>
143
+ <button id="send" aria-label="Send">↑</button>
144
+ </div>
145
+ <script>
146
+ const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send'),empty=document.getElementById('empty');
147
+ let sessionId=crypto.randomUUID(),sending=false;
148
+
149
+ function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
150
+ function fmtTime(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}
151
+ function renderMd(text){
152
+ let h=esc(text);
153
+ h=h.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,'<pre><code>$2</code></pre>');
154
+ h=h.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
155
+ h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
156
+ h=h.replace(/\\n/g,'<br>');
157
+ return h;
158
+ }
159
+
160
+ function addMsg(role,text,opts){
161
+ if(empty)empty.remove();
162
+ const wrap=document.createElement('div');
163
+ wrap.className='msg-wrap '+role;
164
+ const d=document.createElement('div');
165
+ d.className='msg '+role;
166
+ if(opts?.html)d.innerHTML=text;else if(role==='assistant'&&text)d.innerHTML=renderMd(text);else d.textContent=text;
167
+ wrap.appendChild(d);
168
+ const time=document.createElement('div');
169
+ time.className='msg-time';
170
+ time.textContent=fmtTime();
171
+ wrap.appendChild(time);
172
+ if(role==='assistant'&&text){
173
+ const rx=document.createElement('div');rx.className='reactions';
174
+ rx.innerHTML='<button data-r="šŸ‘" onclick="react(this)">šŸ‘</button><button data-r="šŸ‘Ž" onclick="react(this)">šŸ‘Ž</button>';
175
+ wrap.appendChild(rx);
176
+ }
177
+ msgs.appendChild(wrap);
178
+ msgs.scrollTop=msgs.scrollHeight;
179
+ return d;
180
+ }
181
+
182
+ window.react=function(el){el.classList.toggle('active')};
183
+
184
+ input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
185
+ input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
186
+ btn.addEventListener('click',send);
187
+
188
+ async function send(){
189
+ const text=input.value.trim();
190
+ if(!text||sending)return;
191
+ sending=true;btn.disabled=true;
192
+ input.value='';input.style.height='auto';
193
+ addMsg('user',text);
194
+ const wrap=document.createElement('div');wrap.className='msg-wrap assistant';
195
+ const d=document.createElement('div');d.className='msg assistant';
196
+ d.innerHTML='<div class="typing"><span></span><span></span><span></span></div>';
197
+ wrap.appendChild(d);
198
+ const time=document.createElement('div');time.className='msg-time';time.textContent=fmtTime();
199
+ wrap.appendChild(time);
200
+ msgs.appendChild(wrap);msgs.scrollTop=msgs.scrollHeight;
201
+ try{
202
+ const res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:text,sessionId})});
203
+ if(!res.ok)throw new Error('HTTP '+res.status);
204
+ const reader=res.body.getReader(),dec=new TextDecoder();
205
+ let full='';
206
+ while(true){
207
+ const{done,value}=await reader.read();
208
+ if(done)break;
209
+ const chunk=dec.decode(value,{stream:true});
210
+ const lines=chunk.split('\\n');
211
+ for(const line of lines){
212
+ if(!line.startsWith('data: '))continue;
213
+ const dd=line.slice(6);if(dd==='[DONE]')continue;
214
+ try{const j=JSON.parse(dd);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
215
+ }
216
+ d.innerHTML=renderMd(full)+'<span class="cursor"></span>';
217
+ msgs.scrollTop=msgs.scrollHeight;
218
+ }
219
+ if(!full){d.textContent='(empty response)';}else{d.innerHTML=renderMd(full);}
220
+ const rx=document.createElement('div');rx.className='reactions';
221
+ rx.innerHTML='<button data-r="šŸ‘" onclick="react(this)">šŸ‘</button><button data-r="šŸ‘Ž" onclick="react(this)">šŸ‘Ž</button>';
222
+ wrap.appendChild(rx);
223
+ }catch(e){d.className='msg error';d.textContent='Error: '+e.message;}
224
+ sending=false;btn.disabled=false;input.focus();
225
+ }
226
+
227
+ fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById('title').textContent=d.name}).catch(()=>{});
228
+ </script>
229
+ </body>
230
230
  </html>`;
231
- const DASHBOARD_HTML = `<!DOCTYPE html>
232
- <html lang="en">
233
- <head>
234
- <meta charset="UTF-8">
235
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
236
- <title>OPC Dashboard</title>
237
- <style>
238
- *{margin:0;padding:0;box-sizing:border-box}
239
- body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
240
- h1{font-size:24px;margin-bottom:24px;color:#fff}
241
- .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
242
- .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px}
243
- .card .label{font-size:12px;color:#888;text-transform:uppercase;letter-spacing:1px}
244
- .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
245
- .card .sub{font-size:12px;color:#555;margin-top:4px}
246
- nav{margin-bottom:24px}
247
- nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
248
- nav a:hover{text-decoration:underline}
249
- .chart{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px;margin-bottom:16px}
250
- .chart h3{font-size:14px;color:#888;margin-bottom:12px}
251
- </style>
252
- </head>
253
- <body>
254
- <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
255
- <h1>šŸ“Š Agent Dashboard</h1>
256
- <div class="grid">
257
- <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
258
- <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
259
- <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
260
- <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
261
- <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
262
- <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
263
- </div>
264
- <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
265
- <script>
266
- async function refresh(){
267
- try{
268
- const r=await fetch('/api/dashboard');const d=await r.json();
269
- document.getElementById('sessions').textContent=d.sessions;
270
- document.getElementById('messages').textContent=d.messages;
271
- document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
272
- document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
273
- document.getElementById('kb').textContent=d.knowledgeFiles;
274
- const mins=Math.round((Date.now()-d.startedAt)/60000);
275
- document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
276
- }catch{}
277
- }
278
- refresh();setInterval(refresh,5000);
279
- </script>
280
- </body>
231
+ const DASHBOARD_HTML = `<!DOCTYPE html>
232
+ <html lang="en">
233
+ <head>
234
+ <meta charset="UTF-8">
235
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
236
+ <title>OPC Dashboard</title>
237
+ <style>
238
+ *{margin:0;padding:0;box-sizing:border-box}
239
+ body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
240
+ h1{font-size:24px;margin-bottom:24px;color:#fff}
241
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
242
+ .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px}
243
+ .card .label{font-size:12px;color:#888;text-transform:uppercase;letter-spacing:1px}
244
+ .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
245
+ .card .sub{font-size:12px;color:#555;margin-top:4px}
246
+ nav{margin-bottom:24px}
247
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
248
+ nav a:hover{text-decoration:underline}
249
+ .chart{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px;margin-bottom:16px}
250
+ .chart h3{font-size:14px;color:#888;margin-bottom:12px}
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
255
+ <h1>šŸ“Š Agent Dashboard</h1>
256
+ <div class="grid">
257
+ <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
258
+ <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
259
+ <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
260
+ <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
261
+ <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
262
+ <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
263
+ </div>
264
+ <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
265
+ <script>
266
+ async function refresh(){
267
+ try{
268
+ const r=await fetch('/api/dashboard');const d=await r.json();
269
+ document.getElementById('sessions').textContent=d.sessions;
270
+ document.getElementById('messages').textContent=d.messages;
271
+ document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
272
+ document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
273
+ document.getElementById('kb').textContent=d.knowledgeFiles;
274
+ const mins=Math.round((Date.now()-d.startedAt)/60000);
275
+ document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
276
+ }catch{}
277
+ }
278
+ refresh();setInterval(refresh,5000);
279
+ </script>
280
+ </body>
281
281
  </html>`;
282
282
  class WebChannel extends index_1.BaseChannel {
283
283
  type = 'web';
package/dist/cli.js CHANGED
@@ -486,38 +486,7 @@ program
486
486
  process.exit(0);
487
487
  });
488
488
  });
489
- // ── Publish command ──────────────────────────────────────────
490
- program
491
- .command('publish')
492
- .description('Validate and package for OPC Registry')
493
- .option('-f, --file <file>', 'OAD file', 'oad.yaml')
494
- .action(async (opts) => {
495
- try {
496
- const runtime = new runtime_1.AgentRuntime();
497
- const config = await runtime.loadConfig(opts.file);
498
- const trust = config.spec.dtv?.trust?.level ?? 'sandbox';
499
- console.log(`\n${icon.package} Publishing: ${color.bold(config.metadata.name)} v${config.metadata.version}`);
500
- console.log(` ${icon.success} OAD validation passed`);
501
- const manifest = {
502
- name: config.metadata.name,
503
- version: config.metadata.version,
504
- description: config.metadata.description,
505
- author: config.metadata.author,
506
- license: config.metadata.license,
507
- trust,
508
- channels: config.spec.channels.map((c) => c.type),
509
- skills: config.spec.skills.map((s) => s.name),
510
- publishedAt: new Date().toISOString(),
511
- };
512
- fs.writeFileSync('opc-manifest.json', JSON.stringify(manifest, null, 2));
513
- console.log(` ${icon.file} Generated opc-manifest.json`);
514
- console.log(`\n🚧 OPC Registry is coming soon. Manifest saved locally.\n`);
515
- }
516
- catch (err) {
517
- console.error(`${icon.error} Publish failed:`, err instanceof Error ? err.message : err);
518
- process.exit(1);
519
- }
520
- });
489
+ // (publish command moved to marketplace section below)
521
490
  // ── Deploy command ───────────────────────────────────────────
522
491
  program
523
492
  .command('deploy')
@@ -113,32 +113,32 @@ function deployToHermes(options) {
113
113
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
114
114
  files.push('settings.json');
115
115
  // .env template
116
- const envContent = `# Hermes Agent Environment
117
- HERMES_CHARACTER=${oad.metadata.name}
118
- HERMES_MODEL=${oad.spec.model}
119
- HERMES_PROVIDER=${oad.spec.provider?.default ?? 'openai'}
120
- # Add your API keys below:
121
- # OPENAI_API_KEY=
122
- # DEEPSEEK_API_KEY=
116
+ const envContent = `# Hermes Agent Environment
117
+ HERMES_CHARACTER=${oad.metadata.name}
118
+ HERMES_MODEL=${oad.spec.model}
119
+ HERMES_PROVIDER=${oad.spec.provider?.default ?? 'openai'}
120
+ # Add your API keys below:
121
+ # OPENAI_API_KEY=
122
+ # DEEPSEEK_API_KEY=
123
123
  `;
124
124
  fs.writeFileSync(path.join(outputDir, '.env.hermes'), envContent, 'utf-8');
125
125
  files.push('.env.hermes');
126
126
  // README
127
- const readme = `# ${oad.metadata.name} - Hermes Agent
128
-
129
- Converted from OAD format using \`opc deploy --target hermes\`.
130
-
131
- ## Usage
132
-
133
- 1. Copy \`character.json\` to your Hermes agents directory
134
- 2. Configure \`.env.hermes\` with your API keys
135
- 3. Start Hermes with this character
136
-
137
- ## Files
138
-
139
- - \`character.json\` - Agent character definition
140
- - \`settings.json\` - Runtime settings
141
- - \`.env.hermes\` - Environment template
127
+ const readme = `# ${oad.metadata.name} - Hermes Agent
128
+
129
+ Converted from OAD format using \`opc deploy --target hermes\`.
130
+
131
+ ## Usage
132
+
133
+ 1. Copy \`character.json\` to your Hermes agents directory
134
+ 2. Configure \`.env.hermes\` with your API keys
135
+ 3. Start Hermes with this character
136
+
137
+ ## Files
138
+
139
+ - \`character.json\` - Agent character definition
140
+ - \`settings.json\` - Runtime settings
141
+ - \`.env.hermes\` - Environment template
142
142
  `;
143
143
  fs.writeFileSync(path.join(outputDir, 'README.md'), readme, 'utf-8');
144
144
  files.push('README.md');