fenix-claude-plugin 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.mcp.json +1 -1
  3. package/artifacts/claims-editor.html +205 -0
  4. package/artifacts/figures-editor.html +237 -0
  5. package/artifacts/oa-arguments-editor.html +216 -0
  6. package/artifacts/project-overview.html +1 -1
  7. package/artifacts/sections-editor.html +205 -0
  8. package/commands/fenix-install.md +3 -1
  9. package/commands/fenix-setup.md +8 -3
  10. package/package.json +1 -1
  11. package/skills/fenix-application/SKILL.md +5 -1
  12. package/skills/fenix-application/VERSION +1 -1
  13. package/skills/fenix-application/references/claim1-method-drafting.md +1 -1
  14. package/skills/fenix-application/references/claims.md +2 -2
  15. package/skills/fenix-application/references/editors.md +21 -3
  16. package/skills/fenix-application/references/figure-description.md +1 -1
  17. package/skills/fenix-application/references/figures-guidance.md +1 -1
  18. package/skills/fenix-application/references/figures.md +16 -2
  19. package/skills/fenix-application/references/patent-review.md +1 -1
  20. package/skills/fenix-application/references/patent-safe.md +1 -1
  21. package/skills/fenix-application/references/patent.md +4 -3
  22. package/skills/fenix-application/references/spec-guidance.md +1 -1
  23. package/skills/fenix-application/references/spec.md +2 -2
  24. package/skills/fenix-office-action/SKILL.md +4 -4
  25. package/skills/fenix-office-action/VERSION +1 -1
  26. package/skills/fenix-office-action/references/oa-analysis.md +3 -3
  27. package/skills/fenix-office-action/references/oa-drafting.md +62 -45
  28. package/skills/fenix-office-action/references/oa-response.md +23 -10
@@ -0,0 +1,216 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>OA Arguments Editor</title>
6
+ <style>
7
+ *{box-sizing:border-box;margin:0;padding:0}
8
+ body{font-family:system-ui,-apple-system,sans-serif;background:#fff7ed;min-height:100vh;padding:20px 16px}
9
+ .card{background:#fff;max-width:900px;margin:0 auto;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.12);overflow:hidden}
10
+ .hdr{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid #fed7aa}
11
+ .hdr h1{font-size:15px;font-weight:600;color:#1e293b}.hdr .rev{font-size:12px;color:#94a3b8}
12
+ .hdr .toggle-all{margin-left:auto;flex-shrink:0}
13
+ .bdy{padding:20px}.sub{font-size:13px;color:#64748b;margin-bottom:14px}
14
+ input,select,textarea{font-family:inherit;font-size:13px;border:1.5px solid #fed7aa;border-radius:5px;padding:6px 10px;outline:none;width:100%;transition:border-color .15s;background:#fff}
15
+ input:focus,select:focus,textarea:focus{border-color:#ea580c}
16
+ .field{margin-bottom:12px}label{display:block;font-size:12px;font-weight:600;color:#374151;margin-bottom:4px}
17
+ .ctx{font-size:12px;color:#64748b;background:#fffbf5;border:1px solid #fed7aa;border-radius:5px;padding:8px 10px;margin-bottom:12px;line-height:1.5;white-space:pre-wrap}
18
+ .acc{border:1px solid #fed7aa;overflow:hidden;margin-bottom:-1px}
19
+ .acc-hd{display:flex;align-items:center;gap:10px;padding:11px 14px;cursor:pointer;background:#fffbf5;user-select:none}
20
+ .acc-hd:hover{background:#fff7ed}
21
+ .acc-hd .chev{color:#ea580c;font-size:12px;transition:transform .15s;flex-shrink:0}
22
+ .acc.open .chev{transform:rotate(90deg)}
23
+ .acc-hd .ttl{font-weight:600;font-size:13px;color:#1e293b;flex-shrink:0}
24
+ .acc-hd .prev{font-size:12px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
25
+ .acc-bd{display:none;padding:14px;border-top:1px solid #fed7aa;background:#fff}
26
+ .acc.open .acc-bd{display:block}
27
+ .btn-sm{padding:4px 12px;background:#fff;border:1.5px solid #fed7aa;border-radius:5px;font-size:12px;cursor:pointer;color:#374151}
28
+ .btn-sm:hover{border-color:#ea580c}
29
+ .bar{margin-top:16px;display:flex;align-items:center;gap:14px}
30
+ .cta{padding:9px 24px;background:#ea580c;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s}
31
+ .cta:hover{background:#c2410c}.cta:disabled{opacity:.5;cursor:default}
32
+ #st{font-size:13px;font-weight:500}#st.ok{color:#16a34a}#st.err{color:#dc2626}#st.busy{color:#64748b}
33
+ textarea.arg{min-height:160px;line-height:1.6;resize:vertical}
34
+ .diff{display:none;margin-top:16px;border:1px solid #fed7aa;border-radius:6px;overflow:hidden}
35
+ .diff.show{display:block}
36
+ .diff-hd{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff7ed;font-size:12px;font-weight:600;color:#c2410c}
37
+ .diff pre{margin:0;padding:12px;font-size:12px;font-family:Menlo,Consolas,monospace;line-height:1.5;white-space:pre-wrap;max-height:240px;overflow:auto;color:#1e293b}
38
+ .ftr{padding:9px 20px;border-top:1px solid #fed7aa;font-size:11px;color:#94a3b8;display:flex;justify-content:space-between}
39
+ </style>
40
+ </head>
41
+ <body>
42
+ <div class="card">
43
+ <div class="hdr"><h1>OA Arguments Editor</h1><span class="rev" id="revLbl"></span><button class="btn-sm toggle-all" id="toggleAll" onclick="toggleAll()" style="display:none">Expand all</button></div>
44
+ <div class="bdy">
45
+ <p class="sub" id="sub">Loading the Office Action rejections from Fenix…</p>
46
+ <div id="acc"></div>
47
+ <div class="bar"><button class="cta" id="saveBtn" onclick="save()" disabled>Save to Fenix</button><span id="st"></span></div>
48
+ <div class="diff" id="diffPanel">
49
+ <div class="diff-hd"><span>Saved. Paste this summary into chat so the assistant stays in sync:</span><button class="btn-sm" style="margin-left:auto" onclick="copyDiff()">Copy summary</button></div>
50
+ <pre id="diffText"></pre>
51
+ </div>
52
+ </div>
53
+ <div class="ftr"><span>Fenix Patent Assistant — live editor</span><span>v1</span></div>
54
+ </div>
55
+ <script>
56
+ // The assistant replaces __PROJECT_ID__ with the real Fenix project id before opening this artifact.
57
+ var PROJECT_ID = "__PROJECT_ID__";
58
+ // The assistant also substitutes this connector's MCP tool prefix here, e.g. "mcp__<connector-id>__".
59
+ var SERVER_PREFIX = "__SERVER_PREFIX__";
60
+
61
+ // Baseline = the full oa_data object last loaded from / saved to Fenix. We edit only the per-rejection
62
+ // argument + response_strategy and write the WHOLE merged oa_data back (read-merge-write), so every
63
+ // other key (rejections' other fields, dispositions, original_claims, references) is preserved.
64
+ var baseOaData = null;
65
+ var baseRejections = [];
66
+
67
+ // ---- Cowork MCP plumbing ----
68
+ function unwrap(res){
69
+ if(res==null) return res;
70
+ if(typeof res==="string"){ try{ return JSON.parse(res); }catch(e){ return res; } }
71
+ if(Array.isArray(res)) return res;
72
+ if(typeof res==="object" && Array.isArray(res.content)){
73
+ var t=res.content.map(function(c){ return (c&&c.text)||""; }).join("");
74
+ try{ return JSON.parse(t); }catch(e){ return res; }
75
+ }
76
+ if(res && res.result!==undefined) return unwrap(res.result);
77
+ return res;
78
+ }
79
+ function toolName(n){ return (SERVER_PREFIX && SERVER_PREFIX.indexOf("__SERVER")!==0) ? SERVER_PREFIX + n : n; }
80
+ async function call(name,args){
81
+ var tries=[toolName(name)]; if(tries[0]!==name) tries.push(name);
82
+ var err; for(var i=0;i<tries.length;i++){ try{ return unwrap(await window.cowork.callMcpTool(tries[i],args||{})); }catch(e){ err=e; } }
83
+ throw err;
84
+ }
85
+
86
+ // ---- Accordion editor (one per rejection) ----
87
+ function elt(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }
88
+ function rejLabel(r,i){
89
+ var t=(r.status_type||"rejection"); var st=/^\d/.test(t)?("§"+t):t;
90
+ var cl=Array.isArray(r.status_claims)&&r.status_claims.length?(" — claims "+r.status_claims.join(", ")):"";
91
+ return "Rejection "+(i+1)+": "+st+cl;
92
+ }
93
+ function ctxText(r){
94
+ var parts=[];
95
+ if(r.match) parts.push(String(r.match).replace(/\s+/g," ").trim().slice(0,400));
96
+ var meta=[];
97
+ if(r.page) meta.push("OA p."+r.page);
98
+ if(Array.isArray(r.status_refs)&&r.status_refs.length) meta.push(r.status_refs.length+" reference(s)");
99
+ if(r.response_strategy) meta.push("strategy: "+r.response_strategy);
100
+ if(meta.length) parts.push(meta.join(" · "));
101
+ return parts.join("\n\n");
102
+ }
103
+ function addRejection(r,i){
104
+ var acc=document.getElementById("acc");
105
+ var wrap=elt("div","acc"); var hd=elt("div","acc-hd");
106
+ hd.appendChild(elt("span","chev","▶")); hd.appendChild(elt("span","ttl",rejLabel(r,i)));
107
+ var prev=elt("span","prev",(r.argument||"(no argument yet)").replace(/\s+/g," ").slice(0,80)); hd.appendChild(prev);
108
+ hd.onclick=function(){ wrap.classList.toggle("open"); syncToggle(); };
109
+ var bd=elt("div","acc-bd");
110
+ bd.appendChild(elt("div","ctx",ctxText(r)));
111
+ var fs=elt("div","field"); fs.appendChild(elt("label",null,"Response strategy"));
112
+ var sel=document.createElement("select"); sel.className="r-strat";
113
+ ["","amend","argue","both"].forEach(function(v){ var o=document.createElement("option"); o.value=v; o.textContent=v||"(unset)"; if((r.response_strategy||"")===v)o.selected=true; sel.appendChild(o); });
114
+ fs.appendChild(sel); bd.appendChild(fs);
115
+ var fa=elt("div","field"); fa.appendChild(elt("label",null,"Argument"));
116
+ var ta=document.createElement("textarea"); ta.className="r-arg arg"; ta.value=r.argument||"";
117
+ ta.oninput=function(){ prev.textContent=(ta.value||"(no argument yet)").replace(/\s+/g," ").slice(0,80); };
118
+ fa.appendChild(ta); bd.appendChild(fa);
119
+ wrap.appendChild(hd); wrap.appendChild(bd); acc.appendChild(wrap);
120
+ }
121
+ function syncToggle(){ var btn=document.getElementById("toggleAll"); var accs=document.querySelectorAll("#acc .acc"); if(!accs.length){ btn.style.display="none"; return; } btn.style.display=""; var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); btn.textContent=anyClosed?"Expand all":"Collapse all"; }
122
+ function toggleAll(){ var accs=document.querySelectorAll("#acc .acc"); var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); accs.forEach(function(a){ if(anyClosed){ a.classList.add("open"); } else { a.classList.remove("open"); } }); syncToggle(); }
123
+
124
+ // Read the editor back into an array of { argument, response_strategy } aligned to baseRejections by index.
125
+ function collect(){
126
+ var out=[]; document.querySelectorAll("#acc .acc").forEach(function(a){
127
+ out.push({ argument:a.querySelector(".r-arg").value, response_strategy:a.querySelector(".r-strat").value });
128
+ }); return out;
129
+ }
130
+
131
+ function setStatus(msg,cls){ var s=document.getElementById("st"); s.textContent=msg||""; s.className=cls||""; }
132
+
133
+ async function load(){
134
+ if(!(window.cowork && typeof window.cowork.callMcpTool==="function")){
135
+ document.getElementById("sub").textContent="";
136
+ setStatus("⚠ Open this as a LIVE ARTIFACT, not a file preview — it needs the Cowork bridge (window.cowork) to read and save Fenix data. Ask the assistant to open it as a live artifact.","err");
137
+ return;
138
+ }
139
+ try{
140
+ var data=await call("get_project",{ projectId:PROJECT_ID, include:["oa_data"] });
141
+ baseOaData=(data&&data.oa_data)||{};
142
+ baseRejections=Array.isArray(baseOaData.oa_rejections)?baseOaData.oa_rejections:[];
143
+ document.getElementById("acc").innerHTML="";
144
+ if(!baseRejections.length){
145
+ document.getElementById("sub").textContent="No rejections found on this project. Populate it from USPTO first.";
146
+ return;
147
+ }
148
+ baseRejections.forEach(function(r,i){ addRejection(r,i); });
149
+ document.getElementById("sub").textContent="Open each rejection to edit its argument and response strategy, then Save to Fenix.";
150
+ document.getElementById("saveBtn").disabled=false;
151
+ syncToggle();
152
+ }catch(e){
153
+ document.getElementById("sub").textContent="";
154
+ setStatus("Could not load rejections: "+((e&&e.message)||e),"err");
155
+ }
156
+ }
157
+
158
+ function summaryText(edited){
159
+ var lines=["[OA ARGUMENTS SAVED]"]; var changed=[], unchanged=0;
160
+ edited.forEach(function(e,i){
161
+ var b=baseRejections[i]||{};
162
+ if((e.argument||"")!==(b.argument||"") || (e.response_strategy||"")!==(b.response_strategy||"")){
163
+ changed.push({ i:i, e:e, b:b });
164
+ } else { unchanged++; }
165
+ });
166
+ if(!changed.length){ lines.push("No argument changes (re-saved identical)."); return lines.join("\n"); }
167
+ changed.forEach(function(c){
168
+ lines.push(""); lines.push(rejLabel(baseRejections[c.i],c.i)+":");
169
+ if((c.e.response_strategy||"")!==(c.b.response_strategy||"")) lines.push(" strategy: "+(c.b.response_strategy||"(unset)")+" -> "+(c.e.response_strategy||"(unset)"));
170
+ lines.push(" argument (full new text): "+((c.e.argument||"").trim()||"(cleared)"));
171
+ });
172
+ if(unchanged) { lines.push(""); lines.push("Unchanged rejections: "+unchanged); }
173
+ return lines.join("\n");
174
+ }
175
+
176
+ async function save(){
177
+ var btn=document.getElementById("saveBtn"); btn.disabled=true; setStatus("Saving…","busy");
178
+ document.getElementById("diffPanel").classList.remove("show");
179
+ var edited=collect();
180
+ try{
181
+ // Read-merge-write: overlay the edited argument/strategy onto a deep copy of the loaded oa_data,
182
+ // preserving every other field, then save the whole object.
183
+ var merged=JSON.parse(JSON.stringify(baseOaData));
184
+ merged.oa_rejections=(merged.oa_rejections||[]).map(function(r,i){
185
+ var e=edited[i]||{}; var out=Object.assign({}, r, { argument:e.argument });
186
+ if(e.response_strategy) out.response_strategy=e.response_strategy; else delete out.response_strategy;
187
+ return out;
188
+ });
189
+ var res=await call("save_project",{ projectId:PROJECT_ID, fields:{ oa_data:merged } });
190
+ if(res && res.isError){ throw new Error(res.error||"save failed"); }
191
+ if(res && res.success===false){ throw new Error("save failed"); }
192
+ var summary=summaryText(edited);
193
+ // Advance the baseline to what we just saved.
194
+ baseOaData=merged; baseRejections=merged.oa_rejections;
195
+ var nChanged=summary.split("\n").filter(function(l){ return /:$/.test(l); }).length;
196
+ setStatus("✓ Saved to Fenix"+(nChanged?" — "+nChanged+" argument(s) changed":""),"ok");
197
+ document.getElementById("diffText").textContent=summary;
198
+ document.getElementById("diffPanel").classList.add("show");
199
+ document.getElementById("diffPanel").__summary=summary;
200
+ try{ if(window.cowork && typeof window.cowork.sendPrompt==="function"){ window.cowork.sendPrompt(summary); } }catch(e){}
201
+ }catch(e){
202
+ setStatus("Save failed: "+((e&&e.message)||e),"err");
203
+ }finally{ btn.disabled=false; }
204
+ }
205
+
206
+ function copyDiff(){
207
+ var txt=document.getElementById("diffPanel").__summary||document.getElementById("diffText").textContent;
208
+ var done=function(){ setStatus("Summary copied — paste it into chat.","ok"); };
209
+ if(navigator.clipboard){ navigator.clipboard.writeText(txt).then(done).catch(fb); } else { fb(); }
210
+ function fb(){ var el=document.createElement("textarea"); el.value=txt; el.style.cssText="position:fixed;top:0;left:0;opacity:0;"; document.body.appendChild(el); el.select(); try{ document.execCommand("copy"); done(); }catch(e){} document.body.removeChild(el); }
211
+ }
212
+
213
+ window.onload=load;
214
+ </script>
215
+ </body>
216
+ </html>
@@ -433,7 +433,7 @@ async function loadProject(pid) {
433
433
  localStorage.setItem(LS_KEY, pid);
434
434
  try {
435
435
  const [full, tasksRaw] = await Promise.all([
436
- call(FENIX.project, { projectId: pid }).catch(() => null),
436
+ call(FENIX.project, { projectId: pid, include: ["matter", "project", "patent"] }).catch(() => null),
437
437
  call(FENIX.tasks, { projectId: pid }).catch(() => null),
438
438
  ]);
439
439
  if (!full) { c.innerHTML = '<div class="center err">No data returned for this matter.</div>'; return; }
@@ -0,0 +1,205 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Spec Sections Editor</title>
6
+ <style>
7
+ *{box-sizing:border-box;margin:0;padding:0}
8
+ body{font-family:system-ui,-apple-system,sans-serif;background:#fff7ed;min-height:100vh;padding:20px 16px}
9
+ .card{background:#fff;max-width:900px;margin:0 auto;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.12);overflow:hidden}
10
+ .hdr{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid #fed7aa}
11
+ .hdr h1{font-size:15px;font-weight:600;color:#1e293b}.hdr .rev{font-size:12px;color:#94a3b8}
12
+ .hdr .toggle-all{margin-left:auto;flex-shrink:0}
13
+ .bdy{padding:20px}.sub{font-size:13px;color:#64748b;margin-bottom:14px}
14
+ input,select,textarea{font-family:inherit;font-size:13px;border:1.5px solid #fed7aa;border-radius:5px;padding:6px 10px;outline:none;width:100%;transition:border-color .15s;background:#fff}
15
+ input:focus,select:focus,textarea:focus{border-color:#ea580c}
16
+ .field{margin-bottom:12px}label{display:block;font-size:12px;font-weight:600;color:#374151;margin-bottom:4px}
17
+ .acc{border:1px solid #fed7aa;overflow:hidden;margin-bottom:-1px}
18
+ .acc-hd{display:flex;align-items:center;gap:10px;padding:11px 14px;cursor:pointer;background:#fffbf5;user-select:none}
19
+ .acc-hd:hover{background:#fff7ed}
20
+ .acc-hd .chev{color:#ea580c;font-size:12px;transition:transform .15s;flex-shrink:0}
21
+ .acc.open .chev{transform:rotate(90deg)}
22
+ .acc-hd .ttl{font-weight:600;font-size:13px;color:#1e293b;flex-shrink:0}
23
+ .acc-hd .prev{font-size:12px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
24
+ .acc-bd{display:none;padding:14px;border-top:1px solid #fed7aa;background:#fff}
25
+ .acc.open .acc-bd{display:block}
26
+ .btn-sm{padding:4px 12px;background:#fff;border:1.5px solid #fed7aa;border-radius:5px;font-size:12px;cursor:pointer;color:#374151}
27
+ .btn-sm:hover{border-color:#ea580c}
28
+ .rm-item{margin-left:auto;color:#dc2626;background:none;border:none;cursor:pointer;font-size:13px;flex-shrink:0}
29
+ .bar{margin-top:16px;display:flex;align-items:center;gap:14px}
30
+ .cta{padding:9px 24px;background:#ea580c;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s}
31
+ .cta:hover{background:#c2410c}.cta:disabled{opacity:.5;cursor:default}
32
+ #st{font-size:13px;font-weight:500}#st.ok{color:#16a34a}#st.err{color:#dc2626}#st.busy{color:#64748b}
33
+ textarea.sec{min-height:200px;font-family:Menlo,Consolas,monospace;line-height:1.6;resize:vertical}
34
+ .addbar{margin:6px 0 4px}
35
+ .diff{display:none;margin-top:16px;border:1px solid #fed7aa;border-radius:6px;overflow:hidden}
36
+ .diff.show{display:block}
37
+ .diff-hd{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff7ed;font-size:12px;font-weight:600;color:#c2410c}
38
+ .diff pre{margin:0;padding:12px;font-size:12px;font-family:Menlo,Consolas,monospace;line-height:1.5;white-space:pre-wrap;max-height:240px;overflow:auto;color:#1e293b}
39
+ .ftr{padding:9px 20px;border-top:1px solid #fed7aa;font-size:11px;color:#94a3b8;display:flex;justify-content:space-between}
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div class="card">
44
+ <div class="hdr"><h1>Spec Sections Editor</h1><span class="rev" id="revLbl"></span><button class="btn-sm toggle-all" id="toggleAll" onclick="toggleAll()" style="display:none">Expand all</button></div>
45
+ <div class="bdy">
46
+ <p class="sub" id="sub">Loading current spec sections from Fenix…</p>
47
+ <div id="acc"></div>
48
+ <div class="addbar"><button class="btn-sm" onclick="addItem()">+ Add section</button></div>
49
+ <div class="bar"><button class="cta" id="saveBtn" onclick="save()" disabled>Save to Fenix</button><span id="st"></span></div>
50
+ <div class="diff" id="diffPanel">
51
+ <div class="diff-hd"><span>Saved. Paste this summary into chat so the assistant stays in sync:</span><button class="btn-sm" style="margin-left:auto" onclick="copyDiff()">Copy summary</button></div>
52
+ <pre id="diffText"></pre>
53
+ </div>
54
+ </div>
55
+ <div class="ftr"><span>Fenix Patent Assistant — live editor</span><span>v1</span></div>
56
+ </div>
57
+ <script>
58
+ // The assistant replaces __PROJECT_ID__ with the real Fenix project id before opening this artifact.
59
+ var PROJECT_ID = "__PROJECT_ID__";
60
+ // The assistant also substitutes this connector's MCP tool prefix here, e.g. "mcp__<connector-id>__"
61
+ // (the prefix differs per connection, so it cannot be hardcoded). If left unsubstituted, call()
62
+ // falls back to the bare tool name (which works on hosts that don't namespace MCP tools).
63
+ var SERVER_PREFIX = "__SERVER_PREFIX__";
64
+
65
+ var baseline = {}; // { name: plainText } last loaded from / saved to Fenix
66
+ var baselineRev = null;
67
+
68
+ function unwrap(res){
69
+ if(res==null) return res;
70
+ if(typeof res==="string"){ try{ return JSON.parse(res); }catch(e){ return res; } }
71
+ if(Array.isArray(res)) return res;
72
+ if(typeof res==="object" && Array.isArray(res.content)){
73
+ var t=res.content.map(function(c){ return (c&&c.text)||""; }).join("");
74
+ try{ return JSON.parse(t); }catch(e){ return res; }
75
+ }
76
+ if(res && res.result!==undefined) return unwrap(res.result);
77
+ return res;
78
+ }
79
+ function toolName(n){ return (SERVER_PREFIX && SERVER_PREFIX.indexOf("__SERVER")!==0) ? SERVER_PREFIX + n : n; }
80
+ async function call(name,args){
81
+ var tries=[toolName(name)]; if(tries[0]!==name) tries.push(name);
82
+ var err; for(var i=0;i<tries.length;i++){ try{ return unwrap(await window.cowork.callMcpTool(tries[i],args||{})); }catch(e){ err=e; } }
83
+ throw err;
84
+ }
85
+
86
+ function elt(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }
87
+ function makeAcc(headerTitle,preview,bodyBuilder){
88
+ var wrap=elt("div","acc"); var hd=elt("div","acc-hd");
89
+ hd.appendChild(elt("span","chev","▶")); hd.appendChild(elt("span","ttl",headerTitle));
90
+ var prev=elt("span","prev",preview||""); hd.appendChild(prev);
91
+ var rm=elt("button","rm-item","Remove"); rm.onclick=function(ev){ ev.stopPropagation(); wrap.remove(); syncToggle(); }; hd.appendChild(rm);
92
+ hd.onclick=function(){ wrap.classList.toggle("open"); syncToggle(); };
93
+ var bd=elt("div","acc-bd"); bodyBuilder(bd,prev);
94
+ wrap.appendChild(hd); wrap.appendChild(bd); return wrap;
95
+ }
96
+ function secBody(bd,prev,name,text){
97
+ var f1=elt("div","field"); f1.appendChild(elt("label",null,"Section Name")); var ni=document.createElement("input"); ni.className="s-name"; ni.value=name||""; f1.appendChild(ni); bd.appendChild(f1);
98
+ var f2=elt("div","field"); f2.appendChild(elt("label",null,"Text (blank line = new paragraph)")); var ta=document.createElement("textarea"); ta.className="s-txt sec"; ta.value=text||""; ta.oninput=function(){ prev.textContent=(ta.value||"").replace(/\n/g," ").slice(0,80); }; f2.appendChild(ta); bd.appendChild(f2);
99
+ }
100
+ function addSection(name,text,orig){ var acc=document.getElementById("acc"); var w=makeAcc(name||"Section",(text||"").replace(/\n/g," ").slice(0,80),function(bd,prev){ secBody(bd,prev,name,text); }); w.__sec=orig||{}; acc.appendChild(w); return w; }
101
+ function addItem(){ addSection("New Section","").classList.add("open"); syncToggle(); }
102
+ function syncToggle(){ var btn=document.getElementById("toggleAll"); var accs=document.querySelectorAll("#acc .acc"); if(!accs.length){ btn.style.display="none"; return; } btn.style.display=""; var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); btn.textContent=anyClosed?"Expand all":"Collapse all"; }
103
+ function toggleAll(){ var accs=document.querySelectorAll("#acc .acc"); var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); accs.forEach(function(a){ if(anyClosed){ a.classList.add("open"); } else { a.classList.remove("open"); } }); syncToggle(); }
104
+
105
+ // Strip <p> wrappers to editable plain text (blank line between paragraphs).
106
+ function paragraphsToText(paras){ return (paras||[]).map(function(p){ return String(p).replace(/<[^>]+>/g,"").trim(); }).filter(Boolean).join("\n\n"); }
107
+ // Editable text -> Fenix paragraph array (each paragraph wrapped in <p>).
108
+ function textToParagraphs(tx){ return String(tx||"").split(/\n\n+/).map(function(p){ return p.trim(); }).filter(Boolean).map(function(p){ return "<p>"+p.replace(/\n/g," ")+"</p>"; }); }
109
+
110
+ function collect(){
111
+ var secs={}; document.querySelectorAll("#acc .acc").forEach(function(a){
112
+ var nm=a.querySelector(".s-name").value.trim(); var tx=a.querySelector(".s-txt").value;
113
+ // Overlay edits onto the ORIGINAL section so any fields besides paragraphs survive.
114
+ if(nm){ secs[nm]=Object.assign({}, a.__sec||{}, { paragraphs:textToParagraphs(tx) }); }
115
+ }); return secs;
116
+ }
117
+
118
+ // ---- Diff by section name (compare normalized text) ----
119
+ function normText(tx){ return String(tx||"").replace(/\s+/g," ").trim(); }
120
+ function diff(oldMap,newSecs){
121
+ // newSecs is { name: { paragraphs } }; oldMap is { name: plainText }
122
+ var newMap={}; Object.keys(newSecs).forEach(function(k){ newMap[k]=paragraphsToText(newSecs[k].paragraphs); });
123
+ var changed=[], added=[], removed=[], unchanged=[];
124
+ Object.keys(newMap).forEach(function(k){
125
+ if(!(k in oldMap)){ added.push({name:k,text:newMap[k]}); }
126
+ else if(normText(newMap[k])!==normText(oldMap[k])){ changed.push({name:k,text:newMap[k]}); }
127
+ else { unchanged.push(k); }
128
+ });
129
+ Object.keys(oldMap).forEach(function(k){ if(!(k in newMap)) removed.push(k); });
130
+ changed.sort(function(a,b){ return a.name<b.name?-1:1; }); added.sort(function(a,b){ return a.name<b.name?-1:1; });
131
+ removed.sort(); unchanged.sort();
132
+ return { changed:changed, added:added, removed:removed, unchanged:unchanged };
133
+ }
134
+ function summaryText(d,rev){
135
+ var lines=["[SECTIONS SAVED - sectionsRev "+(rev==null?"?":rev)+"]"];
136
+ if(!d.changed.length && !d.added.length && !d.removed.length){ lines.push("No section changes (re-saved identical set)."); return lines.join("\n"); }
137
+ if(d.changed.length){ lines.push(""); lines.push("Changed sections (full text):"); d.changed.forEach(function(s){ lines.push("## "+s.name); lines.push(s.text); }); }
138
+ if(d.added.length){ lines.push(""); lines.push("Added sections (full text):"); d.added.forEach(function(s){ lines.push("## "+s.name); lines.push(s.text); }); }
139
+ if(d.removed.length){ lines.push(""); lines.push("Removed sections: "+d.removed.join(", ")); }
140
+ if(d.unchanged.length){ lines.push(""); lines.push("Unchanged: "+d.unchanged.join(", ")); }
141
+ return lines.join("\n");
142
+ }
143
+
144
+ function setStatus(msg,cls){ var s=document.getElementById("st"); s.textContent=msg||""; s.className=cls||""; }
145
+
146
+ async function load(){
147
+ if(!(window.cowork && typeof window.cowork.callMcpTool==="function")){
148
+ document.getElementById("sub").textContent="";
149
+ setStatus("⚠ Open this as a LIVE ARTIFACT, not a file preview — it needs the Cowork bridge (window.cowork) to read and save Fenix data. Ask the assistant to open it as a live artifact.","err");
150
+ return;
151
+ }
152
+ try{
153
+ var data=await call("get_project",{ projectId:PROJECT_ID, include:["sections"] });
154
+ var sections=(data&&data.sections)||{};
155
+ baseline={}; document.getElementById("acc").innerHTML="";
156
+ Object.keys(sections).forEach(function(name){
157
+ var txt=paragraphsToText(sections[name]&&sections[name].paragraphs);
158
+ baseline[name]=txt; addSection(name,txt,sections[name]);
159
+ });
160
+ baselineRev=(data&&data.revs&&typeof data.revs.sections!=="undefined")?data.revs.sections:null;
161
+ document.getElementById("sub").textContent="Open any section to edit it. Blank line = new paragraph. Add or remove sections, then Save to Fenix.";
162
+ document.getElementById("revLbl").textContent=baselineRev==null?"":("rev "+baselineRev);
163
+ document.getElementById("saveBtn").disabled=false;
164
+ syncToggle();
165
+ }catch(e){
166
+ document.getElementById("sub").textContent="";
167
+ setStatus("Could not load sections: "+((e&&e.message)||e),"err");
168
+ }
169
+ }
170
+
171
+ async function save(){
172
+ var btn=document.getElementById("saveBtn"); btn.disabled=true; setStatus("Saving…","busy");
173
+ document.getElementById("diffPanel").classList.remove("show");
174
+ var sections=collect();
175
+ try{
176
+ var res=await call("save_project",Object.assign({ projectId:PROJECT_ID, type:"sections", sections:sections }, (baselineRev!=null?{baseRev:baselineRev}:{})));
177
+ if(res && res.conflict){ setStatus("⚠ Not saved — the sections changed in Fenix since you opened this editor (now at rev "+res.currentRev+"). Reload to get the latest, then re-apply your edits.","err"); return; }
178
+ if(res && res.isError){ throw new Error(res.error||"save failed"); }
179
+ var rev=(res&&typeof res.sectionsRev!=="undefined")?res.sectionsRev:null;
180
+ var d=diff(baseline,sections);
181
+ var summary=summaryText(d,rev);
182
+ // Advance baseline to the saved set (as plain text).
183
+ baseline={}; Object.keys(sections).forEach(function(k){ baseline[k]=paragraphsToText(sections[k].paragraphs); }); baselineRev=rev;
184
+ document.getElementById("revLbl").textContent=rev==null?"":("rev "+rev);
185
+ var nChanged=d.changed.length+d.added.length+d.removed.length;
186
+ setStatus("✓ Saved to Fenix"+(rev==null?"":" (rev "+rev+")")+(nChanged?" — "+nChanged+" section(s) changed":""),"ok");
187
+ document.getElementById("diffText").textContent=summary;
188
+ document.getElementById("diffPanel").classList.add("show");
189
+ document.getElementById("diffPanel").__summary=summary;
190
+ try{ if(window.cowork && typeof window.cowork.sendPrompt==="function"){ window.cowork.sendPrompt(summary); } }catch(e){}
191
+ }catch(e){ setStatus("Save failed: "+((e&&e.message)||e),"err"); }
192
+ finally{ btn.disabled=false; }
193
+ }
194
+
195
+ function copyDiff(){
196
+ var txt=document.getElementById("diffPanel").__summary||document.getElementById("diffText").textContent;
197
+ var done=function(){ setStatus("Summary copied — paste it into chat.","ok"); };
198
+ if(navigator.clipboard){ navigator.clipboard.writeText(txt).then(done).catch(fb); } else { fb(); }
199
+ function fb(){ var el=document.createElement("textarea"); el.value=txt; el.style.cssText="position:fixed;top:0;left:0;opacity:0;"; document.body.appendChild(el); el.select(); try{ document.execCommand("copy"); done(); }catch(e){} document.body.removeChild(el); }
200
+ }
201
+
202
+ window.onload=load;
203
+ </script>
204
+ </body>
205
+ </html>
@@ -12,4 +12,6 @@ For each skill — `fenix-application`, `fenix-office-action`, `fenix-project`:
12
12
 
13
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
14
 
15
- If `install_fenix_skills` returns 401/unauthorized, the API key isn't set run `/fenix-setup` first.
15
+ If the `install_fenix_skills` tool is **not available** (you can't find it, or the `fenix` server shows "still connecting" in `/mcp`), the plugin's MCP server hasn't started in this session. Run **`/reload-plugins`** (or fully restart Claude Code), then retry — do not give up after one lookup.
16
+
17
+ If `install_fenix_skills` returns **401/unauthorized**, the API key isn't set or is wrong — run `/fenix-setup`, then `/reload-plugins`.
@@ -7,8 +7,13 @@ Help the user connect the Fenix plugin. **Only one value is needed: a single API
7
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
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
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.
10
+ - **macOS / Linux:** add `export FENIX_API_KEY="<their key>"` to `~/.zshrc` or `~/.bashrc`.
11
+ - **Windows (PowerShell):** run `setx FENIX_API_KEY "<their key>"`.
12
+ 4. **Reload so the connector picks up the key — this step is required.** The Fenix MCP server only connects at session startup, so after setting the key the user must **run `/reload-plugins`** (or fully quit and reopen Claude Code, and reopen the terminal so the new env var is in scope). Without this, the `fenix` server stays unconnected even with a valid key.
13
+ 5. Verify with `/mcp` — the `fenix` server should show **connected** with tools. Then call a read-only tool such as `get_project` (or run `/fenix-install`).
14
+
15
+ ## Troubleshooting
16
+ - **`fenix` server shows "still connecting" / no Fenix tools (install_fenix_skills, get_project) appear:** the server hasn't started in this session. Run **`/reload-plugins`**, or fully restart Claude Code. ("Still connecting" is NOT an auth error — a bad key shows as "Failed to connect.")
17
+ - **"Failed to connect" / 401:** the key is missing or wrong. Confirm `FENIX_API_KEY` is set **in the environment Claude Code was launched from** (set it, reopen the terminal, then `/reload-plugins`).
13
18
 
14
19
  Never print, log, or store the key anywhere other than the environment variable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fenix-claude-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Fenix patent automation plugin for Claude Code — MCP connector plus patent drafting, office-action, and project skills.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://claude.fenix.ai",
@@ -6,7 +6,7 @@ description: >-
6
6
 
7
7
  # Fenix Application
8
8
 
9
- **Skill version: 13** (authoritative version is in the top-level `VERSION` file; this line is for reference only).
9
+ **Skill version: 21** (authoritative version is in the top-level `VERSION` file; this line is for reference only).
10
10
 
11
11
  ## Triggers
12
12
 
@@ -62,6 +62,10 @@ Custom instructions may freely change tone, phrasing, default counts, section pr
62
62
  - `scripts/parse_claims.py` -- local claims parser
63
63
  - `scripts/calculate_figure_layout.py` -- local layout calculator
64
64
 
65
+ ## Staying current
66
+
67
+ This is **fenix-application** version 21. Every `get_project` response includes a `_fenix.skillVersions` map of the server's authoritative skill versions. When you load a project, compare `_fenix.skillVersions["fenix-application"]` to 21: if the server value is higher, a newer version of this skill is available -- tell the user and offer to run `/fenix-install` (or update the Fenix plugin) before continuing. Do not silently keep working on a stale skill.
68
+
65
69
  ## How to use
66
70
 
67
71
  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.
@@ -1 +1 @@
1
- 13
1
+ 21
@@ -1,6 +1,6 @@
1
1
  # Method Claim 1 Drafting Guide
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
4
 
5
5
  House style for drafting an **independent method claim 1**, especially for AI/ML
6
6
  inventions. Use this whenever you draft a method independent claim. (Apparatus/CRM/
@@ -1,6 +1,6 @@
1
1
  # Claims Workflow
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
4
 
5
5
  ## Before drafting
6
6
  Read `references/patent-safe.md` for language guidelines.
@@ -21,7 +21,7 @@ Claims flow through the system **parsed**, as an array of claim objects -- not r
21
21
  Parse claims into this schema yourself with `scripts/parse_claims.py` (pipe the text in, or `--file claims.txt --pretty`). Keep the claims in this parsed form from drafting through save -- do not flatten back to raw text. Holding the schema also lets you make structural edits directly (renumber, relink a dependent, etc.).
22
22
 
23
23
  ## Editor (accordion)
24
- **Always ask the user whether they want to open the claims editor to review/edit the full set** before (or instead of) the one-at-a-time pass -- e.g. "Want me to open the claims editor so you can review them all at once?" Only open it if they say yes. To open it, open `assets/claims_editor.html`. Pre-fill `INITIAL_CONTENT` with the **parsed schema array** -- e.g. `INITIAL_CONTENT = [{"claimNumber":"1","claimStatus":"original","claimType":"independent","claimText":["A system comprising:","a processor; and","a memory."],"parent":"","independentParent":""}, ...]`. The editor shows every claim as a collapsible accordion item (open any claim to edit its number/text, add or remove claims) and **keeps the claims parsed**: on Copy it emits `[EDITOR SUBMIT - claims]` followed by a JSON array of schema objects. Strip the prefix and pass that array straight to save.
24
+ **Always ask the user whether they want to open the claims editor to review/edit the full set** before (or instead of) the one-at-a-time pass -- e.g. "Want me to open the claims editor so you can review them all at once?" Only open it if they say yes. **In Cowork, prefer the live claims editor:** `get_live_artifacts({ name: "claims-editor" })`, substitute `__PROJECT_ID__` (project id) and `__SERVER_PREFIX__` (your MCP tool prefix, e.g. `mcp__<id>__` — see `references/editors.md`) in the returned HTML, and open it — it loads the current claims and **saves them itself**; when the user pastes a `[CLAIMS SAVED - claimsRev N]` block, apply that delta to your working set and do **not** call `save_project` again (see `references/editors.md`). **Otherwise (non-Cowork), open `assets/claims_editor.html`** and pre-fill `INITIAL_CONTENT` with the **parsed schema array** -- e.g. `INITIAL_CONTENT = [{"claimNumber":"1","claimStatus":"original","claimType":"independent","claimText":["A system comprising:","a processor; and","a memory."],"parent":"","independentParent":""}, ...]`. The editor shows every claim as a collapsible accordion item (open any claim to edit its number/text, add or remove claims) and **keeps the claims parsed**: on Copy it emits `[EDITOR SUBMIT - claims]` followed by a JSON array of schema objects. Strip the prefix and pass that array straight to save.
25
25
 
26
26
  ## Save (parsed array -- preferred)
27
27
  Call `save_project({ type: "claims", claims: [...] })` with the parsed schema array (from your parse or from the editor). The server persists the array and derives the claim tree the spec generator needs, then returns `parsedClaims` (the normalized array, with `claimType`/`parent`/`independentParent` re-derived server-side) and `treeBuilt: true`. You do not need to re-verify a structured save.
@@ -1,10 +1,28 @@
1
1
  # Editors — how to drive them
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
4
 
5
- This skill ships three accordion HTML editors in `assets/`. **You never need to open or read the editor HTML** — everything you need to pre-fill and read them back is below.
5
+ There are two ways to run the claims / figures / sections editors. **Prefer the live editors in Cowork**; use the clipboard editors elsewhere. **You never need to open or read any editor HTML** — everything you need is below.
6
6
 
7
- ## Open an editor with pre-filled data
7
+ ## Live editors (preferred in Cowork)
8
+
9
+ In a Cowork host, use the **self-saving live editor artifacts** instead of the clipboard editors. They load and save themselves, so the full claim/figure/section set never has to pass back through chat — far faster and far cheaper in tokens.
10
+
11
+ To open one:
12
+ 1. Call `get_live_artifacts({ name: "claims-editor" })` (or `"figures-editor"`, `"sections-editor"`).
13
+ 2. Take the returned `html` and substitute its two placeholders before writing it: `__PROJECT_ID__` → the target Fenix project id, and `__SERVER_PREFIX__` → your MCP connector's tool prefix (the part before the tool name in a fully-qualified tool id — e.g. if your tools appear as `mcp__abc123__get_project`, the prefix is `mcp__abc123__`; bare tool names do not resolve when the host namespaces MCP tools). Then write it to the returned `fileName` and open it as a live HTML artifact for the user.
14
+ 3. That's it — the artifact loads the project's current claims/figures/sections itself (via `get_project`) and, on **Save**, writes them back itself (via `save_project`). **Do NOT pre-fill it, and do NOT call `save_project` for it yourself.**
15
+
16
+ **When the user pastes a `[CLAIMS SAVED - claimsRev N]` block** (or `[FIGURES SAVED - figuresRev N]` / `[SECTIONS SAVED - sectionsRev N]`), the editor has **already saved to Fenix**:
17
+ - **Do NOT call `save_project` again** — it is already persisted.
18
+ - Update your working copy from the block: it gives the **full text of every changed/added** item, the **numbers of removed** items, and the **numbers left unchanged**. Apply that delta to the set you already had.
19
+ - Note the rev (`claimsRev`/`figuresRev`/`sectionsRev`). `get_project` returns the current rev for each; if yours is behind, reload to re-sync.
20
+
21
+ If the host is **not** Cowork (no live artifacts, or the user can't run them), fall back to the clipboard editors below.
22
+
23
+ ## Clipboard editors (fallback — non-Cowork hosts)
24
+
25
+ This skill also ships three accordion HTML editors in `assets/`. Open one with pre-filled data:
8
26
  1. Take the editor file (`assets/<editor>.html`) as-is — do not parse it.
9
27
  2. Replace the line `var INITIAL_CONTENT="";` with `var INITIAL_CONTENT=<JSON>;`, where `<JSON>` is the data for that editor in the shape below (a JSON string the editor will `JSON.parse`, or an inlined array/object — both work).
10
28
  3. Save the working copy and open it for the user.
@@ -1,6 +1,6 @@
1
1
  # Figure Description Protocol
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
4
 
5
5
  You are a patent drafting assistant. Your task is to write a detailed textual description of a figure (e.g., FIG. 1) for inclusion in the Detailed Description section of a patent application.
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Figures Guidance
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
4
 
5
5
  ## Figure List
6
6
 
@@ -1,8 +1,22 @@
1
1
  # Figures Workflow
2
2
 
3
- **Version: 13**
3
+ **Version: 21**
4
+
5
+ ## ⚠️ Renumbering invariant (applies at EVERY point in the process)
6
+ Figures are numbered **sequentially with no gaps** (FIG. 1, 2, 3, ...), and **every element number is derived from its figure's number** -- flowchart steps `N00, N05, N10, ...` and device components `N05, N10, ...`, where `N` is the figure number (see `## Numbering` and `references/figures-guidance.md`).
7
+
8
+ **So ANY time the figure set changes -- a figure is ADDED, REMOVED, reordered, or template figures are IMPORTED (at intake, in Phase 1, in Phase 2, or via the editor) -- you MUST renumber BEFORE saving:**
9
+ 1. Renumber the **figures** so they remain sequential and gap-free.
10
+ 2. Renumber **every affected figure's steps/components** to match its new figure number -- e.g. if FIG. 2 becomes FIG. 3, its `205, 210, ...` components become `305, 310, ...`.
11
+
12
+ Never leave a figure or an element number out of sync with its figure's position. **Importing template figures is the most common trap** -- the template's figure and element numbers will not line up with your set, so renumber them in. This is not optional and not only an end-of-phase step: renumber the moment the set changes, every time.
4
13
 
5
14
  ## Phase 1 -- List
15
+ 0. **Ask up front how the user wants to start the figure list** -- e.g. "Want to start the figures from a template, from a figure-strategy document you have, or draft them from scratch?" Three options:
16
+ - **From a template:** `list_patent_templates` -> let the user pick -> import with `save_project({ templateId })` so the template's figures become the starting set, **then renumber the imported figures and all their steps/components into your set** (see the Renumbering invariant -- template numbers will not match). (Skip this option if a template/source was already imported during intake.)
17
+ - **From a figure strategy document:** if the user has a document describing the intended figures (which figures, what each illustrates, key steps/components), ask them to provide or upload it, then read it and use it to build the figure list (interpret it against `references/figures-guidance.md`).
18
+ - **From scratch:** plan the figure list from the disclosure.
19
+ Then continue from step 1.
6
20
  1. `get_project({ projectId, include: ["figures"] })` -- note ALL figures including Placeholders
7
21
  2. Use `references/figures-guidance.md` for figure list planning
8
22
  3. Number new figures from max(existing)+1
@@ -23,7 +37,7 @@ For each: read `references/figures-guidance.md` -> generate -> show -- **END T
23
37
  `{ type, label, number, text, name, action, angle, flipX, flipY }`
24
38
 
25
39
  ## Editor (accordion)
26
- **Always ask the user whether they want to open the figures editor to review/edit all figures at once** -- e.g. "Want me to open the figures editor so you can review them all together?" Only open it if they say yes. To open it, open `assets/figures_editor.html` with `INITIAL_CONTENT` set to the full figures JSON array. Each figure is a collapsible accordion item; opening one reveals its number, type, brief description, and a steps/components table with add/remove rows. There is no introductory-sentence field -- the figure's introductory/opening sentence is generated from its brief description during spec drafting. The user can also add/remove whole figures. On `[EDITOR SUBMIT - figures]`, strip the prefix and call `save_project({ type: "figures", figures: [...] })` with the returned array.
40
+ **Always ask the user whether they want to open the figures editor to review/edit all figures at once** -- e.g. "Want me to open the figures editor so you can review them all together?" Only open it if they say yes. **In Cowork, prefer the live figures editor:** `get_live_artifacts({ name: "figures-editor" })`, substitute `__PROJECT_ID__` (project id) and `__SERVER_PREFIX__` (your MCP tool prefix, e.g. `mcp__<id>__` — see `references/editors.md`) in the returned HTML, and open it — it loads the current figures and **saves them itself**; when the user pastes a `[FIGURES SAVED - figuresRev N]` block, apply that delta and do **not** call `save_project` again (see `references/editors.md`). **Otherwise (non-Cowork), open `assets/figures_editor.html`** with `INITIAL_CONTENT` set to the full figures JSON array. Each figure is a collapsible accordion item; opening one reveals its number, type, brief description, and a steps/components table with add/remove rows. There is no introductory-sentence field -- the figure's introductory/opening sentence is generated from its brief description during spec drafting. The user can also add/remove whole figures -- **after any add/remove, renumber the figures and their steps/components** (see the Renumbering invariant) before saving. On `[EDITOR SUBMIT - figures]`, strip the prefix and call `save_project({ type: "figures", figures: [...] })` with the returned array.
27
41
 
28
42
  ## After all figures
29
43
  Run `scripts/calculate_figure_layout.py` with the figures JSON -> check `warnings` array and `readyToPrint` flag. **Do NOT print automatically.** Only call `print_patent(type=drawings)` if the user explicitly asks for the drawings file. After figures are saved and the layout is clean, simply tell the user the figures are ready and ask whether they want the drawings file generated.