json-object-editor 0.10.668 → 0.10.670
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.
- package/TESTING_PHASE1_PROGRESS.md +216 -0
- package/_www/ai-jobs.html +218 -0
- package/_www/mcp-nav.js +21 -12
- package/_www/mcp-test.html +287 -287
- package/capp/capp.css +3 -2
- package/css/joe-ai.css +71 -0
- package/css/joe.css +1 -1
- package/docs/protocol_report_template.html +479 -0
- package/docs/protocol_report_template.js +563 -0
- package/js/JsonObjectEditor.jquery.craydent.js +22 -3
- package/js/joe-ai.js +3220 -2301
- package/js/joe.js +23 -4
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/pages/template.html +2 -0
- package/server/fields/core.js +16 -2
- package/server/init.js +9 -1
- package/server/modules/AiJobs.js +412 -0
- package/server/modules/MCP.js +1481 -1368
- package/server/modules/Server.js +3 -0
- package/server/plugins/chatgpt.js +2332 -2113
- package/server/schemas/ai_prompt.js +9 -2
- package/server/schemas/report.js +1 -1
- package/server/schemas/task.js +1 -0
- package/web-components/field-jobs-container.js +224 -0
package/_www/mcp-test.html
CHANGED
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>JOE MCP Test</title>
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<style>
|
|
8
|
-
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
|
|
9
|
-
label{display:block;margin:8px 0 4px}
|
|
10
|
-
select, input, textarea, button{font-size:14px}
|
|
11
|
-
textarea{width:100%;height:160px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
12
|
-
pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto}
|
|
13
|
-
.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
|
|
14
|
-
.small{font-size:12px;color:#666}
|
|
15
|
-
.bad{color:#b00020}
|
|
16
|
-
.good{color:#0a7d00}
|
|
17
|
-
.preset{margin:8px 0;}
|
|
18
|
-
</style>
|
|
19
|
-
</head>
|
|
20
|
-
<body>
|
|
21
|
-
<div id="mcp-nav"></div>
|
|
22
|
-
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
|
|
23
|
-
<h1>JOE MCP Test</h1>
|
|
24
|
-
<div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
|
|
25
|
-
|
|
26
|
-
<h3>Toolsets (from manifest)</h3>
|
|
27
|
-
<pre id="toolsets"></pre>
|
|
28
|
-
|
|
29
|
-
<h3>Manifest</h3>
|
|
30
|
-
<div class="row">
|
|
31
|
-
<label for="base">Base URL</label>
|
|
32
|
-
<input id="base" value="" placeholder="http://localhost:{{PORT}}" style="min-width:280px"/>
|
|
33
|
-
<button id="loadManifest">Load manifest</button>
|
|
34
|
-
<span id="status" class="small"></span>
|
|
35
|
-
</div>
|
|
36
|
-
<label for="tool">Tools</label>
|
|
37
|
-
<select id="tool"></select>
|
|
38
|
-
<pre id="toolInfo"></pre>
|
|
39
|
-
|
|
40
|
-
<h3>Presets</h3>
|
|
41
|
-
<div class="preset">
|
|
42
|
-
<button id="presetFuzzyUser">fuzzySearch users: q="corey hadden"</button>
|
|
43
|
-
<button id="presetFuzzyHouse">fuzzySearch houses: q="backyard"</button>
|
|
44
|
-
<button id="presetListApps">listApps: all app definitions</button>
|
|
45
|
-
<button id="presetGetSchemas">getSchemas: ["client","user"]</button>
|
|
46
|
-
<button id="presetGetSchemasSummary">getSchemas (summaryOnly): ["task","project"]</button>
|
|
47
|
-
<button id="presetSearchSlimRecent">search clients (slim, recent)</button>
|
|
48
|
-
<button id="presetSearchWithCount">search clients (withCount)</button>
|
|
49
|
-
<button id="presetSearchCountOnly">search clients (countOnly)</button>
|
|
50
|
-
<button id="presetSaveObjects">saveObjects: batch save (example)</button>
|
|
51
|
-
<button id="presetUnderstandObject">understandObject: by _id</button>
|
|
52
|
-
<button id="presetFindByTag">findObjectsByTag: by tags (example)</button>
|
|
53
|
-
<button id="presetFindByStatus">findObjectsByStatus: by status (example)</button>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<h3>Call JSON-RPC</h3>
|
|
57
|
-
<label for="params">Params (JSON)</label>
|
|
58
|
-
<textarea id="params">{}</textarea>
|
|
59
|
-
<div class="row">
|
|
60
|
-
<button id="call">POST /mcp</button>
|
|
61
|
-
<span id="callStatus" class="small"></span>
|
|
62
|
-
</div>
|
|
63
|
-
<pre id="result"></pre>
|
|
64
|
-
|
|
65
|
-
<script>
|
|
66
|
-
(function(){
|
|
67
|
-
const $ = (id)=>document.getElementById(id);
|
|
68
|
-
const base = $('base');
|
|
69
|
-
const loadBtn = $('loadManifest');
|
|
70
|
-
const toolsetsEl = document.getElementById('toolsets');
|
|
71
|
-
const status = $('status');
|
|
72
|
-
const toolSel = $('tool');
|
|
73
|
-
const toolInfo = $('toolInfo');
|
|
74
|
-
const params = $('params');
|
|
75
|
-
const callBtn = $('call');
|
|
76
|
-
const callStatus = $('callStatus');
|
|
77
|
-
const result = $('result');
|
|
78
|
-
const presetFuzzyUser = $('presetFuzzyUser');
|
|
79
|
-
const presetFuzzyHouse = $('presetFuzzyHouse');
|
|
80
|
-
const presetListApps = $('presetListApps');
|
|
81
|
-
const presetGetSchemas = $('presetGetSchemas');
|
|
82
|
-
const presetGetSchemasSummary = $('presetGetSchemasSummary');
|
|
83
|
-
const presetSearchSlimRecent = $('presetSearchSlimRecent');
|
|
84
|
-
const presetSearchWithCount = $('presetSearchWithCount');
|
|
85
|
-
const presetSearchCountOnly = $('presetSearchCountOnly');
|
|
86
|
-
const presetSaveObjects = $('presetSaveObjects');
|
|
87
|
-
const presetUnderstandObject = $('presetUnderstandObject');
|
|
88
|
-
const presetFindByTag = $('presetFindByTag');
|
|
89
|
-
const presetFindByStatus = $('presetFindByStatus');
|
|
90
|
-
|
|
91
|
-
// Try to infer base from window location
|
|
92
|
-
base.value = base.value || (location.origin);
|
|
93
|
-
|
|
94
|
-
let manifest = null;
|
|
95
|
-
let idCounter = 1;
|
|
96
|
-
|
|
97
|
-
function ensureTool(name, meta){
|
|
98
|
-
if (!manifest) { manifest = { tools: [] }; }
|
|
99
|
-
manifest.tools = manifest.tools || [];
|
|
100
|
-
var existing = (manifest.tools||[]).find(function(t){ return t && t.name === name; });
|
|
101
|
-
if (!existing && meta) { manifest.tools.push(meta); }
|
|
102
|
-
// ensure option exists in select
|
|
103
|
-
var hasOpt = false;
|
|
104
|
-
for (var i=0;i<toolSel.options.length;i++){ if (toolSel.options[i].value === name) { hasOpt = true; break; } }
|
|
105
|
-
if (!hasOpt){ var opt=document.createElement('option'); opt.value=name; opt.textContent=name; toolSel.appendChild(opt); }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function setStatus(el, msg, ok){
|
|
109
|
-
el.textContent = msg || '';
|
|
110
|
-
el.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function fetchJSON(url, opts){
|
|
114
|
-
const res = await fetch(url, opts);
|
|
115
|
-
const ct = res.headers.get('content-type')||'';
|
|
116
|
-
const isJSON = ct.includes('application/json');
|
|
117
|
-
if(!res.ok){
|
|
118
|
-
let detail = isJSON ? await res.json().catch(()=>({})) : await res.text();
|
|
119
|
-
throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
|
|
120
|
-
}
|
|
121
|
-
return isJSON ? res.json() : res.text();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
loadBtn.onclick = async function(){
|
|
125
|
-
setStatus(status, 'Loading...', null);
|
|
126
|
-
toolSel.innerHTML = '';
|
|
127
|
-
toolInfo.textContent='';
|
|
128
|
-
try{
|
|
129
|
-
const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
|
|
130
|
-
manifest = await fetchJSON(url);
|
|
131
|
-
// Instance info handled by shared nav script
|
|
132
|
-
(manifest.tools||[]).forEach(t=>{
|
|
133
|
-
const opt=document.createElement('option');
|
|
134
|
-
opt.value=t.name; opt.textContent=t.name;
|
|
135
|
-
toolSel.appendChild(opt);
|
|
136
|
-
});
|
|
137
|
-
if((manifest.tools||[]).length){
|
|
138
|
-
toolSel.selectedIndex=0; renderToolInfo();
|
|
139
|
-
}
|
|
140
|
-
if (toolsetsEl) {
|
|
141
|
-
try{
|
|
142
|
-
toolsetsEl.textContent = JSON.stringify(manifest.toolsets || {}, null, 2);
|
|
143
|
-
}catch(_e){
|
|
144
|
-
toolsetsEl.textContent = '';
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
setStatus(status, 'Manifest loaded', true);
|
|
148
|
-
}catch(e){
|
|
149
|
-
setStatus(status, e.message||String(e), false);
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
function renderToolInfo(){
|
|
154
|
-
const name = toolSel.value;
|
|
155
|
-
const tool = (manifest.tools||[]).find(t=>t.name===name);
|
|
156
|
-
toolInfo.textContent = tool ? JSON.stringify(tool, null, 2) : '';
|
|
157
|
-
// Prefill common params for convenience
|
|
158
|
-
if(tool && tool.params){
|
|
159
|
-
params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
|
|
160
|
-
}
|
|
161
|
-
// Hydrate presets UI
|
|
162
|
-
if (name === 'hydrate') {
|
|
163
|
-
params.value = JSON.stringify({}, null, 2);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
toolSel.onchange = renderToolInfo;
|
|
167
|
-
|
|
168
|
-
presetFuzzyUser.onclick = function(){
|
|
169
|
-
toolSel.value = 'fuzzySearch';
|
|
170
|
-
renderToolInfo();
|
|
171
|
-
params.value = JSON.stringify({ q: 'corey hadden', filters: { itemtype: 'user' }, threshold: 0.5 }, null, 2);
|
|
172
|
-
};
|
|
173
|
-
presetFuzzyHouse.onclick = function(){
|
|
174
|
-
toolSel.value = 'fuzzySearch';
|
|
175
|
-
renderToolInfo();
|
|
176
|
-
params.value = JSON.stringify({ q: 'backyard', filters: { itemtype: 'house' }, threshold: 0.5 }, null, 2);
|
|
177
|
-
};
|
|
178
|
-
presetListApps.onclick = function(){
|
|
179
|
-
toolSel.value = 'listApps';
|
|
180
|
-
renderToolInfo();
|
|
181
|
-
params.value = JSON.stringify({}, null, 2);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
presetGetSchemas.onclick = function(){
|
|
185
|
-
ensureTool('getSchemas', {
|
|
186
|
-
name: 'getSchemas',
|
|
187
|
-
description: 'Retrieve multiple schema definitions. If omitted, returns all.',
|
|
188
|
-
params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } } } },
|
|
189
|
-
returns: { type: 'object' }
|
|
190
|
-
});
|
|
191
|
-
toolSel.value = 'getSchemas';
|
|
192
|
-
renderToolInfo();
|
|
193
|
-
params.value = JSON.stringify({ names: ["client", "user"] }, null, 2);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
presetGetSchemasSummary.onclick = function(){
|
|
197
|
-
ensureTool('getSchemas', {
|
|
198
|
-
name: 'getSchemas',
|
|
199
|
-
description: 'Retrieve multiple schemas. With summaryOnly=true, returns summaries; if names omitted, returns all.',
|
|
200
|
-
params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } }, summaryOnly: { type: 'boolean' } } },
|
|
201
|
-
returns: { type: 'object' }
|
|
202
|
-
});
|
|
203
|
-
toolSel.value = 'getSchemas';
|
|
204
|
-
renderToolInfo();
|
|
205
|
-
params.value = JSON.stringify({ names: ["task", "project"], summaryOnly: true }, null, 2);
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
presetSearchSlimRecent.onclick = function(){
|
|
209
|
-
toolSel.value = 'search';
|
|
210
|
-
renderToolInfo();
|
|
211
|
-
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, sortBy: 'joeUpdated', sortDir: 'desc', slim: true }, null, 2);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
presetSearchWithCount.onclick = function(){
|
|
215
|
-
toolSel.value = 'search';
|
|
216
|
-
renderToolInfo();
|
|
217
|
-
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, withCount: true, sortBy: 'joeUpdated', sortDir: 'desc' }, null, 2);
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
presetSearchCountOnly.onclick = function(){
|
|
221
|
-
toolSel.value = 'search';
|
|
222
|
-
renderToolInfo();
|
|
223
|
-
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, countOnly: true }, null, 2);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
presetSaveObjects.onclick = function(){
|
|
227
|
-
toolSel.value = 'saveObjects';
|
|
228
|
-
renderToolInfo();
|
|
229
|
-
// Example: two minimal objects; adjust itemtype/fields as needed
|
|
230
|
-
params.value = JSON.stringify({
|
|
231
|
-
objects: [
|
|
232
|
-
{ itemtype: "client", name: "Batch Client A" },
|
|
233
|
-
{ itemtype: "client", name: "Batch Client B" }
|
|
234
|
-
],
|
|
235
|
-
stopOnError: false,
|
|
236
|
-
concurrency: 5
|
|
237
|
-
}, null, 2);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
presetUnderstandObject.onclick = function(){
|
|
241
|
-
toolSel.value = 'understandObject';
|
|
242
|
-
renderToolInfo();
|
|
243
|
-
// Provide a template; user should replace _id with a real object id.
|
|
244
|
-
params.value = JSON.stringify({ _id: "REPLACE_WITH_OBJECT_ID", depth: 2 }, null, 2);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
presetFindByTag.onclick = function(){
|
|
248
|
-
toolSel.value = 'findObjectsByTag';
|
|
249
|
-
renderToolInfo();
|
|
250
|
-
// Example: find objects with all of these tags
|
|
251
|
-
params.value = JSON.stringify({ tags: ["REPLACE_WITH_TAG_ID_1", "REPLACE_WITH_TAG_ID_2"], itemtype: "task" }, null, 2);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
presetFindByStatus.onclick = function(){
|
|
255
|
-
toolSel.value = 'findObjectsByStatus';
|
|
256
|
-
renderToolInfo();
|
|
257
|
-
// Example: find objects with this status
|
|
258
|
-
params.value = JSON.stringify({ status: "REPLACE_WITH_STATUS_ID", itemtype: "task" }, null, 2);
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
callBtn.onclick = async function(){
|
|
262
|
-
setStatus(callStatus, 'Calling...', null);
|
|
263
|
-
result.textContent='';
|
|
264
|
-
try{
|
|
265
|
-
const url = base.value.replace(/\/$/,'') + '/mcp';
|
|
266
|
-
let p = {};
|
|
267
|
-
try{ p = params.value ? JSON.parse(params.value) : {}; }catch(e){ throw new Error('Invalid JSON in params'); }
|
|
268
|
-
const body = {
|
|
269
|
-
jsonrpc: '2.0',
|
|
270
|
-
id: String(idCounter++),
|
|
271
|
-
method: toolSel.value,
|
|
272
|
-
params: p
|
|
273
|
-
};
|
|
274
|
-
const resp = await fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
|
|
275
|
-
result.textContent = JSON.stringify(resp, null, 2);
|
|
276
|
-
setStatus(callStatus, 'OK', true);
|
|
277
|
-
}catch(e){
|
|
278
|
-
setStatus(callStatus, e.message||String(e), false);
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
// Auto-load manifest on open
|
|
283
|
-
setTimeout(()=>loadBtn.click(), 50);
|
|
284
|
-
})();
|
|
285
|
-
</script>
|
|
286
|
-
</body>
|
|
287
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>JOE MCP Test</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<style>
|
|
8
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
|
|
9
|
+
label{display:block;margin:8px 0 4px}
|
|
10
|
+
select, input, textarea, button{font-size:14px}
|
|
11
|
+
textarea{width:100%;height:160px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
12
|
+
pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto}
|
|
13
|
+
.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
|
|
14
|
+
.small{font-size:12px;color:#666}
|
|
15
|
+
.bad{color:#b00020}
|
|
16
|
+
.good{color:#0a7d00}
|
|
17
|
+
.preset{margin:8px 0;}
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div id="mcp-nav"></div>
|
|
22
|
+
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
|
|
23
|
+
<h1>JOE MCP Test</h1>
|
|
24
|
+
<div class="small">Use this page to discover tools and call the JSON-RPC endpoint.</div>
|
|
25
|
+
|
|
26
|
+
<h3>Toolsets (from manifest)</h3>
|
|
27
|
+
<pre id="toolsets"></pre>
|
|
28
|
+
|
|
29
|
+
<h3>Manifest</h3>
|
|
30
|
+
<div class="row">
|
|
31
|
+
<label for="base">Base URL</label>
|
|
32
|
+
<input id="base" value="" placeholder="http://localhost:{{PORT}}" style="min-width:280px"/>
|
|
33
|
+
<button id="loadManifest">Load manifest</button>
|
|
34
|
+
<span id="status" class="small"></span>
|
|
35
|
+
</div>
|
|
36
|
+
<label for="tool">Tools</label>
|
|
37
|
+
<select id="tool"></select>
|
|
38
|
+
<pre id="toolInfo"></pre>
|
|
39
|
+
|
|
40
|
+
<h3>Presets</h3>
|
|
41
|
+
<div class="preset">
|
|
42
|
+
<button id="presetFuzzyUser">fuzzySearch users: q="corey hadden"</button>
|
|
43
|
+
<button id="presetFuzzyHouse">fuzzySearch houses: q="backyard"</button>
|
|
44
|
+
<button id="presetListApps">listApps: all app definitions</button>
|
|
45
|
+
<button id="presetGetSchemas">getSchemas: ["client","user"]</button>
|
|
46
|
+
<button id="presetGetSchemasSummary">getSchemas (summaryOnly): ["task","project"]</button>
|
|
47
|
+
<button id="presetSearchSlimRecent">search clients (slim, recent)</button>
|
|
48
|
+
<button id="presetSearchWithCount">search clients (withCount)</button>
|
|
49
|
+
<button id="presetSearchCountOnly">search clients (countOnly)</button>
|
|
50
|
+
<button id="presetSaveObjects">saveObjects: batch save (example)</button>
|
|
51
|
+
<button id="presetUnderstandObject">understandObject: by _id</button>
|
|
52
|
+
<button id="presetFindByTag">findObjectsByTag: by tags (example)</button>
|
|
53
|
+
<button id="presetFindByStatus">findObjectsByStatus: by status (example)</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<h3>Call JSON-RPC</h3>
|
|
57
|
+
<label for="params">Params (JSON)</label>
|
|
58
|
+
<textarea id="params">{}</textarea>
|
|
59
|
+
<div class="row">
|
|
60
|
+
<button id="call">POST /mcp</button>
|
|
61
|
+
<span id="callStatus" class="small"></span>
|
|
62
|
+
</div>
|
|
63
|
+
<pre id="result"></pre>
|
|
64
|
+
|
|
65
|
+
<script>
|
|
66
|
+
(function(){
|
|
67
|
+
const $ = (id)=>document.getElementById(id);
|
|
68
|
+
const base = $('base');
|
|
69
|
+
const loadBtn = $('loadManifest');
|
|
70
|
+
const toolsetsEl = document.getElementById('toolsets');
|
|
71
|
+
const status = $('status');
|
|
72
|
+
const toolSel = $('tool');
|
|
73
|
+
const toolInfo = $('toolInfo');
|
|
74
|
+
const params = $('params');
|
|
75
|
+
const callBtn = $('call');
|
|
76
|
+
const callStatus = $('callStatus');
|
|
77
|
+
const result = $('result');
|
|
78
|
+
const presetFuzzyUser = $('presetFuzzyUser');
|
|
79
|
+
const presetFuzzyHouse = $('presetFuzzyHouse');
|
|
80
|
+
const presetListApps = $('presetListApps');
|
|
81
|
+
const presetGetSchemas = $('presetGetSchemas');
|
|
82
|
+
const presetGetSchemasSummary = $('presetGetSchemasSummary');
|
|
83
|
+
const presetSearchSlimRecent = $('presetSearchSlimRecent');
|
|
84
|
+
const presetSearchWithCount = $('presetSearchWithCount');
|
|
85
|
+
const presetSearchCountOnly = $('presetSearchCountOnly');
|
|
86
|
+
const presetSaveObjects = $('presetSaveObjects');
|
|
87
|
+
const presetUnderstandObject = $('presetUnderstandObject');
|
|
88
|
+
const presetFindByTag = $('presetFindByTag');
|
|
89
|
+
const presetFindByStatus = $('presetFindByStatus');
|
|
90
|
+
|
|
91
|
+
// Try to infer base from window location
|
|
92
|
+
base.value = base.value || (location.origin);
|
|
93
|
+
|
|
94
|
+
let manifest = null;
|
|
95
|
+
let idCounter = 1;
|
|
96
|
+
|
|
97
|
+
function ensureTool(name, meta){
|
|
98
|
+
if (!manifest) { manifest = { tools: [] }; }
|
|
99
|
+
manifest.tools = manifest.tools || [];
|
|
100
|
+
var existing = (manifest.tools||[]).find(function(t){ return t && t.name === name; });
|
|
101
|
+
if (!existing && meta) { manifest.tools.push(meta); }
|
|
102
|
+
// ensure option exists in select
|
|
103
|
+
var hasOpt = false;
|
|
104
|
+
for (var i=0;i<toolSel.options.length;i++){ if (toolSel.options[i].value === name) { hasOpt = true; break; } }
|
|
105
|
+
if (!hasOpt){ var opt=document.createElement('option'); opt.value=name; opt.textContent=name; toolSel.appendChild(opt); }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setStatus(el, msg, ok){
|
|
109
|
+
el.textContent = msg || '';
|
|
110
|
+
el.className = 'small ' + (ok===true?'good': ok===false?'bad':'');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function fetchJSON(url, opts){
|
|
114
|
+
const res = await fetch(url, opts);
|
|
115
|
+
const ct = res.headers.get('content-type')||'';
|
|
116
|
+
const isJSON = ct.includes('application/json');
|
|
117
|
+
if(!res.ok){
|
|
118
|
+
let detail = isJSON ? await res.json().catch(()=>({})) : await res.text();
|
|
119
|
+
throw new Error('HTTP '+res.status+': '+(isJSON?JSON.stringify(detail):detail));
|
|
120
|
+
}
|
|
121
|
+
return isJSON ? res.json() : res.text();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
loadBtn.onclick = async function(){
|
|
125
|
+
setStatus(status, 'Loading...', null);
|
|
126
|
+
toolSel.innerHTML = '';
|
|
127
|
+
toolInfo.textContent='';
|
|
128
|
+
try{
|
|
129
|
+
const url = base.value.replace(/\/$/,'') + '/.well-known/mcp/manifest.json';
|
|
130
|
+
manifest = await fetchJSON(url);
|
|
131
|
+
// Instance info handled by shared nav script
|
|
132
|
+
(manifest.tools||[]).forEach(t=>{
|
|
133
|
+
const opt=document.createElement('option');
|
|
134
|
+
opt.value=t.name; opt.textContent=t.name;
|
|
135
|
+
toolSel.appendChild(opt);
|
|
136
|
+
});
|
|
137
|
+
if((manifest.tools||[]).length){
|
|
138
|
+
toolSel.selectedIndex=0; renderToolInfo();
|
|
139
|
+
}
|
|
140
|
+
if (toolsetsEl) {
|
|
141
|
+
try{
|
|
142
|
+
toolsetsEl.textContent = JSON.stringify(manifest.toolsets || {}, null, 2);
|
|
143
|
+
}catch(_e){
|
|
144
|
+
toolsetsEl.textContent = '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
setStatus(status, 'Manifest loaded', true);
|
|
148
|
+
}catch(e){
|
|
149
|
+
setStatus(status, e.message||String(e), false);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
function renderToolInfo(){
|
|
154
|
+
const name = toolSel.value;
|
|
155
|
+
const tool = (manifest.tools||[]).find(t=>t.name===name);
|
|
156
|
+
toolInfo.textContent = tool ? JSON.stringify(tool, null, 2) : '';
|
|
157
|
+
// Prefill common params for convenience
|
|
158
|
+
if(tool && tool.params){
|
|
159
|
+
params.value = JSON.stringify(Object.fromEntries(Object.keys(tool.params.properties||{}).map(k=>[k, null])), null, 2);
|
|
160
|
+
}
|
|
161
|
+
// Hydrate presets UI
|
|
162
|
+
if (name === 'hydrate') {
|
|
163
|
+
params.value = JSON.stringify({}, null, 2);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
toolSel.onchange = renderToolInfo;
|
|
167
|
+
|
|
168
|
+
presetFuzzyUser.onclick = function(){
|
|
169
|
+
toolSel.value = 'fuzzySearch';
|
|
170
|
+
renderToolInfo();
|
|
171
|
+
params.value = JSON.stringify({ q: 'corey hadden', filters: { itemtype: 'user' }, threshold: 0.5 }, null, 2);
|
|
172
|
+
};
|
|
173
|
+
presetFuzzyHouse.onclick = function(){
|
|
174
|
+
toolSel.value = 'fuzzySearch';
|
|
175
|
+
renderToolInfo();
|
|
176
|
+
params.value = JSON.stringify({ q: 'backyard', filters: { itemtype: 'house' }, threshold: 0.5 }, null, 2);
|
|
177
|
+
};
|
|
178
|
+
presetListApps.onclick = function(){
|
|
179
|
+
toolSel.value = 'listApps';
|
|
180
|
+
renderToolInfo();
|
|
181
|
+
params.value = JSON.stringify({}, null, 2);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
presetGetSchemas.onclick = function(){
|
|
185
|
+
ensureTool('getSchemas', {
|
|
186
|
+
name: 'getSchemas',
|
|
187
|
+
description: 'Retrieve multiple schema definitions. If omitted, returns all.',
|
|
188
|
+
params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } } } },
|
|
189
|
+
returns: { type: 'object' }
|
|
190
|
+
});
|
|
191
|
+
toolSel.value = 'getSchemas';
|
|
192
|
+
renderToolInfo();
|
|
193
|
+
params.value = JSON.stringify({ names: ["client", "user"] }, null, 2);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
presetGetSchemasSummary.onclick = function(){
|
|
197
|
+
ensureTool('getSchemas', {
|
|
198
|
+
name: 'getSchemas',
|
|
199
|
+
description: 'Retrieve multiple schemas. With summaryOnly=true, returns summaries; if names omitted, returns all.',
|
|
200
|
+
params: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' } }, summaryOnly: { type: 'boolean' } } },
|
|
201
|
+
returns: { type: 'object' }
|
|
202
|
+
});
|
|
203
|
+
toolSel.value = 'getSchemas';
|
|
204
|
+
renderToolInfo();
|
|
205
|
+
params.value = JSON.stringify({ names: ["task", "project"], summaryOnly: true }, null, 2);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
presetSearchSlimRecent.onclick = function(){
|
|
209
|
+
toolSel.value = 'search';
|
|
210
|
+
renderToolInfo();
|
|
211
|
+
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, sortBy: 'joeUpdated', sortDir: 'desc', slim: true }, null, 2);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
presetSearchWithCount.onclick = function(){
|
|
215
|
+
toolSel.value = 'search';
|
|
216
|
+
renderToolInfo();
|
|
217
|
+
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, limit: 25, withCount: true, sortBy: 'joeUpdated', sortDir: 'desc' }, null, 2);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
presetSearchCountOnly.onclick = function(){
|
|
221
|
+
toolSel.value = 'search';
|
|
222
|
+
renderToolInfo();
|
|
223
|
+
params.value = JSON.stringify({ itemtype: 'client', source: 'cache', query: { itemtype: 'client' }, countOnly: true }, null, 2);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
presetSaveObjects.onclick = function(){
|
|
227
|
+
toolSel.value = 'saveObjects';
|
|
228
|
+
renderToolInfo();
|
|
229
|
+
// Example: two minimal objects; adjust itemtype/fields as needed
|
|
230
|
+
params.value = JSON.stringify({
|
|
231
|
+
objects: [
|
|
232
|
+
{ itemtype: "client", name: "Batch Client A" },
|
|
233
|
+
{ itemtype: "client", name: "Batch Client B" }
|
|
234
|
+
],
|
|
235
|
+
stopOnError: false,
|
|
236
|
+
concurrency: 5
|
|
237
|
+
}, null, 2);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
presetUnderstandObject.onclick = function(){
|
|
241
|
+
toolSel.value = 'understandObject';
|
|
242
|
+
renderToolInfo();
|
|
243
|
+
// Provide a template; user should replace _id with a real object id.
|
|
244
|
+
params.value = JSON.stringify({ _id: "REPLACE_WITH_OBJECT_ID", depth: 2 }, null, 2);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
presetFindByTag.onclick = function(){
|
|
248
|
+
toolSel.value = 'findObjectsByTag';
|
|
249
|
+
renderToolInfo();
|
|
250
|
+
// Example: find objects with all of these tags
|
|
251
|
+
params.value = JSON.stringify({ tags: ["REPLACE_WITH_TAG_ID_1", "REPLACE_WITH_TAG_ID_2"], itemtype: "task" }, null, 2);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
presetFindByStatus.onclick = function(){
|
|
255
|
+
toolSel.value = 'findObjectsByStatus';
|
|
256
|
+
renderToolInfo();
|
|
257
|
+
// Example: find objects with this status
|
|
258
|
+
params.value = JSON.stringify({ status: "REPLACE_WITH_STATUS_ID", itemtype: "task" }, null, 2);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
callBtn.onclick = async function(){
|
|
262
|
+
setStatus(callStatus, 'Calling...', null);
|
|
263
|
+
result.textContent='';
|
|
264
|
+
try{
|
|
265
|
+
const url = base.value.replace(/\/$/,'') + '/mcp';
|
|
266
|
+
let p = {};
|
|
267
|
+
try{ p = params.value ? JSON.parse(params.value) : {}; }catch(e){ throw new Error('Invalid JSON in params'); }
|
|
268
|
+
const body = {
|
|
269
|
+
jsonrpc: '2.0',
|
|
270
|
+
id: String(idCounter++),
|
|
271
|
+
method: toolSel.value,
|
|
272
|
+
params: p
|
|
273
|
+
};
|
|
274
|
+
const resp = await fetchJSON(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
|
|
275
|
+
result.textContent = JSON.stringify(resp, null, 2);
|
|
276
|
+
setStatus(callStatus, 'OK', true);
|
|
277
|
+
}catch(e){
|
|
278
|
+
setStatus(callStatus, e.message||String(e), false);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Auto-load manifest on open
|
|
283
|
+
setTimeout(()=>loadBtn.click(), 50);
|
|
284
|
+
})();
|
|
285
|
+
</script>
|
|
286
|
+
</body>
|
|
287
|
+
</html>
|
package/capp/capp.css
CHANGED
|
@@ -325,7 +325,7 @@ capp-button-icon svg{
|
|
|
325
325
|
width:100%;
|
|
326
326
|
text-align:center;
|
|
327
327
|
}
|
|
328
|
-
html.no-touch
|
|
328
|
+
html.no-touch capp-button:hover svg-label,
|
|
329
329
|
html.touch .show-icons capp-button svg-label,
|
|
330
330
|
capp-button.selected svg-label
|
|
331
331
|
{
|
|
@@ -337,7 +337,8 @@ capp-button.selected svg-label
|
|
|
337
337
|
width:100%;
|
|
338
338
|
text-align:center;
|
|
339
339
|
}
|
|
340
|
-
capp-button.selected capp-button-icon svg
|
|
340
|
+
capp-button.selected capp-button-icon svg,
|
|
341
|
+
capp-button:hover capp-button-icon svg{
|
|
341
342
|
height: 40px;
|
|
342
343
|
width:100%;
|
|
343
344
|
}
|