fenix-claude-plugin 0.1.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 (40) hide show
  1. package/.claude-plugin/plugin.json +13 -0
  2. package/.mcp.json +11 -0
  3. package/README.md +55 -0
  4. package/artifacts/practice-dashboard.html +469 -0
  5. package/artifacts/project-overview.html +481 -0
  6. package/artifacts/user-dashboard.html +368 -0
  7. package/commands/fenix-install.md +15 -0
  8. package/commands/fenix-setup.md +14 -0
  9. package/package.json +16 -0
  10. package/skills/fenix-application/CUSTOMIZE.md +1 -0
  11. package/skills/fenix-application/SKILL.md +95 -0
  12. package/skills/fenix-application/VERSION +1 -0
  13. package/skills/fenix-application/assets/claims_editor.html +87 -0
  14. package/skills/fenix-application/assets/figures_editor.html +87 -0
  15. package/skills/fenix-application/assets/spec_editor.html +87 -0
  16. package/skills/fenix-application/references/claims.md +30 -0
  17. package/skills/fenix-application/references/disclosure.md +33 -0
  18. package/skills/fenix-application/references/editors.md +52 -0
  19. package/skills/fenix-application/references/figure-description.md +67 -0
  20. package/skills/fenix-application/references/figures-guidance.md +122 -0
  21. package/skills/fenix-application/references/figures.md +28 -0
  22. package/skills/fenix-application/references/patent-review-advantages.md +35 -0
  23. package/skills/fenix-application/references/patent-review-checklist.md +36 -0
  24. package/skills/fenix-application/references/patent-review-claim-formats.md +71 -0
  25. package/skills/fenix-application/references/patent-review-profanity.md +57 -0
  26. package/skills/fenix-application/references/patent-review.md +82 -0
  27. package/skills/fenix-application/references/patent-safe.md +44 -0
  28. package/skills/fenix-application/references/patent.md +18 -0
  29. package/skills/fenix-application/references/spec-guidance.md +195 -0
  30. package/skills/fenix-application/references/spec.md +48 -0
  31. package/skills/fenix-application/scripts/calculate_figure_layout.py +32 -0
  32. package/skills/fenix-application/scripts/parse_claims.py +39 -0
  33. package/skills/fenix-office-action/CUSTOMIZE.md +1 -0
  34. package/skills/fenix-office-action/SKILL.md +40 -0
  35. package/skills/fenix-office-action/VERSION +1 -0
  36. package/skills/fenix-office-action/references/oa-response.md +10 -0
  37. package/skills/fenix-project/CUSTOMIZE.md +1 -0
  38. package/skills/fenix-project/SKILL.md +41 -0
  39. package/skills/fenix-project/VERSION +1 -0
  40. package/skills/fenix-project/references/create-project.md +10 -0
@@ -0,0 +1,368 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Team Member</title>
7
+ <style>
8
+ :root{
9
+ color-scheme: dark;
10
+ --bg:#0c0e13; --card:#14171f; --card2:#191d27; --ink:#c3c8d4; --muted:#7e8598;
11
+ --line:#242936; --line2:#2f3543;
12
+ --accent:#818cf8; --accent2:#6366f1; --accentbg:#1c2030; --accentglow:#2a2f4a;
13
+ --red:#f87171; --redbg:#2a1417; --orange:#fb923c; --orangebg:#2a1c10;
14
+ --amber:#fbbf24; --amberbg:#2a230f; --green:#34d399; --greenbg:#0f2a20;
15
+ --gray:#8b92a6; --graybg:#1b1f29; --purple:#a78bfa; --purplebg:#211a33;
16
+ }
17
+ *{box-sizing:border-box}
18
+ body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
19
+ .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
20
+ .wrap{max-width:1280px;margin:0 auto;padding:22px 20px 80px}
21
+ .top{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:4px}
22
+ .brand{display:flex;align-items:center;gap:13px}
23
+ .brand .logo{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;border-radius:11px;border:1px solid var(--line2);background:#0a0c11;line-height:0}
24
+ .brand .logo img{display:block;width:38px;height:38px;object-fit:contain}
25
+ h1{font-size:22px;margin:0;letter-spacing:-.02em;font-weight:800}
26
+ .sub{color:var(--muted);font-size:12.5px;margin-top:3px}
27
+ .prog{display:flex;flex-wrap:wrap;gap:7px;margin:14px 0 16px;min-height:24px}
28
+ .chip{display:flex;align-items:center;gap:7px;background:var(--card);border:1px solid var(--line);border-radius:20px;padding:4px 11px;font-size:11.5px;color:var(--muted)}
29
+ .chip.done{color:var(--green);border-color:transparent;background:var(--greenbg)}
30
+ .chip.err{color:var(--red);border-color:transparent;background:var(--redbg)}
31
+ .chip .sp{width:11px;height:11px;border:2px solid var(--line2);border-top-color:var(--accent);border-radius:50%;animation:s .7s linear infinite;display:inline-block}
32
+ .bar{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;margin-bottom:18px}
33
+ .fld{display:flex;flex-direction:column;gap:5px}
34
+ .fld label{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:700}
35
+ select{background:var(--card2);border:1px solid var(--line2);border-radius:9px;padding:9px 12px;color:var(--ink);font-size:13px;min-width:300px}
36
+ .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin-bottom:20px}
37
+ .kpi{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:15px 16px;position:relative;overflow:hidden}
38
+ .kpi .n{font-size:30px;font-weight:800;letter-spacing:-.03em;line-height:1}
39
+ .kpi .l{color:var(--muted);font-size:11.5px;margin-top:7px}
40
+ .kpi.accent .n{color:var(--accent)}
41
+ .kpi.purple{background:linear-gradient(180deg,var(--purplebg),var(--card));border-color:#2e2547}
42
+ .kpi.purple .n{color:var(--purple)}
43
+ .kpi.red{background:linear-gradient(180deg,var(--redbg),var(--card));border-color:#3a1f24}
44
+ .kpi.red .n{color:var(--red)}
45
+ .ld{color:var(--muted);opacity:.4}
46
+ .panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px}
47
+ .panel h3{margin:0 0 14px;font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:700;display:flex;justify-content:space-between;align-items:center;gap:8px}
48
+ .panel h3 .cnt{color:var(--ink);font-weight:800}
49
+ .profile{display:flex;align-items:center;gap:15px;margin-bottom:6px}
50
+ .av{width:54px;height:54px;border-radius:50%;background:var(--accentbg);color:var(--accent);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:17px;flex-shrink:0;border:1px solid var(--line2)}
51
+ .pname{font-size:18px;font-weight:800;margin:0;letter-spacing:-.01em}
52
+ .pmeta{color:var(--muted);font-size:12.5px;margin-top:3px}
53
+ .pill{display:inline-block;padding:2px 10px;border-radius:20px;font-size:11px;font-weight:700;white-space:nowrap;margin-left:8px;vertical-align:middle}
54
+ .due{font-weight:700;white-space:nowrap}
55
+ .days{display:inline-block;margin-left:7px;padding:1px 8px;border-radius:6px;font-size:10.5px;font-weight:700}
56
+ .lk{color:var(--accent);text-decoration:none;font-weight:700;font-size:12px;white-space:nowrap}
57
+ .lk:hover{text-decoration:underline}
58
+ .center{text-align:center;color:var(--muted);padding:30px 0}
59
+ .spin{width:26px;height:26px;border:3px solid var(--line2);border-top-color:var(--accent);border-radius:50%;animation:s .7s linear infinite;margin:0 auto 13px}
60
+ @keyframes s{to{transform:rotate(360deg)}}
61
+ .atable{width:100%;border-collapse:separate;border-spacing:0}
62
+ .atable th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700;padding:0 8px 9px}
63
+ .atable td{padding:11px 8px;border-top:1px solid var(--line);vertical-align:middle}
64
+ .title{font-weight:700}
65
+ .meta{color:var(--muted);font-size:12px;margin-top:2px}
66
+ .dot{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:8px;vertical-align:middle}
67
+ .hbar{height:7px;border-radius:5px;background:var(--graybg);overflow:hidden;margin:4px 0 6px}
68
+ .hbar > i{display:block;height:100%;background:linear-gradient(90deg,var(--accent2),var(--accent));border-radius:5px}
69
+ .num{font-weight:800}
70
+ .cbtn{background:var(--card2);border:1px solid var(--line2);color:var(--ink);border-radius:8px;padding:5px 11px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;font-family:inherit}
71
+ .cbtn:hover{background:var(--greenbg);border-color:transparent;color:var(--green)}
72
+ .cbtn.arm{background:var(--green);border-color:transparent;color:#06281d}
73
+ .cbtn:disabled{opacity:.55;cursor:default}
74
+ .hint{background:var(--card2);border:1px solid var(--line);border-radius:11px;padding:11px 14px;color:var(--muted);font-size:12px;margin-top:6px}
75
+ @media(max-width:980px){ .kpis{grid-template-columns:repeat(2,1fr)} }
76
+ @media(max-width:560px){ select{min-width:0;width:100%} }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <div class="wrap">
81
+ <div class="top">
82
+ <div class="brand">
83
+ <span class="logo" aria-label="Fenix logo" role="img"><img src="data:image/png;base64,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" alt="Fenix logo" width="38" height="38"></span>
84
+ <div>
85
+ <h1>Team Member</h1>
86
+ <div class="sub" id="sub">Live · loading…</div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="bar" style="align-items:flex-end">
92
+ <div class="fld">
93
+ <label for="userSel">Team member</label>
94
+ <select id="userSel"><option>Loading…</option></select>
95
+ </div>
96
+ <a href="#" id="resetDef" class="lk" style="display:none;padding-bottom:10px">↺ Reset to default</a>
97
+ <div class="prog" id="prog" style="margin:0 0 4px auto"></div>
98
+ </div>
99
+
100
+ <div class="kpis" id="kpis"></div>
101
+
102
+ <div class="panel" id="profilePanel" style="margin-bottom:14px">
103
+ <h3>Profile</h3>
104
+ <div class="center"><div class="spin"></div>Loading team…</div>
105
+ </div>
106
+
107
+ <div class="panel">
108
+ <h3>Active tasks <span class="cnt" id="taskCount"></span></h3>
109
+ <div id="tasksBody"><div class="center">Select a team member to see their open tasks.</div></div>
110
+ </div>
111
+ </div>
112
+
113
+ <!--
114
+ ┌─────────────────────────────────────────────────────────────────────────┐
115
+ │ CLAUDE: SET THE DEFAULT TEAM MEMBER HERE │
116
+ │ │
117
+ │ When the user asks to open this view "for <person>", edit the │
118
+ │ DEFAULT_USER constant in the <script> below to that person's INITIALS │
119
+ │ (e.g. "MAC") or full NAME (e.g. "Michael Carey"), then re-publish the │
120
+ │ artifact with update_artifact. │
121
+ │ │
122
+ │ Behavior: │
123
+ │ • FIRST load (no saved choice yet) → the view selects DEFAULT_USER. │
124
+ │ • After the user picks anyone, that choice is saved to localStorage │
125
+ │ ("fx_user") and OVERRIDES DEFAULT_USER on every later open. │
126
+ │ • To force the default to win again, the user clears it — there is a │
127
+ │ "Reset to default" link next to the dropdown that wipes fx_user. │
128
+ │ • Leave DEFAULT_USER = "" to just open on whoever sorts first. │
129
+ │ │
130
+ │ Valid initials come from list_users (e.g. NJZ, MAC, BJA, STA, ...). │
131
+ └─────────────────────────────────────────────────────────────────────────┘
132
+ -->
133
+ <script>
134
+ const SRV='mcp__25de2133-8f0a-47c4-b66d-b520d6be5fe7__';
135
+ const BASE='https://chau.fenix.ai/dashboard/projects/';
136
+
137
+ // ▼▼▼ CLAUDE: edit this one value to choose who the view opens on first load. ▼▼▼
138
+ const DEFAULT_USER = ""; // initials ("MAC") or full name ("Michael Carey"); "" = first in list
139
+ // ▲▲▲ Saved user selections (localStorage "fx_user") override this after first use. ▲▲▲
140
+ let USERS=[],PROJECTS=[],TASKS=[];
141
+ const today=(()=>{const n=new Date();return new Date(n.getFullYear(),n.getMonth(),n.getDate())})();
142
+
143
+ function unwrap(res){
144
+ if(res==null) return null;
145
+ if(typeof res==='string'){ try{return JSON.parse(res)}catch(e){return res} }
146
+ if(Array.isArray(res)) return res;
147
+ if(Array.isArray(res.content)){ const t=res.content.map(c=>c&&c.text||'').join(''); try{return JSON.parse(t)}catch(e){return res} }
148
+ if(res.result!==undefined) return unwrap(res.result);
149
+ return res;
150
+ }
151
+ function asArray(res,key){ const u=unwrap(res); if(Array.isArray(u))return u; if(u&&Array.isArray(u[key]))return u[key]; return []; }
152
+ function call(tool,args){ return window.cowork.callMcpTool(SRV+tool,args||{}).catch(()=>null); }
153
+ function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]))}
154
+
155
+ function pd(raw){
156
+ if(!raw) return null;
157
+ let s=String(raw).trim(); if(!s) return null;
158
+ s=s.replace(/(\d+)(st|nd|rd|th)/gi,'$1');
159
+ let iso=s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
160
+ if(iso) return new Date(+iso[1],+iso[2]-1,+iso[3]);
161
+ let mdy=s.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})$/);
162
+ if(mdy){ let mo=+mdy[1],d=+mdy[2],y=+mdy[3]; if(y<100)y+=2000; return new Date(y,mo-1,d); }
163
+ let t=Date.parse(s); if(isNaN(t)) return null;
164
+ const dt=new Date(t); return new Date(dt.getFullYear(),dt.getMonth(),dt.getDate());
165
+ }
166
+ function dleft(d){ return d?Math.round((d-today)/86400000):null; }
167
+ function fmt(d){ return d?d.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}):'—'; }
168
+ function urg(dl){
169
+ if(dl===null) return {bg:'var(--graybg)',fg:'var(--gray)',lab:'No date'};
170
+ if(dl<0) return {bg:'var(--redbg)',fg:'var(--red)',lab:Math.abs(dl)+'d overdue'};
171
+ if(dl<=7) return {bg:'var(--redbg)',fg:'var(--red)',lab:dl+'d left'};
172
+ if(dl<=21) return {bg:'var(--orangebg)',fg:'var(--orange)',lab:dl+'d left'};
173
+ if(dl<=45) return {bg:'var(--amberbg)',fg:'var(--amber)',lab:dl+'d left'};
174
+ return {bg:'var(--greenbg)',fg:'var(--green)',lab:dl+'d left'};
175
+ }
176
+ function dueCell(d){ const dl=d?dleft(d):null,u=urg(dl);
177
+ return `<span class="due mono">${fmt(d)}</span><span class="days" style="background:${u.bg};color:${u.fg}">${u.lab}</span>`; }
178
+ function initialsOf(u){ if(u.initials) return u.initials.toUpperCase();
179
+ const p=(u.name||'?').split(/\s+/); return ((p[0]||'')[0]||'')+((p[1]||'')[0]||''); }
180
+
181
+ function setChip(id,state,label){
182
+ const el=document.getElementById('chip-'+id); if(!el) return;
183
+ el.className='chip'+(state==='done'?' done':state==='err'?' err':'');
184
+ el.innerHTML=(state==='load'?'<span class="sp"></span>':state==='done'?'✓ ':'✕ ')+label;
185
+ }
186
+ function statusDot(s){ const l=(s||'').toLowerCase();
187
+ let c='var(--accent)'; if(l==='completed')c='var(--green)'; else if(l==='in-progress')c='var(--amber)'; else if(l==='inactive')c='var(--gray)';
188
+ return `<span class="dot" style="background:${c}"></span>`; }
189
+
190
+ // TASKS is fetched per-user (list_open_tasks({assigneeId})), so it already holds
191
+ // only the selected user's tasks; just drop any that have been completed.
192
+ function tasksFor(u){
193
+ if(!u||!Array.isArray(TASKS)) return [];
194
+ return TASKS.filter(t=>t.status!=='completed');
195
+ }
196
+
197
+ // Two-click confirm, then mark the task complete in Fenix via complete_task.
198
+ function completeTask(taskId, btn){
199
+ if(!taskId) return;
200
+ if(btn.dataset.armed!=='1'){
201
+ btn.dataset.armed='1'; btn.textContent='Confirm?'; btn.classList.add('arm');
202
+ clearTimeout(btn._t);
203
+ btn._t=setTimeout(()=>{ btn.dataset.armed='0'; btn.textContent='✓ Done'; btn.classList.remove('arm'); },4000);
204
+ return;
205
+ }
206
+ clearTimeout(btn._t); doComplete(taskId, btn);
207
+ }
208
+ async function doComplete(taskId, btn){
209
+ btn.disabled=true; btn.textContent='…'; btn.classList.remove('arm');
210
+ const res=unwrap(await call('complete_task',{taskId}));
211
+ const ok = res!=null && res!==false && !(res&&res.error);
212
+ if(ok){
213
+ const t=Array.isArray(TASKS)?TASKS.find(x=>x.id===taskId):null; if(t) t.status='completed';
214
+ const sel=document.getElementById('userSel').value;
215
+ renderKpis(USERS.find(u=>u.userId===sel)); renderTasks(sel);
216
+ } else {
217
+ btn.disabled=false; btn.dataset.armed='0'; btn.textContent='Retry';
218
+ }
219
+ }
220
+
221
+ function renderKpis(u){
222
+ const mine=u?tasksFor(u):[];
223
+ const overdue=mine.filter(t=>t._dl!==null&&t._dl<0).length;
224
+ const v=x=>u?x:'…';
225
+ document.getElementById('kpis').innerHTML=`
226
+ <div class="kpi accent"><div class="n mono">${v(mine.length)}</div><div class="l">Open tasks</div></div>
227
+ <div class="kpi red"><div class="n mono">${v(overdue)}</div><div class="l">Overdue tasks</div></div>
228
+ <div class="kpi"><div class="n mono">${u?(u.totalEstimatedHours??0):'…'}</div><div class="l">Estimated hours</div></div>
229
+ <div class="kpi purple"><div class="n mono">${u?(u.totalActiveProjects??0):'…'}</div><div class="l">Active projects</div></div>`;
230
+ }
231
+
232
+ async function renderProfile(userId){
233
+ const panel=document.getElementById('profilePanel');
234
+ const u=USERS.find(x=>x.userId===userId);
235
+ renderKpis(u);
236
+ if(!u){ panel.innerHTML='<h3>Profile</h3><div class="center">No user selected.</div>'; return; }
237
+ let avail='',email='';
238
+ if(u.initials){ const info=unwrap(await call('get_user_by_initials',{initials:u.initials}));
239
+ if(info&&info.found){ avail=info.availabilityStatus||''; email=info.email||''; } }
240
+ const al=avail.toLowerCase();
241
+ let abg='var(--graybg)',afg='var(--gray)';
242
+ if(al==='busy'){abg='var(--redbg)';afg='var(--red)';}
243
+ else if(al==='available'){abg='var(--greenbg)';afg='var(--green)';}
244
+ const badge=avail?`<span class="pill" style="background:${abg};color:${afg}">${esc(avail)}</span>`:'';
245
+ panel.innerHTML=`
246
+ <h3>Profile</h3>
247
+ <div class="profile">
248
+ <div class="av">${esc(initialsOf(u)||'?')}</div>
249
+ <div>
250
+ <p class="pname">${esc(u.name||'Unknown')} ${badge}</p>
251
+ <p class="pmeta">${u.initials?esc(u.initials)+' · ':''}${email?esc(email):'<span class="ld">no email on file</span>'}</p>
252
+ </div>
253
+ </div>
254
+ <table class="atable" style="margin-top:14px">
255
+ <tr><td style="border-top:0;color:var(--muted)">Estimated hours</td><td style="border-top:0;text-align:right" class="num mono">${u.totalEstimatedHours??0}</td></tr>
256
+ <tr><td style="color:var(--muted)">Active projects</td><td style="text-align:right" class="num mono">${u.totalActiveProjects??0}</td></tr>
257
+ </table>`;
258
+ }
259
+
260
+ function renderTasks(userId){
261
+ const u=USERS.find(x=>x.userId===userId);
262
+ const body=document.getElementById('tasksBody');
263
+ const cnt=document.getElementById('taskCount');
264
+ if(!u){ body.innerHTML='<div class="center">Select a team member.</div>'; cnt.textContent=''; return; }
265
+ if(TASKS===null){
266
+ cnt.textContent='';
267
+ body.innerHTML='<div class="hint">Couldn’t load tasks for '+esc(u.name)+' (the <span class="mono">list_open_tasks</span> call failed). Estimated hours and active-project counts above still reflect this member’s load. Try reselecting the member.</div>';
268
+ return;
269
+ }
270
+ if(TASKS.length===0 && body.dataset.loading==='1'){
271
+ cnt.textContent=''; body.innerHTML='<div class="center"><div class="spin"></div>Loading tasks…</div>'; return;
272
+ }
273
+ const mine=tasksFor(u).slice().sort((a,b)=>(a._d?a._d:9e15)-(b._d?b._d:9e15));
274
+ cnt.textContent=mine.length?'· '+mine.length:'';
275
+ if(!mine.length){ body.innerHTML='<div class="center">No open tasks assigned to '+esc(u.name)+'.</div>'; return; }
276
+ const rows=mine.map(t=>`
277
+ <tr><td>${statusDot(t.status)}<span class="title">${esc(t.name||'(task)')}</span></td>
278
+ <td>${esc(t.matterNum||'—')}${t.client&&t.client!=='Unassigned'?'<div class="meta">'+esc(t.client)+'</div>':''}</td>
279
+ <td>${dueCell(t._d)}</td>
280
+ <td style="text-align:right;white-space:nowrap">
281
+ <button class="cbtn" onclick="completeTask('${esc(t.id)}',this)"${t.id?'':' disabled title="No task id available"'}>✓ Done</button>
282
+ <a class="lk" style="margin-left:12px" href="${esc(t.url)}" target="_blank" rel="noopener">Open ↗</a>
283
+ </td></tr>`).join('');
284
+ body.innerHTML=`<table class="atable">
285
+ <thead><tr><th>Task</th><th>Matter</th><th>Due</th><th style="text-align:right">Action</th></tr></thead>
286
+ <tbody>${rows}</tbody></table>`;
287
+ }
288
+
289
+ function fillSelect(el,items,valKey,labelFn,saved){
290
+ el.innerHTML=items.map(i=>`<option value="${esc(i[valKey])}">${esc(labelFn(i))}</option>`).join('');
291
+ if(saved&&items.some(i=>i[valKey]===saved)) el.value=saved;
292
+ }
293
+
294
+ // Resolve the Claude-set DEFAULT_USER (initials or full name) to a userId.
295
+ function resolveDefaultUser(){
296
+ const d=String(DEFAULT_USER||'').trim().toLowerCase();
297
+ if(!d) return null;
298
+ const m=USERS.find(u=>(u.initials||'').toLowerCase()===d)||USERS.find(u=>(u.name||'').toLowerCase()===d);
299
+ return m?m.userId:null;
300
+ }
301
+
302
+ function mapTasks(res){
303
+ const pc={}; PROJECTS.forEach(p=>{pc[p.projectId]=p.clientName;});
304
+ TASKS=asArray(res,'tasks').map(t=>{const d=pd(t.dueDate);return {
305
+ id:t._id||t.taskId||t.id||'',
306
+ name:t.taskName||t.name||'(task)', matterNum:t.matterNum||'',
307
+ client:pc[t.projectId]||t.clientName||'Unassigned',
308
+ assigneeId:(t.assignee&&t.assignee.userId)||'',
309
+ status:t.status||'active', projectId:t.projectId, url:BASE+(t.projectId||''),
310
+ _d:d, _dl:dleft(d)
311
+ };});
312
+ }
313
+
314
+ // Fetch only the selected user's open tasks via the server-side assigneeId
315
+ // filter (small, exact payload — no firm-wide pull, no initials matching).
316
+ async function loadTasks(userId){
317
+ const body=document.getElementById('tasksBody');
318
+ TASKS=[];
319
+ if(!userId){ body.dataset.loading='0'; renderTasks(userId); return; }
320
+ body.dataset.loading='1'; renderTasks(userId); // show spinner
321
+ const res=await call('list_open_tasks',{assigneeId:userId});
322
+ body.dataset.loading='0';
323
+ if(res===null){ TASKS=null; renderTasks(userId); return; } // fetch failed
324
+ mapTasks(res);
325
+ renderKpis(USERS.find(u=>u.userId===userId));
326
+ renderTasks(userId);
327
+ }
328
+
329
+ async function boot(){
330
+ document.getElementById('prog').innerHTML=
331
+ '<span id="chip-users" class="chip"><span class="sp"></span>Team</span>'+
332
+ '<span id="chip-projects" class="chip"><span class="sp"></span>Active projects</span>';
333
+ renderKpis(null);
334
+
335
+ const [uRes,pRes]=await Promise.all([call('list_users'),call('list_active_projects')]);
336
+ USERS=asArray(uRes).filter(u=>u.name&&u.name!=='Placeholder');
337
+ PROJECTS=asArray(pRes);
338
+ setChip('users',USERS.length?'done':'err',USERS.length+' members');
339
+ setChip('projects',PROJECTS.length?'done':'err',PROJECTS.length+' projects');
340
+
341
+ USERS.sort((a,b)=>(b.totalEstimatedHours||0)-(a.totalEstimatedHours||0)||(a.name||'').localeCompare(b.name||''));
342
+
343
+ const uSel=document.getElementById('userSel');
344
+ const savedU=localStorage.getItem('fx_user');
345
+ // First load uses Claude's DEFAULT_USER; a saved pick overrides it thereafter.
346
+ const initialSel=(savedU&&USERS.some(u=>u.userId===savedU))?savedU:resolveDefaultUser();
347
+ fillSelect(uSel,USERS,'userId',u=>`${u.name}${u.initials?' ('+u.initials+')':''} — ${u.totalActiveProjects||0} proj`,initialSel);
348
+
349
+ // Selecting a member fetches just that person's open tasks (server-side filter).
350
+ async function select(userId){ await renderProfile(userId); await loadTasks(userId); }
351
+
352
+ uSel.onchange=async()=>{ localStorage.setItem('fx_user',uSel.value); syncReset(); await select(uSel.value); };
353
+
354
+ // Reset-to-default link: clears the saved pick so DEFAULT_USER wins again.
355
+ const reset=document.getElementById('resetDef');
356
+ function syncReset(){ reset.style.display=(DEFAULT_USER&&localStorage.getItem('fx_user'))?'':'none'; }
357
+ reset.onclick=async(e)=>{ e.preventDefault(); localStorage.removeItem('fx_user');
358
+ const d=resolveDefaultUser(); if(d) uSel.value=d; syncReset(); await select(uSel.value); };
359
+ syncReset();
360
+
361
+ document.getElementById('sub').textContent='Live · '+USERS.length+' team members';
362
+ await select(uSel.value);
363
+ }
364
+ boot().catch(e=>{ document.getElementById('profilePanel').innerHTML=
365
+ '<h3>Profile</h3><div class="center">Failed to load Fenix data.</div>'; });
366
+ </script>
367
+ </body>
368
+ </html>
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: Refresh the Fenix patent skills from the server (per-firm customized, latest version)
3
+ ---
4
+
5
+ The Fenix plugin ships a **static snapshot** of the patent skills so they work the moment you install it. Run this command to pull the **latest, firm-customized** versions from the connected Fenix server (the snapshot is a generic default; your server account may customize the prompt content).
6
+
7
+ For each skill — `fenix-application`, `fenix-office-action`, `fenix-project`:
8
+
9
+ 1. Call the `install_fenix_skills` MCP tool with that `skill` name. If the skill is already installed locally, read its `VERSION` file and pass `fromVersion` so the server returns only changed files (incremental update).
10
+ 2. Follow the `nextStep` in the response to save it.
11
+ 3. **Never overwrite an existing `CUSTOMIZE.md`.** If a local `CUSTOMIZE.md` already exists for the skill, keep that existing file and discard the downloaded copy — it holds the user's own customizations.
12
+
13
+ The connector is configured by this plugin and **infers your firm (client + database) from your single API key**, so never pass a `clientDb` or client argument to any tool — let it default.
14
+
15
+ If `install_fenix_skills` returns 401/unauthorized, the API key isn't set — run `/fenix-setup` first.
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Configure the Fenix plugin's API key (one key; client and database are inferred)
3
+ ---
4
+
5
+ Help the user connect the Fenix plugin. **Only one value is needed: a single API key.**
6
+
7
+ 1. Confirm the user has a **Fenix API key** (issued by Fenix for their firm). The server maps this key to their account and **infers the client and database from it** — they never provide a client name, database, or any second value.
8
+ 2. The connector reads the key from the **`FENIX_API_KEY`** environment variable and sends it as a bearer token to `https://claude.fenix.ai/api/plugin`.
9
+ 3. Guide them to set `FENIX_API_KEY` (do not type the key into chat; have them set it themselves):
10
+ - **macOS / Linux:** add `export FENIX_API_KEY="<their key>"` to `~/.zshrc` or `~/.bashrc`, then restart the terminal and Claude Code.
11
+ - **Windows (PowerShell):** run `setx FENIX_API_KEY "<their key>"`, then restart the terminal and Claude Code.
12
+ 4. Verify the connection by calling a read-only tool such as `get_project` (or run `/fenix-install`). A 401/unauthorized means the key is missing or wrong, or Claude Code wasn't restarted after setting it.
13
+
14
+ Never print, log, or store the key anywhere other than the environment variable.
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "fenix-claude-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Fenix patent automation plugin for Claude Code — MCP connector plus patent drafting, office-action, and project skills.",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://claude.fenix.ai",
7
+ "keywords": ["claude", "claude-code", "claude-plugin", "patent", "fenix", "mcp"],
8
+ "files": [
9
+ ".claude-plugin/",
10
+ ".mcp.json",
11
+ "commands/",
12
+ "skills/",
13
+ "artifacts/",
14
+ "README.md"
15
+ ]
16
+ }
@@ -0,0 +1 @@
1
+ place custom instructions here
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: fenix-application
3
+ description: >-
4
+ Full patent application drafting and review skill: claims drafting, figure planning, spec sections, pre-filing review (structural, claims, §101, profanity, §112, style), patent language guidelines, HTML editors, and Python helper scripts.
5
+ ---
6
+
7
+ # Fenix Application
8
+
9
+ **Skill version: 11** (authoritative version is in the top-level `VERSION` file; this line is for reference only).
10
+
11
+ ## Triggers
12
+
13
+ - draft a patent
14
+ - patent application
15
+ - draft claims
16
+ - draft figures
17
+ - draft spec
18
+ - open the claims editor
19
+ - open the figures editor
20
+ - patent safe language
21
+ - review this application
22
+ - review the spec
23
+ - is this ready to file
24
+ - audit the claims
25
+ - red-line this
26
+ - check this application
27
+ - spot issues
28
+ - what's wrong with claim
29
+
30
+ ## Customization
31
+
32
+ Before applying any workflow in this skill, read the `CUSTOMIZE.md` file in this skill directory. If it contains anything other than the placeholder text "place custom instructions here", treat its contents as user overrides that take precedence over the default instructions in this SKILL.md and its reference files.
33
+
34
+ Apply the custom overrides **only as long as the result still satisfies every structured-output contract** this skill depends on, including:
35
+ - the JSON shapes passed to `save_project` (claims, figures, sections)
36
+ - the `[EDITOR SUBMIT - <type>]` clipboard prefixes produced by the HTML editors
37
+ - the figure ElementSchema (`type`, `label`, `number`, `text`) and component numbering rules
38
+ - the spec section names and ordering required by the save/print tools
39
+
40
+ Custom instructions may freely change tone, phrasing, default counts, section preferences, review emphasis, and similar. If a custom instruction would break a required format, follow the default format and tell the user which customization could not be applied and why.
41
+
42
+ ## Contents
43
+ - `references/patent.md` -- 3-phase workflow
44
+ - `references/claims.md` -- claims drafting + verification
45
+ - `references/figures.md` -- Phase 1+2 figure workflow
46
+ - `references/spec.md` -- spec section workflow
47
+ - `references/editors.md` -- exact INITIAL_CONTENT shapes + submit handling for the 3 HTML editors (read this instead of the editor HTML)
48
+ - `references/patent-safe.md` -- language guidelines
49
+ - `references/figure-description.md` -- figure description protocol
50
+ - `references/disclosure.md` -- disclosure interview
51
+ - `references/figures-guidance.md` -- figure planning prompts
52
+ - `references/spec-guidance.md` -- spec section prompts
53
+ - `references/patent-review.md` -- pre-filing review workflow (6-section checklist)
54
+ - `references/patent-review-checklist.md` -- 16-point final review checklist
55
+ - `references/patent-review-claim-formats.md` -- Method/CRM/System/Training templates
56
+ - `references/patent-review-advantages.md` -- advantages paragraph template
57
+ - `references/patent-review-profanity.md` -- patent profanity term list
58
+ - `assets/claims_editor.html` -- accordion editor for ALL claims (open any claim to edit; keeps claims parsed; outputs a JSON claim array in the Fenix schema)
59
+ - `assets/figures_editor.html` -- accordion editor for ALL figures (open any figure to edit its steps/components table; outputs figures JSON array)
60
+ - `assets/spec_editor.html` -- accordion editor for ALL spec sections, each figure description its own section (outputs sections JSON object)
61
+ - `scripts/parse_claims.py` -- local claims parser
62
+ - `scripts/calculate_figure_layout.py` -- local layout calculator
63
+
64
+ ## How to use
65
+
66
+ Read the relevant section below for the task at hand, then follow it to the specific reference file. Scripts in `scripts/` run locally without calling Fenix endpoints.
67
+
68
+ ## Gather invention disclosure
69
+
70
+ Trigger when the user wants to describe a new invention or answer disclosure questions:
71
+ - "run the disclosure interview" / "gather the disclosure" / "new invention"
72
+
73
+ Read `references/disclosure.md` for the structured interview protocol. Captures technical field, problem, inventive concept, method steps, components, variations, examples, and prior art.
74
+
75
+ ## Review a patent application
76
+
77
+ Trigger when the user asks to review, audit, red-line, or check a patent application or claim set -- including "is this ready to file?" or "what's wrong with claim 1?"
78
+
79
+ 1. Read `references/patent-review.md` for the full 6-section review workflow.
80
+ 2. Sub-references used during the review:
81
+ - `references/patent-review-checklist.md` -- 16-point final checklist
82
+ - `references/patent-review-claim-formats.md` -- claim format templates (Method/CRM/System/Training)
83
+ - `references/patent-review-advantages.md` -- advantages paragraph template and defects
84
+ - `references/patent-review-profanity.md` -- patent profanity term list
85
+ - `references/patent-safe.md` -- language guidelines (also used during drafting)
86
+ 3. Input: uploaded .docx, pasted text, or Fenix project via `get_project({ projectId, include: ["claims","sections"] })`.
87
+ 4. Output: inline issue list tagged 🔴 Blocker / 🟡 Issue / 🔵 Nit, grouped by section, ending with a summary line.
88
+
89
+ ## Draft a patent application
90
+
91
+ Read `references/patent.md` for the 3-phase workflow overview. Phase-specific references:
92
+ - **Claims**: `references/claims.md`, `references/patent-safe.md`
93
+ - **Figures**: `references/figures.md`, `references/figures-guidance.md`
94
+ - **Spec**: `references/spec.md`, `references/spec-guidance.md`, `references/figure-description.md`
95
+ - **Disclosure interview**: `references/disclosure.md`
@@ -0,0 +1 @@
1
+ 11