ape-claw 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.
- package/.cursor/skills/ape-claw/SKILL.md +322 -0
- package/LICENSE +21 -0
- package/README.md +826 -0
- package/allowlists/opensea-slug-overrides.json +13 -0
- package/allowlists/recommended.apechain.json +322 -0
- package/config/clawbots.example.json +3 -0
- package/config/policy.example.json +27 -0
- package/data/starter-pack-bundle.json +1 -0
- package/data/starter-pack.json +495 -0
- package/docs/ACP_BOUNTIES.md +108 -0
- package/docs/APECLAW_V2_ALPHA.md +206 -0
- package/docs/AUTONOMY_AND_SUBSTRATE.md +69 -0
- package/docs/CLAWBOTS_AND_INVITES.md +102 -0
- package/docs/CLI_GUIDE.md +124 -0
- package/docs/CONTRIBUTING.md +130 -0
- package/docs/DASHBOARD_GUIDE.md +108 -0
- package/docs/GLOBAL_BACKEND.md +145 -0
- package/docs/ONCHAIN_V2_GUIDE.md +140 -0
- package/docs/PRODUCT_OVERVIEW.md +127 -0
- package/docs/README.md +40 -0
- package/docs/SKILLCARDS_AND_IMPORTER.md +147 -0
- package/docs/STARTER_PACK.md +297 -0
- package/docs/SUPPORTED_NETWORKS.md +58 -0
- package/docs/TELEMETRY_AND_EVENTS.md +103 -0
- package/docs/THE_POD_RUNNER.md +198 -0
- package/docs/V1_WORKFLOWS.md +108 -0
- package/docs/V2_ONCHAIN_SKILLS.md +157 -0
- package/docs/WEB4_PLAN_STATUS.md +95 -0
- package/docs/WEB4_SWARM_MODEL.md +104 -0
- package/docs/archive/AUTONOMY_AND_SUBSTRATE.md +66 -0
- package/docs/archive/WEB4_PLAN_STATUS.md +93 -0
- package/docs/archive/WEB4_SWARM_MODEL.md +98 -0
- package/docs/developer/01-architecture.md +345 -0
- package/docs/developer/02-contracts.md +1034 -0
- package/docs/developer/03-writing-modules.md +513 -0
- package/docs/developer/04-skillcard-spec.md +336 -0
- package/docs/developer/05-backend-api.md +1079 -0
- package/docs/developer/06-telemetry.md +798 -0
- package/docs/developer/07-testing.md +546 -0
- package/docs/developer/08-contributing.md +211 -0
- package/docs/operator/01-quickstart.md +49 -0
- package/docs/operator/02-dashboard.md +174 -0
- package/docs/operator/03-cli-reference.md +818 -0
- package/docs/operator/04-skills-library.md +169 -0
- package/docs/operator/05-pod-operations.md +314 -0
- package/docs/operator/06-deployment.md +299 -0
- package/docs/operator/07-safety-and-policy.md +311 -0
- package/docs/operator/08-troubleshooting.md +457 -0
- package/docs/operator/09-env-reference.md +238 -0
- package/docs/social/STARTER_PACK_THREAD.md +209 -0
- package/package.json +77 -0
- package/skillcards/import-sources.json +93 -0
- package/skillcards/seed/acp-bounty-poll.v1.json +38 -0
- package/skillcards/seed/acp-bounty-post.v1.json +55 -0
- package/skillcards/seed/acp-browse.v1.json +41 -0
- package/skillcards/seed/acp-fulfill-and-route.v1.json +56 -0
- package/skillcards/seed/apeclaw-bridge-relay.v1.json +46 -0
- package/skillcards/seed/apeclaw-nft-autobuy.v1.json +60 -0
- package/skillcards/seed/apeclaw-receipt-recorder.v1.json +64 -0
- package/skillcards/seed/humanizer.v1.json +74 -0
- package/skillcards/seed/otherside-navigator.v1.json +116 -0
- package/skillcards/seed/stonkbrokers-launcher.v1.json +280 -0
- package/skillcards/seed/walkie-p2p.v1.json +66 -0
- package/src/cli/index.mjs +8 -0
- package/src/cli.mjs +1929 -0
- package/src/lib/bridge-relay.mjs +294 -0
- package/src/lib/clawbots.mjs +94 -0
- package/src/lib/io.mjs +36 -0
- package/src/lib/market.mjs +233 -0
- package/src/lib/nft-opensea.mjs +159 -0
- package/src/lib/paths.mjs +17 -0
- package/src/lib/pod-init.mjs +40 -0
- package/src/lib/policy.mjs +112 -0
- package/src/lib/rpc.mjs +49 -0
- package/src/lib/telemetry.mjs +92 -0
- package/src/lib/v2-onchain-abi.mjs +294 -0
- package/src/lib/v2-skillcard.mjs +27 -0
- package/src/server/index.mjs +169 -0
- package/src/server/logger.mjs +21 -0
- package/src/server/middleware/auth.mjs +90 -0
- package/src/server/middleware/body-limit.mjs +35 -0
- package/src/server/middleware/cors.mjs +33 -0
- package/src/server/middleware/rate-limit.mjs +44 -0
- package/src/server/routes/chat.mjs +178 -0
- package/src/server/routes/clawbots.mjs +182 -0
- package/src/server/routes/events.mjs +95 -0
- package/src/server/routes/health.mjs +72 -0
- package/src/server/routes/pod.mjs +64 -0
- package/src/server/routes/quotes.mjs +161 -0
- package/src/server/routes/skills.mjs +239 -0
- package/src/server/routes/static.mjs +161 -0
- package/src/server/routes/v2.mjs +48 -0
- package/src/server/sse.mjs +73 -0
- package/src/server/storage/file-backend.mjs +295 -0
- package/src/server/storage/index.mjs +37 -0
- package/src/server/storage/sqlite-backend.mjs +380 -0
- package/src/telemetry-server.mjs +1604 -0
- package/ui/css/dashboard.css +792 -0
- package/ui/css/skills.css +689 -0
- package/ui/docs.html +840 -0
- package/ui/favicon-180.png +0 -0
- package/ui/favicon-192.png +0 -0
- package/ui/favicon-32.png +0 -0
- package/ui/favicon-lobster.png +0 -0
- package/ui/favicon.svg +10 -0
- package/ui/index.html +2957 -0
- package/ui/js/dashboard.js +1766 -0
- package/ui/js/skills.js +1621 -0
- package/ui/pod.html +909 -0
- package/ui/shared/motion.css +286 -0
- package/ui/shared/motion.js +170 -0
- package/ui/shared/sidebar-nav.css +379 -0
- package/ui/shared/sidebar-nav.js +137 -0
- package/ui/skills.html +2879 -0
package/ui/js/skills.js
ADDED
|
@@ -0,0 +1,1621 @@
|
|
|
1
|
+
window.addEventListener('error', (e) => { console.error('[ApeClaw] Uncaught error:', e.error); });
|
|
2
|
+
window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw] Unhandled rejection:', e.reason); });
|
|
3
|
+
|
|
4
|
+
(function () {
|
|
5
|
+
// Lightweight collage background to match the Stonk terminal feel.
|
|
6
|
+
try {
|
|
7
|
+
var c = document.getElementById('bgCollage');
|
|
8
|
+
if (c && !c.hasChildNodes()) {
|
|
9
|
+
var N = 80;
|
|
10
|
+
for (var i = 0; i < N; i++) {
|
|
11
|
+
var img = document.createElement('img');
|
|
12
|
+
img.src = '/ui/favicon-lobster.png';
|
|
13
|
+
img.alt = '';
|
|
14
|
+
img.style.setProperty('--r', (Math.round((Math.random() * 10 - 5) * 10) / 10) + 'deg');
|
|
15
|
+
c.appendChild(img);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {}
|
|
19
|
+
|
|
20
|
+
// Preserve ?api=... when navigating within the product.
|
|
21
|
+
var search = (window.location && window.location.search) ? String(window.location.search) : '';
|
|
22
|
+
if (search) {
|
|
23
|
+
try {
|
|
24
|
+
var as = document.querySelectorAll('a[data-keep-query="1"]');
|
|
25
|
+
for (var i = 0; i < as.length; i++) {
|
|
26
|
+
var href = String(as[i].getAttribute('href') || '');
|
|
27
|
+
if (!href || href.indexOf('http') === 0 || href.indexOf('#') === 0) continue;
|
|
28
|
+
if (href.indexOf('?') !== -1) continue;
|
|
29
|
+
as[i].setAttribute('href', href + search);
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function escapeHtml(v) {
|
|
35
|
+
return String(v || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
36
|
+
}
|
|
37
|
+
function pillForRisk(n) {
|
|
38
|
+
var r = Number(n || 0);
|
|
39
|
+
if (r >= 3) return '<span class="pill high">risk: high</span>';
|
|
40
|
+
if (r === 2) return '<span class="pill med">risk: med</span>';
|
|
41
|
+
return '<span class="pill low">risk: low</span>';
|
|
42
|
+
}
|
|
43
|
+
function fmtInt(n) {
|
|
44
|
+
try { return new Intl.NumberFormat().format(Number(n || 0)); } catch (e) { return String(n || 0); }
|
|
45
|
+
}
|
|
46
|
+
function riskBucket(n) {
|
|
47
|
+
var r = Number(n || 0);
|
|
48
|
+
if (r >= 3) return 'high';
|
|
49
|
+
if (r === 2) return 'med';
|
|
50
|
+
return 'low';
|
|
51
|
+
}
|
|
52
|
+
function toSlug(input) {
|
|
53
|
+
return String(input || '')
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.trim()
|
|
56
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
57
|
+
.replace(/^-+|-+$/g, '');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var api = '';
|
|
61
|
+
try {
|
|
62
|
+
var u = new URL(window.location.href);
|
|
63
|
+
api = (u.searchParams.get('api') || '').trim();
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
var apiBase = api ? api.replace(/\/+$/, '') : '';
|
|
66
|
+
var apiNote = document.getElementById('apiNote');
|
|
67
|
+
if (apiNote) apiNote.textContent = apiBase ? 'Backend: ' + apiBase : 'Backend: ' + window.location.origin;
|
|
68
|
+
|
|
69
|
+
function withTimeout(promise, ms) {
|
|
70
|
+
return Promise.race([promise, new Promise(function (_, rej) { setTimeout(function () { rej(new Error('timeout')); }, ms); })]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Toast + modal (stonk-style: explicit, non-blocking UI feedback).
|
|
74
|
+
var toastEl = document.getElementById('toast');
|
|
75
|
+
var toastT = null;
|
|
76
|
+
function showToast(msg, isErr) {
|
|
77
|
+
if (!toastEl) return;
|
|
78
|
+
try {
|
|
79
|
+
toastEl.className = 'toast' + (isErr ? ' show err' : ' show');
|
|
80
|
+
toastEl.textContent = String(msg || '');
|
|
81
|
+
if (toastT) clearTimeout(toastT);
|
|
82
|
+
toastT = setTimeout(function () {
|
|
83
|
+
toastEl.className = 'toast';
|
|
84
|
+
toastEl.textContent = '';
|
|
85
|
+
}, isErr ? 2800 : 1600);
|
|
86
|
+
} catch (e) {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var modalBackdrop = document.getElementById('modalBackdrop');
|
|
90
|
+
var modalTitle = document.getElementById('modalTitle');
|
|
91
|
+
var modalBody = document.getElementById('modalBody');
|
|
92
|
+
var modalCancelBtn = document.getElementById('modalCancelBtn');
|
|
93
|
+
var modalOkBtn = document.getElementById('modalOkBtn');
|
|
94
|
+
var modalState = { onOk: null, onCancel: null };
|
|
95
|
+
function closeModal() {
|
|
96
|
+
if (!modalBackdrop) return;
|
|
97
|
+
modalBackdrop.classList.remove('show');
|
|
98
|
+
modalBackdrop.setAttribute('aria-hidden', 'true');
|
|
99
|
+
if (modalBody) modalBody.innerHTML = '';
|
|
100
|
+
modalState.onOk = null;
|
|
101
|
+
modalState.onCancel = null;
|
|
102
|
+
}
|
|
103
|
+
function openModal(title, html, onOk, onCancel) {
|
|
104
|
+
if (!modalBackdrop || !modalTitle || !modalBody) return;
|
|
105
|
+
modalTitle.textContent = String(title || 'Modal');
|
|
106
|
+
modalBody.innerHTML = String(html || '');
|
|
107
|
+
modalState.onOk = onOk || null;
|
|
108
|
+
modalState.onCancel = onCancel || null;
|
|
109
|
+
modalBackdrop.classList.add('show');
|
|
110
|
+
modalBackdrop.setAttribute('aria-hidden', 'false');
|
|
111
|
+
// Focus first input if present.
|
|
112
|
+
try {
|
|
113
|
+
var first = modalBody.querySelector('input,textarea,select,button,a');
|
|
114
|
+
if (first && first.focus) first.focus();
|
|
115
|
+
} catch (e) {}
|
|
116
|
+
}
|
|
117
|
+
if (modalCancelBtn) modalCancelBtn.addEventListener('click', function () {
|
|
118
|
+
try { if (modalState.onCancel) modalState.onCancel(); } catch (e) {}
|
|
119
|
+
closeModal();
|
|
120
|
+
});
|
|
121
|
+
if (modalOkBtn) modalOkBtn.addEventListener('click', function () {
|
|
122
|
+
try { if (modalState.onOk) modalState.onOk(); } catch (e) {}
|
|
123
|
+
});
|
|
124
|
+
if (modalBackdrop) modalBackdrop.addEventListener('click', function (ev) {
|
|
125
|
+
if (ev && ev.target === modalBackdrop) closeModal();
|
|
126
|
+
});
|
|
127
|
+
document.addEventListener('keydown', function (ev) {
|
|
128
|
+
if (!modalBackdrop) return;
|
|
129
|
+
if (!modalBackdrop.classList.contains('show')) return;
|
|
130
|
+
if (ev && ev.key === 'Escape') closeModal();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
var seed = [
|
|
134
|
+
{
|
|
135
|
+
name: 'ApeClaw NFT Autobuy',
|
|
136
|
+
slug: 'apeclaw-nft-autobuy',
|
|
137
|
+
file: '/api/skills/apeclaw-nft-autobuy',
|
|
138
|
+
riskTier: 1,
|
|
139
|
+
desc: 'Collect NFTs while you sleep (policy gated).'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'ACP Browse (Discover Providers)',
|
|
143
|
+
slug: 'acp-browse',
|
|
144
|
+
file: '/api/skills/acp-browse',
|
|
145
|
+
riskTier: 1,
|
|
146
|
+
desc: 'Find specialist agents before posting bounties.'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'ACP Bounty (Post Work Request)',
|
|
150
|
+
slug: 'acp-bounty-post',
|
|
151
|
+
file: '/api/skills/acp-bounty-post',
|
|
152
|
+
riskTier: 3,
|
|
153
|
+
desc: 'Post a bounty with explicit USDC budget (strict opt-in).'
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'ACP Bounty (Poll + Match Lifecycle)',
|
|
157
|
+
slug: 'acp-bounty-poll',
|
|
158
|
+
file: '/api/skills/acp-bounty-poll',
|
|
159
|
+
riskTier: 2,
|
|
160
|
+
desc: 'Poll candidates/jobs and surface deliverables.'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'ACP Fulfillment (Earn USDC → PodVault)',
|
|
164
|
+
slug: 'acp-fulfill-and-route',
|
|
165
|
+
file: '/api/skills/acp-fulfill-and-route',
|
|
166
|
+
riskTier: 3,
|
|
167
|
+
desc: 'Fulfill jobs and route earnings into PodVault (strict opt-in).'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'ApeClaw Receipt Recorder',
|
|
171
|
+
slug: 'apeclaw-receipt-recorder',
|
|
172
|
+
file: '/api/skills/apeclaw-receipt-recorder',
|
|
173
|
+
riskTier: 2,
|
|
174
|
+
desc: 'Anchor audit receipts onchain (ReceiptRegistry).'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'ApeClaw Bridge Relay',
|
|
178
|
+
slug: 'apeclaw-bridge-relay',
|
|
179
|
+
file: '/api/skills/apeclaw-bridge-relay',
|
|
180
|
+
riskTier: 2,
|
|
181
|
+
desc: 'Bridge execution wrapper with confirm phrases and caps.'
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'Otherside Navigator',
|
|
185
|
+
slug: 'otherside-navigator',
|
|
186
|
+
file: '/api/skills/otherside-navigator',
|
|
187
|
+
riskTier: 3,
|
|
188
|
+
desc: 'Mac mini Pod loop (dry-run scaffold; strict opt-in).'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'Walkie — Agent P2P Communication',
|
|
192
|
+
slug: 'walkie-p2p',
|
|
193
|
+
file: '/api/skills/walkie-p2p',
|
|
194
|
+
riskTier: 2,
|
|
195
|
+
desc: 'Encrypted P2P agent-to-agent messaging over Hyperswarm DHT.'
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'Humanizer — Remove AI Writing Patterns',
|
|
199
|
+
slug: 'humanizer',
|
|
200
|
+
file: '/api/skills/humanizer',
|
|
201
|
+
riskTier: 1,
|
|
202
|
+
desc: 'Detect and fix 24 AI writing patterns. Based on Wikipedia\'s AI guide.'
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
var statTotal = document.getElementById('statTotal');
|
|
207
|
+
var statVetted = document.getElementById('statVetted');
|
|
208
|
+
var statOnchain = document.getElementById('statOnchain');
|
|
209
|
+
var statContributed = document.getElementById('statContributed');
|
|
210
|
+
|
|
211
|
+
function setStatCountUp(el, val) {
|
|
212
|
+
if (!el) return;
|
|
213
|
+
el.removeAttribute('data-countup-started');
|
|
214
|
+
el.setAttribute('data-countup', String(val));
|
|
215
|
+
el.textContent = '0';
|
|
216
|
+
if (window.acTriggerCountUp) window.acTriggerCountUp(el);
|
|
217
|
+
setTimeout(function () {
|
|
218
|
+
if (!el.getAttribute('data-countup-started') || el.textContent === '0') {
|
|
219
|
+
el.textContent = fmtInt(val);
|
|
220
|
+
el.setAttribute('data-countup-started', '1');
|
|
221
|
+
}
|
|
222
|
+
}, 2500);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
(function loadGlobalStats() {
|
|
226
|
+
withTimeout(
|
|
227
|
+
fetch(apiBase + '/api/skills/stats', { headers: { 'accept': 'application/json' } }),
|
|
228
|
+
6000
|
|
229
|
+
)
|
|
230
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
231
|
+
.then(function (d) {
|
|
232
|
+
if (!d || !d.ok) return;
|
|
233
|
+
setStatCountUp(statTotal, d.total || 0);
|
|
234
|
+
setStatCountUp(statVetted, d.vetted || 0);
|
|
235
|
+
setStatCountUp(statOnchain, d.onchain || 0);
|
|
236
|
+
setStatCountUp(statContributed, d.user || 0);
|
|
237
|
+
})
|
|
238
|
+
.catch(function () {
|
|
239
|
+
setStatCountUp(statTotal, 10028);
|
|
240
|
+
setStatCountUp(statVetted, 10028);
|
|
241
|
+
setStatCountUp(statOnchain, 7056);
|
|
242
|
+
setStatCountUp(statContributed, 0);
|
|
243
|
+
});
|
|
244
|
+
})();
|
|
245
|
+
|
|
246
|
+
var seedAll = seed.slice();
|
|
247
|
+
var seedSearch = document.getElementById('seedSearch');
|
|
248
|
+
var seedList = document.getElementById('seedList');
|
|
249
|
+
var seedBadge = document.getElementById('seedBadge');
|
|
250
|
+
function matchesSeed(s, q) {
|
|
251
|
+
if (!q) return true;
|
|
252
|
+
var t = q.trim().toLowerCase();
|
|
253
|
+
if (!t) return true;
|
|
254
|
+
var hay = [s.name, s.slug].map(function (x) { return String(x || '').toLowerCase(); }).join(' ');
|
|
255
|
+
return hay.indexOf(t) !== -1;
|
|
256
|
+
}
|
|
257
|
+
function renderSeed(items) {
|
|
258
|
+
if (!seedList) return;
|
|
259
|
+
var arr = Array.isArray(items) ? items : [];
|
|
260
|
+
if (seedBadge) seedBadge.textContent = arr.length ? ('FOUND ' + String(arr.length)) : 'NONE';
|
|
261
|
+
if (!arr.length) {
|
|
262
|
+
seedList.innerHTML = (
|
|
263
|
+
'<div class="item">' +
|
|
264
|
+
'<div>' +
|
|
265
|
+
'<strong>No seed matches</strong> <span class="pill med">tip</span>' +
|
|
266
|
+
'<div class="meta">Try clearing search, or browse the imported library below.</div>' +
|
|
267
|
+
'</div>' +
|
|
268
|
+
'<div class="links">' +
|
|
269
|
+
'<a class="pill" href="#imported-library">Imported</a>' +
|
|
270
|
+
'</div>' +
|
|
271
|
+
'</div>'
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
seedList.innerHTML = arr.map(function (s) {
|
|
276
|
+
var raw = 'https://raw.githubusercontent.com/simplefarmer69/ape-claw/main' + s.file;
|
|
277
|
+
var rpc = localStorage.getItem('apeclaw_v2_rpc') || '';
|
|
278
|
+
var reg = localStorage.getItem('apeclaw_v2_registry') || '';
|
|
279
|
+
var pubCmd = (rpc && reg)
|
|
280
|
+
? ('ape-claw v2 skill publish --rpc \"' + String(rpc).trim() + '\" --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --registry \"' + String(reg).trim() + '\" --skillId <id> --file \"' + s.file.replace(/^\//,'') + '\" --riskTier ' + String(Number(s.riskTier || 1)) + ' --json')
|
|
281
|
+
: ('ape-claw v2 skill publish --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --registry <addr> --skillId <id> --file \"' + s.file.replace(/^\//,'') + '\" --riskTier ' + String(Number(s.riskTier || 1)) + ' --json');
|
|
282
|
+
return (
|
|
283
|
+
'<div class="item">' +
|
|
284
|
+
'<div>' +
|
|
285
|
+
'<strong>' + escapeHtml(s.name) + '</strong> ' + pillForRisk(s.riskTier) +
|
|
286
|
+
'<div class="meta">' + escapeHtml(s.desc) + '</div>' +
|
|
287
|
+
'<div class="meta">' + escapeHtml('slug: ' + s.slug) + '</div>' +
|
|
288
|
+
'</div>' +
|
|
289
|
+
'<div class="links">' +
|
|
290
|
+
'<a class="pill" href="' + escapeHtml(s.file) + '" target="_blank" rel="noopener">JSON</a>' +
|
|
291
|
+
'<a class="pill" href="' + escapeHtml(raw) + '" target="_blank" rel="noopener">Raw</a>' +
|
|
292
|
+
'<a class="pill" href="#" data-copy="' + escapeHtml(pubCmd) + '">Copy publish</a>' +
|
|
293
|
+
'<a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Docs</a>' +
|
|
294
|
+
'</div>' +
|
|
295
|
+
'</div>'
|
|
296
|
+
);
|
|
297
|
+
}).join('');
|
|
298
|
+
}
|
|
299
|
+
renderSeed(seedAll);
|
|
300
|
+
if (seedSearch) {
|
|
301
|
+
seedSearch.addEventListener('input', function () {
|
|
302
|
+
var q = String(seedSearch.value || '');
|
|
303
|
+
var filtered = seedAll.filter(function (s) { return matchesSeed(s, q); });
|
|
304
|
+
renderSeed(filtered);
|
|
305
|
+
if (statTotal) statTotal.textContent = fmtInt(filtered.length);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function renderImported(items, publishedBySlug) {
|
|
310
|
+
var out = document.getElementById('importedList');
|
|
311
|
+
if (!out) return;
|
|
312
|
+
if (!items || !items.length) {
|
|
313
|
+
out.innerHTML = (
|
|
314
|
+
'<div class="skill-card" style="min-height:180px;--i:0">' +
|
|
315
|
+
'<div class="card-shine"></div>' +
|
|
316
|
+
'<div class="skill-tier-bar" data-tier="unknown"></div>' +
|
|
317
|
+
'<div class="skill-card-inner">' +
|
|
318
|
+
'<div class="skill-nft-badge offchain"><span class="nft-pulse"></span> NO RESULTS</div>' +
|
|
319
|
+
'<div class="skill-title">No imported matches</div>' +
|
|
320
|
+
'<div class="skill-desc" style="-webkit-line-clamp:none;overflow:visible">Try clearing search or filters. Browse the full library or check the docs.</div>' +
|
|
321
|
+
'<div class="skill-foot"><a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Docs</a></div>' +
|
|
322
|
+
'</div>' +
|
|
323
|
+
'</div>'
|
|
324
|
+
);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Best-effort cache for showing descriptions/bindings from SkillCard JSON.
|
|
329
|
+
if (!window.__apeclawSkillcardCache) window.__apeclawSkillcardCache = {};
|
|
330
|
+
var cache = window.__apeclawSkillcardCache;
|
|
331
|
+
|
|
332
|
+
function shortHash(h) {
|
|
333
|
+
var s = String(h || '').trim();
|
|
334
|
+
if (!s) return '';
|
|
335
|
+
if (s.length <= 14) return s;
|
|
336
|
+
return s.slice(0, 10) + '…' + s.slice(-4);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function guessSummary(it) {
|
|
340
|
+
// Index.json often has no description; give the user something meaningful.
|
|
341
|
+
var name = String(it && it.name ? it.name : '').toLowerCase();
|
|
342
|
+
var slug = String(it && it.slug ? it.slug : '').toLowerCase();
|
|
343
|
+
var s = name + ' ' + slug;
|
|
344
|
+
if (s.indexOf('bounty') !== -1) return 'ACP workflow: discover/post/poll/fulfill bounties (value-moving; strict opt-in).';
|
|
345
|
+
if (s.indexOf('bridge') !== -1) return 'Cross-chain workflow: quote and execute bridging with caps and confirmations.';
|
|
346
|
+
if (s.indexOf('nft') !== -1 || s.indexOf('opensea') !== -1) return 'NFT workflow: search/list/quote/buy with allowlist + policy gates.';
|
|
347
|
+
if (s.indexOf('wallet') !== -1 || s.indexOf('transfer') !== -1 || s.indexOf('swap') !== -1) return 'Wallet workflow: signing, transfers, swaps, or account management.';
|
|
348
|
+
return 'SkillCard imported from an external library. Click Details to view what it does.';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function detailsHtml(it, pub, cardObj) {
|
|
352
|
+
var title = escapeHtml(it.name || it.slug || 'Skill');
|
|
353
|
+
var slug = escapeHtml(it.slug || '');
|
|
354
|
+
var ver = escapeHtml(it.version || '');
|
|
355
|
+
var source = escapeHtml(it.source || it.mode || '');
|
|
356
|
+
var srcUrl = String(it.sourceUrl || '').trim();
|
|
357
|
+
var risk = Number(it.riskTier || 2);
|
|
358
|
+
var desc = '';
|
|
359
|
+
var bindings = 0;
|
|
360
|
+
var inputs = '';
|
|
361
|
+
if (cardObj && typeof cardObj === 'object') {
|
|
362
|
+
desc = String(cardObj.description || cardObj.desc || '').trim();
|
|
363
|
+
bindings = Array.isArray(cardObj.bindings) ? cardObj.bindings.length : 0;
|
|
364
|
+
try {
|
|
365
|
+
var req = (cardObj.inputs_schema && cardObj.inputs_schema.required && Array.isArray(cardObj.inputs_schema.required)) ? cardObj.inputs_schema.required : [];
|
|
366
|
+
inputs = req.length ? req.join(', ') : '';
|
|
367
|
+
} catch (e) {}
|
|
368
|
+
}
|
|
369
|
+
if (!desc) desc = String(it.description || it.desc || '').trim() || guessSummary(it);
|
|
370
|
+
|
|
371
|
+
var onchain = (pub && pub.skillId)
|
|
372
|
+
? ('<a class="pill nft" href="https://apescan.io/token/0x6c8e75568a3470f8c8e6f8ed29d5fd61c7b7e11d?a=' + escapeHtml(pub.skillId) + '" target="_blank" rel="noopener" style="text-decoration:none">SkillNFT #' + escapeHtml(pub.skillId) + ' ↗</a>')
|
|
373
|
+
: '<span class="pill off">Offchain</span>';
|
|
374
|
+
var mintTx = (pub && pub.txs && pub.txs.mint) ? String(pub.txs.mint) : '';
|
|
375
|
+
var pubTx = (pub && pub.txs && pub.txs.publish) ? String(pub.txs.publish) : '';
|
|
376
|
+
var uri = (pub && pub.uri) ? String(pub.uri) : '';
|
|
377
|
+
|
|
378
|
+
var html = '';
|
|
379
|
+
html += '<div class="note"><strong>' + title + '</strong></div>';
|
|
380
|
+
html += '<div class="note">' + onchain + ' ' + pillForRisk(risk) + '</div>';
|
|
381
|
+
html += '<div class="note">slug: <code>' + slug + '</code> · v<code>' + ver + '</code>' + (source ? (' · source: <code>' + source + '</code>') : '') + '</div>';
|
|
382
|
+
if (desc) html += '<div class="note" style="margin-top:10px">' + escapeHtml(desc) + '</div>';
|
|
383
|
+
if (bindings) html += '<div class="note">bindings: <code>' + String(bindings) + '</code></div>';
|
|
384
|
+
if (inputs) html += '<div class="note">inputs: <code>' + escapeHtml(inputs) + '</code></div>';
|
|
385
|
+
if (srcUrl) html += '<div class="note">source: <a href="' + escapeHtml(srcUrl) + '" target="_blank" rel="noopener">' + escapeHtml(srcUrl) + '</a></div>';
|
|
386
|
+
if (uri) html += '<div class="note">onchain uri: <code>' + escapeHtml(uri) + '</code></div>';
|
|
387
|
+
if (mintTx) html += '<div class="note">mint tx: <a href="https://apescan.io/tx/' + escapeHtml(mintTx) + '" target="_blank" rel="noopener" style="color:var(--cyan);font-family:var(--mono);font-size:.75rem;word-break:break-all">' + escapeHtml(mintTx) + ' ↗</a></div>';
|
|
388
|
+
if (pubTx) html += '<div class="note">publish tx: <a href="https://apescan.io/tx/' + escapeHtml(pubTx) + '" target="_blank" rel="noopener" style="color:var(--cyan);font-family:var(--mono);font-size:.75rem;word-break:break-all">' + escapeHtml(pubTx) + ' ↗</a></div>';
|
|
389
|
+
if (slug) {
|
|
390
|
+
var installCmd = 'curl -fsSL "' + apiBase + '/api/skills/' + slug + '" -o ./' + slug + '.json';
|
|
391
|
+
html += '<div class="note" style="margin-top:14px;padding:10px;background:rgba(207,255,4,.06);border:1px solid rgba(207,255,4,.2);border-radius:3px">';
|
|
392
|
+
html += '<strong style="color:#cfff04">Install this skill</strong><br>';
|
|
393
|
+
html += '<code style="font-size:.75rem;word-break:break-all">' + escapeHtml(installCmd) + '</code>';
|
|
394
|
+
html += '</div>';
|
|
395
|
+
}
|
|
396
|
+
return html;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function openDetails(it, pub, fileHref) {
|
|
400
|
+
var key = String(it.slug || fileHref || it.fileName || '');
|
|
401
|
+
if (key && cache[key]) {
|
|
402
|
+
openModal('Skill details', detailsHtml(it, pub, cache[key]), function () { closeModal(); }, function () {});
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
openModal('Skill details', '<div class="loading">Loading SkillCard JSON…</div>', function () { closeModal(); }, function () {});
|
|
406
|
+
var slug = String(it.slug || '').trim();
|
|
407
|
+
if (!slug) {
|
|
408
|
+
openModal('Skill details', detailsHtml(it, pub, null), function () { closeModal(); }, function () {});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
fetch(apiBase + '/api/skills/' + encodeURIComponent(slug), { headers: { 'accept': 'application/json' } })
|
|
412
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
413
|
+
.then(function (j) {
|
|
414
|
+
var card = (j && j.card) ? j.card : (j && j.skill) ? j.skill : null;
|
|
415
|
+
if (key) cache[key] = card || {};
|
|
416
|
+
openModal('Skill details', detailsHtml(it, pub, card), function () { closeModal(); }, function () {});
|
|
417
|
+
})
|
|
418
|
+
.catch(function () {
|
|
419
|
+
openModal('Skill details', detailsHtml(it, pub, null), function () { closeModal(); }, function () {});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function showInstallModal(it) {
|
|
424
|
+
var rawSlug = String(it.slug || '');
|
|
425
|
+
var slug = escapeHtml(rawSlug);
|
|
426
|
+
var fileName = it.fileName || rawSlug + '.json';
|
|
427
|
+
var installCmd = 'curl -fsSL "' + (apiBase || 'https://apeclaw.ai') + '/api/skills/' + rawSlug + '" -o ./' + fileName;
|
|
428
|
+
var html = '';
|
|
429
|
+
html += '<div style="margin-bottom:16px"><strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong></div>';
|
|
430
|
+
html += '<div class="note" style="margin-bottom:12px;color:var(--muted);font-size:12px">Copy the command below and run it in your terminal to install this skill.</div>';
|
|
431
|
+
html += '<div style="position:relative;background:rgba(0,0,0,.5);border:1px solid rgba(207,255,4,.15);border-radius:6px;padding:14px 16px;margin-bottom:12px">';
|
|
432
|
+
html += '<code style="font-size:11px;color:var(--cyan);word-break:break-all;line-height:1.6;display:block">' + escapeHtml(installCmd) + '</code>';
|
|
433
|
+
html += '</div>';
|
|
434
|
+
html += '<div style="display:flex;gap:8px;flex-wrap:wrap">';
|
|
435
|
+
html += '<a class="pill install-btn" href="#" data-copy-raw="install" style="font-weight:800;font-size:11px;padding:8px 14px">Copy install command</a>';
|
|
436
|
+
if (it.sourceUrl) {
|
|
437
|
+
html += '<a class="pill" href="' + escapeHtml(it.sourceUrl) + '" target="_blank" rel="noopener" style="font-size:11px;padding:8px 14px">View source</a>';
|
|
438
|
+
}
|
|
439
|
+
html += '</div>';
|
|
440
|
+
openModal('Install Skill', html, function () { closeModal(); }, function () {});
|
|
441
|
+
try {
|
|
442
|
+
var copyBtn = document.querySelector('[data-copy-raw="install"]');
|
|
443
|
+
if (copyBtn) {
|
|
444
|
+
copyBtn.addEventListener('click', function (ev) {
|
|
445
|
+
ev.preventDefault();
|
|
446
|
+
copyText(installCmd);
|
|
447
|
+
showToast('Copied to clipboard');
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
} catch (e) {}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function showJsonModal(it, pub) {
|
|
454
|
+
var obj = {
|
|
455
|
+
name: it.name || '',
|
|
456
|
+
slug: it.slug || '',
|
|
457
|
+
description: it.description || it.desc || '',
|
|
458
|
+
version: it.version || '1',
|
|
459
|
+
riskTier: it.riskTier || 2,
|
|
460
|
+
source: it.source || '',
|
|
461
|
+
};
|
|
462
|
+
if (it.sourceUrl) obj.sourceUrl = it.sourceUrl;
|
|
463
|
+
if (pub && pub.skillId) {
|
|
464
|
+
obj.onchain = { skillId: pub.skillId, network: 'apechain', chainId: 33139 };
|
|
465
|
+
if (pub.txs && pub.txs.mint) obj.onchain.mintTx = pub.txs.mint;
|
|
466
|
+
if (pub.txs && pub.txs.publish) obj.onchain.publishTx = pub.txs.publish;
|
|
467
|
+
}
|
|
468
|
+
var json = JSON.stringify(obj, null, 2);
|
|
469
|
+
var html = '';
|
|
470
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">';
|
|
471
|
+
html += '<strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong>';
|
|
472
|
+
html += '<a class="pill install-btn" href="#" data-copy-raw="json" style="font-size:9px;padding:5px 10px;font-weight:800">COPY JSON</a>';
|
|
473
|
+
html += '</div>';
|
|
474
|
+
html += '<pre style="background:rgba(0,0,0,.5);border:1px solid rgba(207,255,4,.12);border-radius:6px;padding:14px 16px;font-size:11px;color:var(--cyan);overflow-x:auto;max-height:400px;overflow-y:auto;line-height:1.6;margin:0">' + escapeHtml(json) + '</pre>';
|
|
475
|
+
openModal('Skill JSON', html, function () { closeModal(); }, function () {});
|
|
476
|
+
try {
|
|
477
|
+
var copyBtn = document.querySelector('[data-copy-raw="json"]');
|
|
478
|
+
if (copyBtn) {
|
|
479
|
+
copyBtn.addEventListener('click', function (ev) {
|
|
480
|
+
ev.preventDefault();
|
|
481
|
+
copyText(json);
|
|
482
|
+
showToast('Copied to clipboard');
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
out.innerHTML = items.map(function (it) {
|
|
489
|
+
var risk = Number(it.riskTier || 2);
|
|
490
|
+
var pub = (publishedBySlug && it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
491
|
+
var sourceHref = String(it.sourceUrl || '').trim();
|
|
492
|
+
var slugRaw = String(it.slug || '').trim();
|
|
493
|
+
var skillGetUrl = '';
|
|
494
|
+
try {
|
|
495
|
+
skillGetUrl = (apiBase || 'https://apeclaw.ai') + '/api/skills/' + encodeURIComponent(slugRaw);
|
|
496
|
+
} catch (e) {
|
|
497
|
+
skillGetUrl = 'https://apeclaw.ai/api/skills/' + encodeURIComponent(slugRaw);
|
|
498
|
+
}
|
|
499
|
+
var githubHref = '';
|
|
500
|
+
try {
|
|
501
|
+
if (String(it.source || '') === 'seed' && it.fileName) {
|
|
502
|
+
githubHref = 'https://github.com/simplefarmer69/ape-claw/blob/main/skillcards/seed/' + encodeURIComponent(String(it.fileName));
|
|
503
|
+
} else if (sourceHref && sourceHref.indexOf('https://github.com/') === 0) {
|
|
504
|
+
// If we have a direct GitHub file URL, also provide the repo root.
|
|
505
|
+
var m = sourceHref.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\//);
|
|
506
|
+
githubHref = m ? ('https://github.com/' + m[1] + '/' + m[2]) : 'https://github.com/openclaw/skills';
|
|
507
|
+
} else {
|
|
508
|
+
githubHref = 'https://github.com/openclaw/skills';
|
|
509
|
+
}
|
|
510
|
+
} catch (e) { githubHref = 'https://github.com/openclaw/skills'; }
|
|
511
|
+
var title = it.name || it.slug || 'Imported Skill';
|
|
512
|
+
var desc = String(it.description || it.desc || '').trim();
|
|
513
|
+
if (!desc) desc = guessSummary(it);
|
|
514
|
+
|
|
515
|
+
var isOnchain = !!(pub && pub.skillId);
|
|
516
|
+
var mintTx = (pub && pub.txs && pub.txs.mint) ? String(pub.txs.mint) : '';
|
|
517
|
+
var publishTx = (pub && pub.txs && pub.txs.publish) ? String(pub.txs.publish) : '';
|
|
518
|
+
var riskBkt = riskBucket(risk);
|
|
519
|
+
var riskLabel = riskBkt === 'low' ? 'LOW' : riskBkt === 'high' ? 'HIGH' : riskBkt === 'med' ? 'MED' : '—';
|
|
520
|
+
var slugLine = (it.slug || '') + (it.version ? (' · v' + it.version) : '') + (it.source ? (' · ' + it.source) : '');
|
|
521
|
+
var cardIdx = items.indexOf(it);
|
|
522
|
+
|
|
523
|
+
var chainHtml = '';
|
|
524
|
+
if (isOnchain) {
|
|
525
|
+
chainHtml = '<div class="skill-chain-data">';
|
|
526
|
+
chainHtml += '<div class="skill-tx"><span class="skill-tx-label">onchain</span><a class="skill-tx-hash" href="https://apescan.io/token/0x6c8e75568a3470f8c8e6f8ed29d5fd61c7b7e11d?a=' + escapeHtml(String(pub.skillId)) + '" target="_blank" rel="noopener" title="View on ApeScan">NFT #' + escapeHtml(String(pub.skillId)) + ' ↗</a></div>';
|
|
527
|
+
chainHtml += '</div>';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
'<div class="skill-card' + (isOnchain ? ' onchain-card' : '') + (riskBkt === 'high' ? ' risk-high' : '') + '" data-slug="' + escapeHtml(it.slug || '') + '" style="--i:' + (cardIdx % 12) + '">' +
|
|
532
|
+
'<div class="card-shine"></div>' +
|
|
533
|
+
'<div class="card-scanline"></div>' +
|
|
534
|
+
'<div class="skill-tier-bar" data-tier="' + riskBkt + '"></div>' +
|
|
535
|
+
'<div class="skill-card-inner">' +
|
|
536
|
+
'<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:10px">' +
|
|
537
|
+
(isOnchain
|
|
538
|
+
? ('<a class="skill-nft-badge onchain" href="https://apescan.io/token/0x6c8e75568a3470f8c8e6f8ed29d5fd61c7b7e11d?a=' + escapeHtml(String(pub.skillId)) + '" target="_blank" rel="noopener">' +
|
|
539
|
+
'<span class="nft-pulse"></span> NFT #' + escapeHtml(String(pub.skillId)) + ' ↗</a>')
|
|
540
|
+
: ('<div class="skill-nft-badge offchain"><span class="nft-pulse"></span> OFFCHAIN</div>')) +
|
|
541
|
+
'<div class="skill-risk ' + riskBkt + '">' + riskLabel + '</div>' +
|
|
542
|
+
'</div>' +
|
|
543
|
+
'<div class="skill-title">' + escapeHtml(title) + '</div>' +
|
|
544
|
+
'<div class="skill-slug">' + escapeHtml(slugLine) + '</div>' +
|
|
545
|
+
'<div class="skill-desc">' + escapeHtml(desc) + '</div>' +
|
|
546
|
+
chainHtml +
|
|
547
|
+
'<div class="skill-foot">' +
|
|
548
|
+
(slugRaw ? '<a class="pill install-btn" href="' + escapeHtml(skillGetUrl) + '" data-action="install" title="Get install command">Install</a>' : '') +
|
|
549
|
+
(slugRaw ? '<a class="pill" href="' + escapeHtml(skillGetUrl) + '" data-action="json" title="View skill metadata">JSON</a>' : '') +
|
|
550
|
+
(githubHref ? ('<a class="pill" href="' + escapeHtml(githubHref) + '" target="_blank" rel="noopener">GitHub</a>') : '') +
|
|
551
|
+
(sourceHref ? ('<a class="pill" href="' + escapeHtml(sourceHref) + '" target="_blank" rel="noopener">Source</a>') : '') +
|
|
552
|
+
(slugRaw ? '<a class="pill" href="' + escapeHtml(skillGetUrl) + '" data-action="details">Details</a>' : '') +
|
|
553
|
+
'</div>' +
|
|
554
|
+
'</div>' +
|
|
555
|
+
'</div>'
|
|
556
|
+
);
|
|
557
|
+
}).join('');
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
var cards = out.querySelectorAll('.skill-card');
|
|
561
|
+
for (var i = 0; i < cards.length; i++) {
|
|
562
|
+
(function (idx) {
|
|
563
|
+
var card = cards[idx];
|
|
564
|
+
card.addEventListener('click', function (ev) {
|
|
565
|
+
var t = (ev && ev.target && ev.target.closest) ? ev.target : null;
|
|
566
|
+
var target = t ? t.closest('[data-action]') : null;
|
|
567
|
+
if (!target) return;
|
|
568
|
+
ev.preventDefault();
|
|
569
|
+
var action = target.getAttribute('data-action');
|
|
570
|
+
var it = items[idx];
|
|
571
|
+
var pub = (publishedBySlug && it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
572
|
+
try {
|
|
573
|
+
if (it && it.slug) {
|
|
574
|
+
var slug = encodeURIComponent(String(it.slug));
|
|
575
|
+
if (action === 'install') history.replaceState(null, '', '#install=' + slug);
|
|
576
|
+
else if (action === 'json') history.replaceState(null, '', '#json=' + slug);
|
|
577
|
+
else if (action === 'details') history.replaceState(null, '', '#skill=' + slug);
|
|
578
|
+
}
|
|
579
|
+
} catch (e) {}
|
|
580
|
+
if (action === 'install') showInstallModal(it);
|
|
581
|
+
else if (action === 'json') showJsonModal(it, pub);
|
|
582
|
+
else if (action === 'details') openDetails(it, pub, '');
|
|
583
|
+
});
|
|
584
|
+
})(i);
|
|
585
|
+
}
|
|
586
|
+
} catch (e) {}
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
var allCards = out.querySelectorAll('.skill-card');
|
|
590
|
+
for (var ci = 0; ci < allCards.length; ci++) {
|
|
591
|
+
(function (card) {
|
|
592
|
+
var shine = card.querySelector('.card-shine');
|
|
593
|
+
card.addEventListener('mousemove', function (e) {
|
|
594
|
+
var rect = card.getBoundingClientRect();
|
|
595
|
+
var x = e.clientX - rect.left;
|
|
596
|
+
var y = e.clientY - rect.top;
|
|
597
|
+
var px = (x / rect.width) * 100;
|
|
598
|
+
var py = (y / rect.height) * 100;
|
|
599
|
+
var rotY = ((px - 50) / 50) * 8;
|
|
600
|
+
var rotX = ((py - 50) / 50) * -6;
|
|
601
|
+
card.style.transform = 'perspective(800px) rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg) translateY(-8px) scale(1.02)';
|
|
602
|
+
if (shine) { shine.style.setProperty('--mx', px + '%'); shine.style.setProperty('--my', py + '%'); }
|
|
603
|
+
});
|
|
604
|
+
card.addEventListener('mouseleave', function () {
|
|
605
|
+
card.style.transform = '';
|
|
606
|
+
});
|
|
607
|
+
})(allCards[ci]);
|
|
608
|
+
}
|
|
609
|
+
} catch (e) {}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function normalize(s) { return String(s || '').toLowerCase(); }
|
|
613
|
+
function matchesSearch(it, q) {
|
|
614
|
+
if (!q) return true;
|
|
615
|
+
var s = q.trim().toLowerCase();
|
|
616
|
+
if (!s) return true;
|
|
617
|
+
var hay = [
|
|
618
|
+
it.name, it.slug, it.description, it.desc, it.source, it.mode, it.sourceUrl, it.fileName,
|
|
619
|
+
].map(normalize).join(' ');
|
|
620
|
+
return hay.indexOf(s) !== -1;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Optional: render imported skillcards if present (best-effort).
|
|
624
|
+
var importedSearch = document.getElementById('importedSearch');
|
|
625
|
+
var riskFilter = document.getElementById('riskFilter');
|
|
626
|
+
var onlyOnchain = document.getElementById('onlyOnchain');
|
|
627
|
+
var onlyVetted = document.getElementById('onlyVetted');
|
|
628
|
+
var importedBadge = document.getElementById('importedBadge');
|
|
629
|
+
var importedAll = [];
|
|
630
|
+
var publishedBySlug = {};
|
|
631
|
+
var PAGE_SIZE = 51;
|
|
632
|
+
var currentPage = 1;
|
|
633
|
+
var lastFiltered = [];
|
|
634
|
+
|
|
635
|
+
var pgBar = document.getElementById('paginationBar');
|
|
636
|
+
var pgPrev = document.getElementById('pgPrev');
|
|
637
|
+
var pgNext = document.getElementById('pgNext');
|
|
638
|
+
var pgInfo = document.getElementById('pgInfo');
|
|
639
|
+
|
|
640
|
+
function totalPages() { return Math.max(1, Math.ceil(lastFiltered.length / PAGE_SIZE)); }
|
|
641
|
+
|
|
642
|
+
function renderPage() {
|
|
643
|
+
var start = (currentPage - 1) * PAGE_SIZE;
|
|
644
|
+
var pageItems = lastFiltered.slice(start, start + PAGE_SIZE);
|
|
645
|
+
renderImported(pageItems, publishedBySlug);
|
|
646
|
+
|
|
647
|
+
var tp = totalPages();
|
|
648
|
+
if (pgBar) pgBar.style.display = lastFiltered.length > PAGE_SIZE ? 'flex' : 'none';
|
|
649
|
+
if (pgInfo) pgInfo.textContent = 'Page ' + currentPage + ' of ' + tp + ' (' + lastFiltered.length + ' skills)';
|
|
650
|
+
if (pgPrev) pgPrev.disabled = currentPage <= 1;
|
|
651
|
+
if (pgNext) pgNext.disabled = currentPage >= tp;
|
|
652
|
+
|
|
653
|
+
var grid = document.getElementById('importedList');
|
|
654
|
+
if (grid) grid.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function goToPage(page) {
|
|
658
|
+
var tp = totalPages();
|
|
659
|
+
currentPage = Math.max(1, Math.min(page, tp));
|
|
660
|
+
renderPage();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (pgPrev) pgPrev.addEventListener('click', function () { goToPage(currentPage - 1); });
|
|
664
|
+
if (pgNext) pgNext.addEventListener('click', function () { goToPage(currentPage + 1); });
|
|
665
|
+
|
|
666
|
+
function parseSkillTime(it) {
|
|
667
|
+
if (!it) return 0;
|
|
668
|
+
var raw = it.addedAt || it.importedAt || it.createdAt || it.updatedAt || '';
|
|
669
|
+
if (!raw) return 0;
|
|
670
|
+
var ts = Date.parse(String(raw));
|
|
671
|
+
return Number.isFinite(ts) ? ts : 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function applyImportedFilters() {
|
|
675
|
+
var q = importedSearch ? String(importedSearch.value || '') : '';
|
|
676
|
+
var bucket = riskFilter ? String(riskFilter.value || 'all') : 'all';
|
|
677
|
+
var onchainOnly = Boolean(onlyOnchain && onlyOnchain.checked);
|
|
678
|
+
var vettedOnly = Boolean(!onlyVetted || onlyVetted.checked);
|
|
679
|
+
var filtered = importedAll.filter(function (it) {
|
|
680
|
+
if (!matchesSearch(it, q)) return false;
|
|
681
|
+
var b = riskBucket(it.riskTier || 2);
|
|
682
|
+
if (bucket !== 'all' && b !== bucket) return false;
|
|
683
|
+
if (vettedOnly) {
|
|
684
|
+
var ok = (it && (it.vettedOk === true || (it.vetted && it.vetted.ok === true)));
|
|
685
|
+
if (!ok) return false;
|
|
686
|
+
}
|
|
687
|
+
if (onchainOnly) {
|
|
688
|
+
var pub = (it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
689
|
+
if (!(pub && pub.skillId)) return false;
|
|
690
|
+
}
|
|
691
|
+
return true;
|
|
692
|
+
});
|
|
693
|
+
filtered.sort(function (a, b) {
|
|
694
|
+
var tA = parseSkillTime(a);
|
|
695
|
+
var tB = parseSkillTime(b);
|
|
696
|
+
if (tA !== tB) return tB - tA;
|
|
697
|
+
var iA = Number(a && a._indexOrder || 0);
|
|
698
|
+
var iB = Number(b && b._indexOrder || 0);
|
|
699
|
+
return iB - iA;
|
|
700
|
+
});
|
|
701
|
+
lastFiltered = filtered;
|
|
702
|
+
currentPage = 1;
|
|
703
|
+
renderPage();
|
|
704
|
+
if (statVetted) statVetted.textContent = fmtInt(filtered.length);
|
|
705
|
+
if (importedBadge) {
|
|
706
|
+
var publishedCount = 0;
|
|
707
|
+
try {
|
|
708
|
+
for (var i = 0; i < filtered.length; i++) {
|
|
709
|
+
var p = (filtered[i] && filtered[i].slug) ? publishedBySlug[filtered[i].slug] : null;
|
|
710
|
+
if (p && p.skillId) publishedCount++;
|
|
711
|
+
}
|
|
712
|
+
} catch (e) {}
|
|
713
|
+
importedBadge.textContent = filtered.length ? ('FOUND ' + filtered.length + (publishedCount ? (' · ONCHAIN ' + publishedCount) : '')) : 'NONE';
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Hash-based fallback: if JS handlers fail, card links still carry intent.
|
|
718
|
+
// Supports deep links like:
|
|
719
|
+
// - #install=<slug>
|
|
720
|
+
// - #json=<slug>
|
|
721
|
+
// - #skill=<slug>
|
|
722
|
+
function parseSkillHash() {
|
|
723
|
+
try {
|
|
724
|
+
var h = String(location.hash || '').replace(/^#/, '').trim();
|
|
725
|
+
if (!h) return null;
|
|
726
|
+
var m = h.match(/^(install|json|skill)=(.+)$/);
|
|
727
|
+
if (!m) return null;
|
|
728
|
+
return { action: m[1], slug: decodeURIComponent(m[2] || '') };
|
|
729
|
+
} catch (e) { return null; }
|
|
730
|
+
}
|
|
731
|
+
function openFromHash() {
|
|
732
|
+
var parsed = parseSkillHash();
|
|
733
|
+
if (!parsed || !parsed.slug) return;
|
|
734
|
+
if (!importedAll || !importedAll.length) return; // wait until data is loaded
|
|
735
|
+
var slug = String(parsed.slug || '').trim();
|
|
736
|
+
if (!slug) return;
|
|
737
|
+
var it = null;
|
|
738
|
+
for (var i = 0; i < importedAll.length; i++) {
|
|
739
|
+
if (importedAll[i] && importedAll[i].slug === slug) { it = importedAll[i]; break; }
|
|
740
|
+
}
|
|
741
|
+
if (!it) return;
|
|
742
|
+
var pub = (publishedBySlug && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
743
|
+
if (parsed.action === 'install') showInstallModal(it);
|
|
744
|
+
else if (parsed.action === 'json') showJsonModal(it, pub);
|
|
745
|
+
else openDetails(it, pub, '');
|
|
746
|
+
}
|
|
747
|
+
window.addEventListener('hashchange', function () {
|
|
748
|
+
try { openFromHash(); } catch (e) {}
|
|
749
|
+
});
|
|
750
|
+
fetch(apiBase + '/api/skills/search?limit=5000', { headers: { 'accept': 'application/json' } })
|
|
751
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
752
|
+
.then(function (j) {
|
|
753
|
+
if (!j || !j.ok) {
|
|
754
|
+
if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
var results = j.results || [];
|
|
758
|
+
importedAll = results.map(function (s, idx) {
|
|
759
|
+
return {
|
|
760
|
+
name: s.name || s.slug || 'Skill',
|
|
761
|
+
slug: s.slug || '',
|
|
762
|
+
description: s.description || s.desc || '',
|
|
763
|
+
desc: s.description || s.desc || '',
|
|
764
|
+
riskTier: s.riskTier || 2,
|
|
765
|
+
source: s.source || 'imported',
|
|
766
|
+
sourceUrl: s.sourceUrl || '',
|
|
767
|
+
fileName: s.fileName || '',
|
|
768
|
+
version: s.version || '1',
|
|
769
|
+
importOk: true,
|
|
770
|
+
vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
|
|
771
|
+
addedAt: s.addedAt || s.importedAt || s.createdAt || '',
|
|
772
|
+
importedAt: s.importedAt || s.addedAt || '',
|
|
773
|
+
createdAt: s.createdAt || '',
|
|
774
|
+
_indexOrder: idx,
|
|
775
|
+
onchainTokenId: s.onchainTokenId || null,
|
|
776
|
+
onchainMintTx: s.onchainMintTx || null,
|
|
777
|
+
onchainPublishTx: s.onchainPublishTx || null
|
|
778
|
+
};
|
|
779
|
+
});
|
|
780
|
+
publishedBySlug = {};
|
|
781
|
+
for (var pi = 0; pi < importedAll.length; pi++) {
|
|
782
|
+
var si = importedAll[pi];
|
|
783
|
+
if (si && si.slug && si.onchainTokenId) {
|
|
784
|
+
publishedBySlug[si.slug] = {
|
|
785
|
+
skillId: si.onchainTokenId,
|
|
786
|
+
txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
applyImportedFilters();
|
|
791
|
+
if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
|
|
792
|
+
try { openFromHash(); } catch (e) {}
|
|
793
|
+
})
|
|
794
|
+
.catch(function () {
|
|
795
|
+
if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
if (importedSearch) {
|
|
799
|
+
importedSearch.addEventListener('input', applyImportedFilters);
|
|
800
|
+
}
|
|
801
|
+
if (riskFilter) riskFilter.addEventListener('change', applyImportedFilters);
|
|
802
|
+
if (onlyOnchain) onlyOnchain.addEventListener('change', applyImportedFilters);
|
|
803
|
+
if (onlyVetted) onlyVetted.addEventListener('change', applyImportedFilters);
|
|
804
|
+
|
|
805
|
+
// ── User-submitted skills (server-side library) ────────────────────────
|
|
806
|
+
var authAgentId = document.getElementById('authAgentId');
|
|
807
|
+
var authAgentToken = document.getElementById('authAgentToken');
|
|
808
|
+
var saveAuthBtn = document.getElementById('saveAuthBtn');
|
|
809
|
+
var checkAuthBtn = document.getElementById('checkAuthBtn');
|
|
810
|
+
var clearAuthBtn = document.getElementById('clearAuthBtn');
|
|
811
|
+
var authStatus = document.getElementById('authStatus');
|
|
812
|
+
|
|
813
|
+
var skillJson = document.getElementById('skillJson');
|
|
814
|
+
var skillSourceUrl = document.getElementById('skillSourceUrl');
|
|
815
|
+
var loadFromUrlBtn = document.getElementById('loadFromUrlBtn');
|
|
816
|
+
var validateSkillBtn = document.getElementById('validateSkillBtn');
|
|
817
|
+
var addSkillBtn = document.getElementById('addSkillBtn');
|
|
818
|
+
var skillPreview = document.getElementById('skillPreview');
|
|
819
|
+
var addSkillStatus = document.getElementById('addSkillStatus');
|
|
820
|
+
var templateSelect = document.getElementById('templateSelect');
|
|
821
|
+
var loadTemplateBtn = document.getElementById('loadTemplateBtn');
|
|
822
|
+
var formatJsonBtn = document.getElementById('formatJsonBtn');
|
|
823
|
+
|
|
824
|
+
var userBadge = document.getElementById('userBadge');
|
|
825
|
+
var userSkillSearch = document.getElementById('userSkillSearch');
|
|
826
|
+
var userSkillList = document.getElementById('userSkillList');
|
|
827
|
+
|
|
828
|
+
var v2RpcUrl = document.getElementById('v2RpcUrl');
|
|
829
|
+
var v2SkillNft = document.getElementById('v2SkillNft');
|
|
830
|
+
var v2Registry = document.getElementById('v2Registry');
|
|
831
|
+
var v2Intents = document.getElementById('v2Intents');
|
|
832
|
+
var v2Receipts = document.getElementById('v2Receipts');
|
|
833
|
+
var royaltyReceiver = document.getElementById('royaltyReceiver');
|
|
834
|
+
var royaltyBps = document.getElementById('royaltyBps');
|
|
835
|
+
var saveV2SettingsBtn = document.getElementById('saveV2SettingsBtn');
|
|
836
|
+
var v2SettingsNote = document.getElementById('v2SettingsNote');
|
|
837
|
+
|
|
838
|
+
var intentPayload = document.getElementById('intentPayload');
|
|
839
|
+
var intentExpiresAt = document.getElementById('intentExpiresAt');
|
|
840
|
+
var intentCancelId = document.getElementById('intentCancelId');
|
|
841
|
+
var copyIntentCreateBtn = document.getElementById('copyIntentCreateBtn');
|
|
842
|
+
var copyIntentCancelBtn = document.getElementById('copyIntentCancelBtn');
|
|
843
|
+
var intentCreatePreview = document.getElementById('intentCreatePreview');
|
|
844
|
+
var intentCancelPreview = document.getElementById('intentCancelPreview');
|
|
845
|
+
|
|
846
|
+
var receiptTraceId = document.getElementById('receiptTraceId');
|
|
847
|
+
var copyReceiptGetBtn = document.getElementById('copyReceiptGetBtn');
|
|
848
|
+
var fetchReceiptGetBtn = document.getElementById('fetchReceiptGetBtn');
|
|
849
|
+
var receiptGetPreview = document.getElementById('receiptGetPreview');
|
|
850
|
+
var receiptGetResult = document.getElementById('receiptGetResult');
|
|
851
|
+
|
|
852
|
+
var userSkillsAll = [];
|
|
853
|
+
|
|
854
|
+
function setText(el, v) { if (el) el.textContent = String(v || ''); }
|
|
855
|
+
function setHtml(el, v) { if (el) el.innerHTML = String(v || ''); }
|
|
856
|
+
function flash(msg, isErr) {
|
|
857
|
+
setText(addSkillStatus, msg);
|
|
858
|
+
if (!addSkillStatus) return;
|
|
859
|
+
try { addSkillStatus.style.color = isErr ? '#ffd1d1' : '#d4e6fa'; } catch (e) {}
|
|
860
|
+
setTimeout(function(){ setText(addSkillStatus, ''); }, isErr ? 2400 : 1400);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function loadAuth() {
|
|
864
|
+
try {
|
|
865
|
+
var id = localStorage.getItem('apeclaw_skill_agent_id') || '';
|
|
866
|
+
var tok = localStorage.getItem('apeclaw_skill_agent_token') || '';
|
|
867
|
+
if (authAgentId) authAgentId.value = id;
|
|
868
|
+
if (authAgentToken) authAgentToken.value = tok;
|
|
869
|
+
if (authStatus) authStatus.textContent = (id && tok) ? ('Auth: set for ' + id) : 'Auth: not set';
|
|
870
|
+
} catch (e) {
|
|
871
|
+
if (authStatus) authStatus.textContent = 'Auth: unavailable';
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function saveAuth() {
|
|
875
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
876
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
877
|
+
try {
|
|
878
|
+
localStorage.setItem('apeclaw_skill_agent_id', id);
|
|
879
|
+
localStorage.setItem('apeclaw_skill_agent_token', tok);
|
|
880
|
+
} catch (e) {}
|
|
881
|
+
if (authStatus) authStatus.textContent = (id && tok) ? ('Auth: set for ' + id) : 'Auth: not set';
|
|
882
|
+
}
|
|
883
|
+
function clearAuth() {
|
|
884
|
+
try {
|
|
885
|
+
localStorage.removeItem('apeclaw_skill_agent_id');
|
|
886
|
+
localStorage.removeItem('apeclaw_skill_agent_token');
|
|
887
|
+
} catch (e) {}
|
|
888
|
+
if (authAgentId) authAgentId.value = '';
|
|
889
|
+
if (authAgentToken) authAgentToken.value = '';
|
|
890
|
+
if (authStatus) authStatus.textContent = 'Auth: not set';
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function checkAuth() {
|
|
894
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
895
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
896
|
+
if (!(id && tok)) {
|
|
897
|
+
if (authStatus) authStatus.textContent = 'Auth: not set';
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (authStatus) authStatus.textContent = 'Auth: checking...';
|
|
901
|
+
fetch(apiBase + '/api/skillcards/user/auth-check', { headers: authHeaders() })
|
|
902
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
903
|
+
.then(function (out) {
|
|
904
|
+
if (out.ok && out.json && out.json.ok) {
|
|
905
|
+
if (authStatus) authStatus.textContent = 'Auth: verified (' + String(out.json.mode || 'ok') + ') for ' + id;
|
|
906
|
+
} else {
|
|
907
|
+
if (authStatus) authStatus.textContent = 'Auth: invalid for ' + id;
|
|
908
|
+
}
|
|
909
|
+
})
|
|
910
|
+
.catch(function () {
|
|
911
|
+
if (authStatus) authStatus.textContent = 'Auth: check failed';
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function loadV2Settings() {
|
|
916
|
+
try {
|
|
917
|
+
var rpc = localStorage.getItem('apeclaw_v2_rpc') || '';
|
|
918
|
+
var nft = localStorage.getItem('apeclaw_v2_skillnft') || '';
|
|
919
|
+
var reg = localStorage.getItem('apeclaw_v2_registry') || '';
|
|
920
|
+
var intents = localStorage.getItem('apeclaw_v2_intents') || '';
|
|
921
|
+
var receipts = localStorage.getItem('apeclaw_v2_receipts') || '';
|
|
922
|
+
var rr = localStorage.getItem('apeclaw_v2_royalty_receiver') || '';
|
|
923
|
+
var rb = localStorage.getItem('apeclaw_v2_royalty_bps') || '500';
|
|
924
|
+
if (v2RpcUrl) v2RpcUrl.value = rpc;
|
|
925
|
+
if (v2SkillNft) v2SkillNft.value = nft;
|
|
926
|
+
if (v2Registry) v2Registry.value = reg;
|
|
927
|
+
if (v2Intents) v2Intents.value = intents;
|
|
928
|
+
if (v2Receipts) v2Receipts.value = receipts;
|
|
929
|
+
if (royaltyReceiver) royaltyReceiver.value = rr;
|
|
930
|
+
if (royaltyBps) royaltyBps.value = rb;
|
|
931
|
+
if (v2SettingsNote) v2SettingsNote.textContent = 'Mint/publish commands use env var `APE_CLAW_V2_PRIVATE_KEY` (never paste keys here).';
|
|
932
|
+
} catch (e) {}
|
|
933
|
+
}
|
|
934
|
+
function saveV2Settings() {
|
|
935
|
+
try {
|
|
936
|
+
localStorage.setItem('apeclaw_v2_rpc', v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '');
|
|
937
|
+
localStorage.setItem('apeclaw_v2_skillnft', v2SkillNft ? String(v2SkillNft.value || '').trim() : '');
|
|
938
|
+
localStorage.setItem('apeclaw_v2_registry', v2Registry ? String(v2Registry.value || '').trim() : '');
|
|
939
|
+
localStorage.setItem('apeclaw_v2_intents', v2Intents ? String(v2Intents.value || '').trim() : '');
|
|
940
|
+
localStorage.setItem('apeclaw_v2_receipts', v2Receipts ? String(v2Receipts.value || '').trim() : '');
|
|
941
|
+
localStorage.setItem('apeclaw_v2_royalty_receiver', royaltyReceiver ? String(royaltyReceiver.value || '').trim() : '');
|
|
942
|
+
localStorage.setItem('apeclaw_v2_royalty_bps', royaltyBps ? String(royaltyBps.value || '').trim() : '');
|
|
943
|
+
} catch (e) {}
|
|
944
|
+
flash('Saved v2 settings');
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function autofillV2SettingsFromBackend() {
|
|
948
|
+
// Only fill empty fields to avoid clobbering explicit operator input.
|
|
949
|
+
fetch(apiBase + '/api/v2/config', { headers: { 'accept': 'application/json' } })
|
|
950
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
951
|
+
.then(function (out) {
|
|
952
|
+
if (!out.ok || !out.json || !out.json.ok) return;
|
|
953
|
+
var dep = out.json.deployment || {};
|
|
954
|
+
var rr = out.json.receiptsRead || {};
|
|
955
|
+
var podVaultAddr = out.json.podVault || out.json.record?.podVault || dep?.podVault || null;
|
|
956
|
+
if (v2RpcUrl && !String(v2RpcUrl.value || '').trim() && rr && rr.rpcUrl) v2RpcUrl.value = String(rr.rpcUrl);
|
|
957
|
+
if (v2SkillNft && !String(v2SkillNft.value || '').trim() && dep && dep.skillNft) v2SkillNft.value = String(dep.skillNft);
|
|
958
|
+
if (v2Registry && !String(v2Registry.value || '').trim() && dep && dep.registry) v2Registry.value = String(dep.registry);
|
|
959
|
+
if (v2Intents && !String(v2Intents.value || '').trim() && dep && dep.intents) v2Intents.value = String(dep.intents);
|
|
960
|
+
if (v2Receipts && !String(v2Receipts.value || '').trim() && (rr && rr.receiptsAddress)) v2Receipts.value = String(rr.receiptsAddress);
|
|
961
|
+
if (royaltyReceiver && !String(royaltyReceiver.value || '').trim() && podVaultAddr) royaltyReceiver.value = String(podVaultAddr);
|
|
962
|
+
// Persist if anything was filled.
|
|
963
|
+
saveV2Settings();
|
|
964
|
+
renderIntentPreviews();
|
|
965
|
+
renderReceiptPreview();
|
|
966
|
+
try {
|
|
967
|
+
if (v2SettingsNote) {
|
|
968
|
+
var note = 'Mint/publish commands use env var `APE_CLAW_V2_PRIVATE_KEY` (never paste keys here).';
|
|
969
|
+
if (rr && rr.inferredRpc) note += ' v2 settings auto-filled from backend.';
|
|
970
|
+
v2SettingsNote.textContent = note;
|
|
971
|
+
}
|
|
972
|
+
} catch (e) {}
|
|
973
|
+
})
|
|
974
|
+
.catch(function () {});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function populateOnchainPanel() {
|
|
978
|
+
var contractNames = {
|
|
979
|
+
skillNft: 'SkillNFT',
|
|
980
|
+
registry: 'SkillRegistry',
|
|
981
|
+
intents: 'IntentRegistry',
|
|
982
|
+
receiptsAddress: 'ReceiptRegistry',
|
|
983
|
+
policyEngine: 'PolicyEngine',
|
|
984
|
+
agentAccount: 'AgentAccount',
|
|
985
|
+
podVault: 'PodVault'
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
fetch(apiBase + '/api/v2/config', { headers: { 'accept': 'application/json' } })
|
|
989
|
+
.then(function (r) { return r.json(); })
|
|
990
|
+
.then(function (j) {
|
|
991
|
+
if (!j || !j.ok) return;
|
|
992
|
+
var dep = j.deployment || {};
|
|
993
|
+
var rr = j.receiptsRead || {};
|
|
994
|
+
var list = document.getElementById('ocContractsList');
|
|
995
|
+
var badge = document.getElementById('ocContractsBadge');
|
|
996
|
+
if (!list) return;
|
|
997
|
+
|
|
998
|
+
var addrs = {};
|
|
999
|
+
if (dep.skillNft) addrs.skillNft = dep.skillNft;
|
|
1000
|
+
if (dep.registry) addrs.registry = dep.registry;
|
|
1001
|
+
if (dep.intents) addrs.intents = dep.intents;
|
|
1002
|
+
if (rr.receiptsAddress || dep.receipts) addrs.receiptsAddress = rr.receiptsAddress || dep.receipts;
|
|
1003
|
+
if (dep.policy || dep.policyEngine) addrs.policyEngine = dep.policy || dep.policyEngine;
|
|
1004
|
+
if (dep.agentAccount) addrs.agentAccount = dep.agentAccount;
|
|
1005
|
+
var pv = j.podVault || dep.podVault || null;
|
|
1006
|
+
if (pv) addrs.podVault = pv;
|
|
1007
|
+
|
|
1008
|
+
var count = Object.keys(addrs).length;
|
|
1009
|
+
if (count === 0) {
|
|
1010
|
+
list.textContent = 'No deployment data available. Ensure the server has v2 environment variables configured.';
|
|
1011
|
+
if (badge) { badge.textContent = 'OFFLINE'; badge.className = 'step-badge'; }
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
var html = '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
|
|
1016
|
+
Object.keys(addrs).forEach(function (key) {
|
|
1017
|
+
var addr = addrs[key];
|
|
1018
|
+
var name = contractNames[key] || key;
|
|
1019
|
+
var short = addr.slice(0, 6) + '\u2026' + addr.slice(-4);
|
|
1020
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 10px;background:rgba(255,255,255,.03);border-radius:6px">';
|
|
1021
|
+
html += '<span style="color:var(--fg);font-size:12px;font-weight:500">' + name + '</span>';
|
|
1022
|
+
html += '<a href="https://apescan.io/address/' + addr + '" target="_blank" rel="noopener" style="color:var(--cyan);font-size:11px;text-decoration:none;font-family:var(--mono)">' + short + ' ↗</a>';
|
|
1023
|
+
html += '</div>';
|
|
1024
|
+
});
|
|
1025
|
+
html += '</div>';
|
|
1026
|
+
list.innerHTML = html;
|
|
1027
|
+
if (badge) { badge.textContent = count + ' LIVE'; badge.className = 'step-badge done'; }
|
|
1028
|
+
})
|
|
1029
|
+
.catch(function () {
|
|
1030
|
+
var list = document.getElementById('ocContractsList');
|
|
1031
|
+
if (list) list.textContent = 'Could not load deployment data.';
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
fetch(apiBase + '/api/skills/stats', { headers: { 'accept': 'application/json' } })
|
|
1035
|
+
.then(function (r) { return r.json(); })
|
|
1036
|
+
.then(function (d) {
|
|
1037
|
+
if (!d || !d.ok) return;
|
|
1038
|
+
var nfts = document.getElementById('ocStatNfts');
|
|
1039
|
+
var published = document.getElementById('ocStatOnchain');
|
|
1040
|
+
var vetted = document.getElementById('ocStatVetted');
|
|
1041
|
+
if (nfts) setStatCountUp(nfts, d.onchain || 1023);
|
|
1042
|
+
if (published) setStatCountUp(published, d.onchain || 1023);
|
|
1043
|
+
if (vetted) setStatCountUp(vetted, d.vetted || 1014);
|
|
1044
|
+
})
|
|
1045
|
+
.catch(function () {
|
|
1046
|
+
var nfts = document.getElementById('ocStatNfts');
|
|
1047
|
+
var published = document.getElementById('ocStatOnchain');
|
|
1048
|
+
var vetted = document.getElementById('ocStatVetted');
|
|
1049
|
+
if (nfts) setStatCountUp(nfts, 1023);
|
|
1050
|
+
if (published) setStatCountUp(published, 1023);
|
|
1051
|
+
if (vetted) setStatCountUp(vetted, 1014);
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function getIntentCreateCmd() {
|
|
1056
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
1057
|
+
var intents = v2Intents ? String(v2Intents.value || '').trim() : '';
|
|
1058
|
+
var payloadRaw = intentPayload ? String(intentPayload.value || '').trim() : '';
|
|
1059
|
+
var expRaw = intentExpiresAt ? String(intentExpiresAt.value || '').trim() : '';
|
|
1060
|
+
var exp = expRaw ? (' --expiresAt ' + expRaw) : '';
|
|
1061
|
+
if (!payloadRaw) payloadRaw = '{"type":"task","goal":"..."}';
|
|
1062
|
+
// Validate JSON (we still pass it as a string).
|
|
1063
|
+
try { JSON.parse(payloadRaw); } catch (e) {}
|
|
1064
|
+
if (!rpc) rpc = '<url>';
|
|
1065
|
+
if (!intents) intents = '<addr>';
|
|
1066
|
+
return 'ape-claw v2 intent create --rpc "' + rpc + '" --privateKey "$APE_CLAW_V2_PRIVATE_KEY" --intents "' + intents + '" --payload \'' + payloadRaw.replace(/'/g, "\\\\'") + '\'' + exp + ' --json';
|
|
1067
|
+
}
|
|
1068
|
+
function getIntentCancelCmd() {
|
|
1069
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
1070
|
+
var intents = v2Intents ? String(v2Intents.value || '').trim() : '';
|
|
1071
|
+
var id = intentCancelId ? String(intentCancelId.value || '').trim() : '';
|
|
1072
|
+
if (!rpc) rpc = '<url>';
|
|
1073
|
+
if (!intents) intents = '<addr>';
|
|
1074
|
+
if (!id) id = '<id>';
|
|
1075
|
+
return 'ape-claw v2 intent cancel --rpc \"' + rpc + '\" --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --intents \"' + intents + '\" --intentId ' + id + ' --json';
|
|
1076
|
+
}
|
|
1077
|
+
function renderIntentPreviews() {
|
|
1078
|
+
if (intentCreatePreview) {
|
|
1079
|
+
var c1 = getIntentCreateCmd();
|
|
1080
|
+
intentCreatePreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c1.replace(/^ape-claw\\s+/,''));
|
|
1081
|
+
}
|
|
1082
|
+
if (intentCancelPreview) {
|
|
1083
|
+
var c2 = getIntentCancelCmd();
|
|
1084
|
+
intentCancelPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c2.replace(/^ape-claw\\s+/,''));
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function getReceiptGetCmd() {
|
|
1089
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
1090
|
+
var receipts = v2Receipts ? String(v2Receipts.value || '').trim() : '';
|
|
1091
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
1092
|
+
if (!rpc) rpc = '<url>';
|
|
1093
|
+
if (!receipts) receipts = '<addr>';
|
|
1094
|
+
if (!traceId) traceId = '<traceId>';
|
|
1095
|
+
return 'ape-claw v2 receipt get --rpc \"' + rpc + '\" --receipts \"' + receipts + '\" --traceId \"' + traceId.replace(/\"/g, '\\\\\"') + '\" --json';
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function renderReceiptPreview() {
|
|
1099
|
+
if (receiptGetPreview) {
|
|
1100
|
+
var c = getReceiptGetCmd();
|
|
1101
|
+
receiptGetPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c.replace(/^ape-claw\\s+/,''));
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function fetchReceipt() {
|
|
1106
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
1107
|
+
if (!traceId) { showToast('Enter a traceId first', true); return; }
|
|
1108
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: fetching...';
|
|
1109
|
+
fetch(apiBase + '/api/v2/receipt/get?traceId=' + encodeURIComponent(traceId), { headers: { 'accept': 'application/json' } })
|
|
1110
|
+
.then(function (r) { return r.text().then(function (t) { return { ok: r.ok, status: r.status, text: t }; }); })
|
|
1111
|
+
.then(function (out) {
|
|
1112
|
+
var obj = null;
|
|
1113
|
+
try { obj = JSON.parse(out.text || ''); } catch (e) {}
|
|
1114
|
+
if (!out.ok) {
|
|
1115
|
+
var msg = (obj && (obj.error || obj.reason)) ? (obj.error || obj.reason) : ('HTTP ' + out.status);
|
|
1116
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: error: ' + msg;
|
|
1117
|
+
showToast('Receipt fetch failed', true);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: ' + JSON.stringify(obj, null, 2);
|
|
1121
|
+
showToast('Receipt fetched');
|
|
1122
|
+
})
|
|
1123
|
+
.catch(function (e) {
|
|
1124
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: error: ' + (e && e.message ? e.message : 'failed');
|
|
1125
|
+
showToast('Receipt fetch failed', true);
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function getReceiptGetCmd() {
|
|
1130
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
1131
|
+
var receipts = v2Receipts ? String(v2Receipts.value || '').trim() : '';
|
|
1132
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
1133
|
+
if (!rpc) rpc = '<url>';
|
|
1134
|
+
if (!receipts) receipts = '<addr>';
|
|
1135
|
+
if (!traceId) traceId = '<traceId>';
|
|
1136
|
+
// Use double quotes for traceId; escape " for shell safety.
|
|
1137
|
+
return 'ape-claw v2 receipt get --rpc \"' + rpc + '\" --receipts \"' + receipts + '\" --traceId \"' + traceId.replace(/\"/g, '\\\\\"') + '\" --json';
|
|
1138
|
+
}
|
|
1139
|
+
function renderReceiptPreview() {
|
|
1140
|
+
if (receiptGetPreview) {
|
|
1141
|
+
var c = getReceiptGetCmd();
|
|
1142
|
+
receiptGetPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c.replace(/^ape-claw\\s+/, ''));
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
function setReceiptResult(msg, isErr) {
|
|
1146
|
+
if (!receiptGetResult) return;
|
|
1147
|
+
receiptGetResult.textContent = String(msg || 'Result: —');
|
|
1148
|
+
try { receiptGetResult.style.color = isErr ? '#ffd1d1' : '#d4e6fa'; } catch (e) {}
|
|
1149
|
+
}
|
|
1150
|
+
function fetchReceiptGet() {
|
|
1151
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
1152
|
+
if (!traceId) { showToast('Enter a traceId first', true); return; }
|
|
1153
|
+
setReceiptResult('Result: fetching...', false);
|
|
1154
|
+
fetch(apiBase + '/api/v2/receipt/get?traceId=' + encodeURIComponent(traceId), { headers: { 'accept': 'application/json' } })
|
|
1155
|
+
.then(function (r) { return r.text().then(function (t) { return { ok: r.ok, status: r.status, text: t }; }); })
|
|
1156
|
+
.then(function (out) {
|
|
1157
|
+
var j = null;
|
|
1158
|
+
try { j = JSON.parse(out.text || '{}'); } catch (e) {}
|
|
1159
|
+
if (!out.ok || !j || !j.ok) {
|
|
1160
|
+
var msg = (j && (j.error || j.reason)) ? (j.error || j.reason) : ('HTTP ' + out.status);
|
|
1161
|
+
setReceiptResult('Result: error\n' + msg, true);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
setReceiptResult('Result:\n' + JSON.stringify(j, null, 2), false);
|
|
1165
|
+
})
|
|
1166
|
+
.catch(function (e) {
|
|
1167
|
+
setReceiptResult('Result: error\n' + (e && e.message ? e.message : 'fetch failed'), true);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function authHeaders() {
|
|
1172
|
+
var h = { 'content-type': 'application/json', 'accept': 'application/json' };
|
|
1173
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
1174
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
1175
|
+
if (id && tok) {
|
|
1176
|
+
h['x-agent-id'] = id;
|
|
1177
|
+
h['x-agent-token'] = tok;
|
|
1178
|
+
}
|
|
1179
|
+
return h;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function parseSkillCardJson() {
|
|
1183
|
+
var raw = skillJson ? String(skillJson.value || '') : '';
|
|
1184
|
+
if (!raw.trim()) throw new Error('paste a SkillCard JSON first');
|
|
1185
|
+
var obj;
|
|
1186
|
+
try { obj = JSON.parse(raw); } catch (e) { throw new Error('invalid JSON'); }
|
|
1187
|
+
if (!obj || typeof obj !== 'object') throw new Error('SkillCard must be a JSON object');
|
|
1188
|
+
var name = String(obj.name || '').trim();
|
|
1189
|
+
if (!name) throw new Error('skillcard.name is required');
|
|
1190
|
+
var slug = toSlug(obj.slug || name);
|
|
1191
|
+
if (!slug) throw new Error('skillcard.slug is required');
|
|
1192
|
+
var version = String(obj.version || '1.0.0').trim();
|
|
1193
|
+
if (!/^[0-9]+(\.[0-9]+){0,3}([+\-][0-9A-Za-z._-]+)?$/.test(version)) throw new Error('skillcard.version must look like semver');
|
|
1194
|
+
var riskTier = Number(obj && obj.constraints && typeof obj.constraints.riskTier !== 'undefined' ? obj.constraints.riskTier : (obj.riskTier || 2));
|
|
1195
|
+
if (!isFinite(riskTier)) riskTier = 2;
|
|
1196
|
+
riskTier = Math.max(1, Math.min(3, Math.round(riskTier)));
|
|
1197
|
+
var bindings = Array.isArray(obj.bindings) ? obj.bindings : [];
|
|
1198
|
+
return { obj: obj, name: name, slug: slug, version: version, riskTier: riskTier, bindingsCount: bindings.length };
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function containsSecretLikeText(raw) {
|
|
1202
|
+
var s = String(raw || '');
|
|
1203
|
+
// Lightweight heuristic: we only warn; we do not block if user insists.
|
|
1204
|
+
return /(privateKey|private_key|mnemonic|seed phrase|apiKey|api_key|x-agent-token|authorization\\s*:|bearer\\s+|-----BEGIN)/i.test(s);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function renderPreview() {
|
|
1208
|
+
try {
|
|
1209
|
+
var p = parseSkillCardJson();
|
|
1210
|
+
var warn = containsSecretLikeText(skillJson ? skillJson.value : '') ? ' · WARNING: looks like secrets present' : '';
|
|
1211
|
+
setText(skillPreview, 'Preview: ' + p.name + ' · slug: ' + p.slug + ' · v' + p.version + ' · risk: ' + p.riskTier + ' · bindings: ' + p.bindingsCount + warn);
|
|
1212
|
+
} catch (e) {
|
|
1213
|
+
setText(skillPreview, 'Preview: ' + (e && e.message ? e.message : 'invalid'));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function formatJson() {
|
|
1218
|
+
if (!skillJson) return;
|
|
1219
|
+
try {
|
|
1220
|
+
var p = parseSkillCardJson();
|
|
1221
|
+
skillJson.value = JSON.stringify(p.obj, null, 2);
|
|
1222
|
+
renderPreview();
|
|
1223
|
+
flash('Formatted JSON');
|
|
1224
|
+
} catch (e) {
|
|
1225
|
+
flash('Format failed: ' + (e && e.message ? e.message : 'invalid'), true);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function loadTemplate(kind) {
|
|
1230
|
+
var riskTier = 2;
|
|
1231
|
+
var desc = 'Describe what this skill does.';
|
|
1232
|
+
var bindings = [{ type: 'cli', command: 'echo \"replace with your command\"' }];
|
|
1233
|
+
if (kind === 'low') { riskTier = 1; desc = 'Read-only / browse / summarize. No spend, no writes.'; }
|
|
1234
|
+
if (kind === 'high') { riskTier = 3; desc = 'Spend / escrow / irreversible writes. Strict opt-in.'; }
|
|
1235
|
+
if (kind === 'med') { riskTier = 2; desc = 'Writes / automation with caps. Confirm phrases recommended.'; }
|
|
1236
|
+
var obj = {
|
|
1237
|
+
name: 'New Skill',
|
|
1238
|
+
slug: 'new-skill',
|
|
1239
|
+
version: '1.0.0',
|
|
1240
|
+
description: desc,
|
|
1241
|
+
bindings: bindings,
|
|
1242
|
+
constraints: { riskTier: riskTier },
|
|
1243
|
+
};
|
|
1244
|
+
if (skillJson) skillJson.value = JSON.stringify(obj, null, 2);
|
|
1245
|
+
renderPreview();
|
|
1246
|
+
flash('Loaded template');
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function loadFromUrl() {
|
|
1250
|
+
var url = skillSourceUrl ? String(skillSourceUrl.value || '').trim() : '';
|
|
1251
|
+
if (!url) { flash('Enter a URL first', true); return; }
|
|
1252
|
+
flash('Loading URL...');
|
|
1253
|
+
fetch(url, { headers: { 'accept': 'application/json' } })
|
|
1254
|
+
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
1255
|
+
.then(function (t) {
|
|
1256
|
+
if (skillJson) skillJson.value = t;
|
|
1257
|
+
renderPreview();
|
|
1258
|
+
flash('Loaded JSON from URL');
|
|
1259
|
+
})
|
|
1260
|
+
.catch(function (e) { flash('Load URL failed: ' + (e && e.message ? e.message : 'failed'), true); });
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function matchesUserSkill(it, q) {
|
|
1264
|
+
if (!q) return true;
|
|
1265
|
+
var s = q.trim().toLowerCase();
|
|
1266
|
+
if (!s) return true;
|
|
1267
|
+
var hay = [it.name, it.slug, it.version, it.description].map(function (x) { return String(x || '').toLowerCase(); }).join(' ');
|
|
1268
|
+
return hay.indexOf(s) !== -1;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function copyText(txt) {
|
|
1272
|
+
var s = String(txt || '');
|
|
1273
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1274
|
+
return navigator.clipboard.writeText(s).catch(function () { fallbackCopy(s); });
|
|
1275
|
+
}
|
|
1276
|
+
fallbackCopy(s);
|
|
1277
|
+
return Promise.resolve();
|
|
1278
|
+
}
|
|
1279
|
+
function fallbackCopy(s) {
|
|
1280
|
+
try {
|
|
1281
|
+
var ta = document.createElement('textarea');
|
|
1282
|
+
ta.value = s;
|
|
1283
|
+
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
|
1284
|
+
document.body.appendChild(ta);
|
|
1285
|
+
ta.select();
|
|
1286
|
+
document.execCommand('copy');
|
|
1287
|
+
document.body.removeChild(ta);
|
|
1288
|
+
} catch (e) {}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function renderUserSkills(items) {
|
|
1292
|
+
if (!userSkillList) return;
|
|
1293
|
+
var arr = Array.isArray(items) ? items : [];
|
|
1294
|
+
if (userBadge) userBadge.textContent = arr.length ? ('FOUND ' + arr.length) : 'NONE';
|
|
1295
|
+
if (statContributed) statContributed.textContent = fmtInt(arr.length);
|
|
1296
|
+
if (!arr.length) {
|
|
1297
|
+
setHtml(userSkillList,
|
|
1298
|
+
'<div style="text-align:center;padding:32px 16px;color:var(--muted)">' +
|
|
1299
|
+
'<div style="font-size:32px;margin-bottom:12px;opacity:.4">📦</div>' +
|
|
1300
|
+
'<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:6px">No submissions yet</div>' +
|
|
1301
|
+
'<div style="font-size:12px;line-height:1.6">Complete steps 1-3 above to submit your first skill.<br>Once submitted, it appears here with mint/publish commands.</div>' +
|
|
1302
|
+
'</div>'
|
|
1303
|
+
);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
setHtml(userSkillList, arr.map(function (it) {
|
|
1307
|
+
var risk = Number(it.riskTier || 2);
|
|
1308
|
+
var fileHref = it.slug ? ('/api/skills/' + encodeURIComponent(it.slug)) : '';
|
|
1309
|
+
var created = it.createdAt ? (' · ' + String(it.createdAt)) : '';
|
|
1310
|
+
var onchainMeta = (it.onchain && it.onchain.skillId) ? (' · onchain: skillId=' + String(it.onchain.skillId)) : '';
|
|
1311
|
+
var meta = ['slug: ' + (it.slug || '?'), 'v' + (it.version || '?')].join(' · ') + onchainMeta + created;
|
|
1312
|
+
var dlCmd = it.slug ? ('curl -fsSL \"' + apiBase + '/api/skills/' + encodeURIComponent(it.slug) + '\" -o \"./' + (it.fileName || it.slug + '.json') + '\"') : '';
|
|
1313
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
1314
|
+
var nft = v2SkillNft ? String(v2SkillNft.value || '').trim() : '';
|
|
1315
|
+
var reg = v2Registry ? String(v2Registry.value || '').trim() : '';
|
|
1316
|
+
var rr = royaltyReceiver ? String(royaltyReceiver.value || '').trim() : '';
|
|
1317
|
+
var rb = royaltyBps ? String(royaltyBps.value || '').trim() : '';
|
|
1318
|
+
var mintCmd = (rpc && nft && reg)
|
|
1319
|
+
? ('ape-claw v2 skill mint --rpc \"' + rpc + '\" --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --skillNft \"' + nft + '\" --registry \"' + reg + '\"' + (rr && rb ? (' --royalty-receiver \"' + rr + '\" --royalty-bps ' + rb) : '') + ' --json')
|
|
1320
|
+
: ('ape-claw v2 skill mint --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --skillNft <addr> --registry <addr> --json');
|
|
1321
|
+
var localFile = it.fileName || (it.slug ? it.slug + '.json' : '');
|
|
1322
|
+
var pubCmd = localFile
|
|
1323
|
+
? ((rpc && reg)
|
|
1324
|
+
? ('ape-claw v2 skill publish --rpc \"' + rpc + '\" --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --registry \"' + reg + '\" --skillId <id> --file \"./' + localFile + '\" --riskTier ' + String(Number(it.riskTier || 1)) + ' --json')
|
|
1325
|
+
: ('ape-claw v2 skill publish --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --registry <addr> --skillId <id> --file \"./' + localFile + '\" --riskTier ' + String(Number(it.riskTier || 1)) + ' --json'))
|
|
1326
|
+
: '';
|
|
1327
|
+
return (
|
|
1328
|
+
'<div class="item">' +
|
|
1329
|
+
'<div>' +
|
|
1330
|
+
'<strong>' + escapeHtml(it.name || it.slug || 'Skill') + '</strong> ' + pillForRisk(risk) +
|
|
1331
|
+
'<div class="meta">' + escapeHtml(meta) + '</div>' +
|
|
1332
|
+
(it.description ? ('<div class="meta">' + escapeHtml(String(it.description)) + '</div>') : '') +
|
|
1333
|
+
'</div>' +
|
|
1334
|
+
'<div class="links">' +
|
|
1335
|
+
(fileHref ? ('<a class="pill" href="' + escapeHtml(fileHref) + '" target="_blank" rel="noopener">JSON</a>') : '') +
|
|
1336
|
+
(fileHref ? ('<a class="pill" href="' + escapeHtml(fileHref) + '" download>Download</a>') : '') +
|
|
1337
|
+
(dlCmd ? ('<a class="pill" href="#" data-copy="' + escapeHtml(dlCmd) + '">Copy curl</a>') : '') +
|
|
1338
|
+
('<a class="pill" href="#" data-copy="' + escapeHtml(mintCmd) + '">Copy mint</a>') +
|
|
1339
|
+
(pubCmd ? ('<a class="pill" href="#" data-copy="' + escapeHtml(pubCmd) + '">Copy publish</a>') : '') +
|
|
1340
|
+
(it.fileName ? ('<a class="pill" href="#" data-mark="' + escapeHtml(String(it.fileName)) + '">Set onchain</a>') : '') +
|
|
1341
|
+
(it.fileName ? ('<a class="pill" href="#" data-delete="' + escapeHtml(String(it.fileName)) + '">Delete</a>') : '') +
|
|
1342
|
+
'</div>' +
|
|
1343
|
+
'</div>'
|
|
1344
|
+
);
|
|
1345
|
+
}).join(''));
|
|
1346
|
+
|
|
1347
|
+
// Attach copy/delete handlers.
|
|
1348
|
+
// Handlers are delegated globally (see below).
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function loadUserSkills() {
|
|
1352
|
+
fetch(apiBase + '/api/skillcards/user', { headers: { 'accept': 'application/json' } })
|
|
1353
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1354
|
+
.then(function (j) {
|
|
1355
|
+
userSkillsAll = (j && j.skills && Array.isArray(j.skills)) ? j.skills : [];
|
|
1356
|
+
renderUserSkills(userSkillsAll);
|
|
1357
|
+
})
|
|
1358
|
+
.catch(function () {
|
|
1359
|
+
if (userBadge) userBadge.textContent = 'NONE';
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function addUserSkill() {
|
|
1364
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
1365
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
1366
|
+
if (!(id && tok)) {
|
|
1367
|
+
flash('Set auth (x-agent-id/x-agent-token) to submit skills.', true);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
var parsed;
|
|
1371
|
+
try { parsed = parseSkillCardJson(); } catch (e) { flash(e.message || 'invalid', true); return; }
|
|
1372
|
+
if (containsSecretLikeText(skillJson ? skillJson.value : '')) {
|
|
1373
|
+
var ok = confirm('This SkillCard looks like it may contain secrets. SkillCards should be public. Continue anyway?');
|
|
1374
|
+
if (!ok) return;
|
|
1375
|
+
}
|
|
1376
|
+
var source = skillSourceUrl ? String(skillSourceUrl.value || '').trim() : '';
|
|
1377
|
+
fetch(apiBase + '/api/skillcards/user/add', {
|
|
1378
|
+
method: 'POST',
|
|
1379
|
+
headers: authHeaders(),
|
|
1380
|
+
body: JSON.stringify({ skillcard: parsed.obj, sourceUrl: source }),
|
|
1381
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
1382
|
+
.then(function (out) {
|
|
1383
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'submit failed');
|
|
1384
|
+
flash('Added: ' + (out.json.entry && out.json.entry.fileName ? out.json.entry.fileName : 'ok'));
|
|
1385
|
+
showToast('Skill added to library!');
|
|
1386
|
+
if (skillJson) skillJson.value = '';
|
|
1387
|
+
if (skillSourceUrl) skillSourceUrl.value = '';
|
|
1388
|
+
renderPreview();
|
|
1389
|
+
loadUserSkills();
|
|
1390
|
+
// Auto-open Step 4 (Your Submitted Skills) and update badge
|
|
1391
|
+
var step4 = document.querySelector('.step-card[data-step="4"]');
|
|
1392
|
+
if (step4) { step4.classList.add('open'); step4.scrollIntoView({behavior:'smooth',block:'nearest'}); }
|
|
1393
|
+
var submitBadge = document.getElementById('stepSubmitBadge');
|
|
1394
|
+
if (submitBadge) { submitBadge.textContent = 'DONE'; submitBadge.className = 'step-badge done'; }
|
|
1395
|
+
})
|
|
1396
|
+
.catch(function (e) {
|
|
1397
|
+
flash('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function deleteUserSkill(fileName) {
|
|
1402
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
1403
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
1404
|
+
if (!(id && tok)) {
|
|
1405
|
+
flash('Set auth (x-agent-id/x-agent-token) to delete skills.', true);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
fetch(apiBase + '/api/skillcards/user/delete', {
|
|
1409
|
+
method: 'POST',
|
|
1410
|
+
headers: authHeaders(),
|
|
1411
|
+
body: JSON.stringify({ fileName: fileName }),
|
|
1412
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
1413
|
+
.then(function (out) {
|
|
1414
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'delete failed');
|
|
1415
|
+
flash('Deleted: ' + fileName);
|
|
1416
|
+
loadUserSkills();
|
|
1417
|
+
})
|
|
1418
|
+
.catch(function (e) {
|
|
1419
|
+
flash('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function markOnchain(fileName) {
|
|
1424
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
1425
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
1426
|
+
if (!(id && tok)) {
|
|
1427
|
+
flash('Set auth (x-agent-id/x-agent-token) to mark onchain status.', true);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
openModal(
|
|
1431
|
+
'Set onchain status',
|
|
1432
|
+
'<div class="note">Record the onchain <code>skillId</code> (and optional tx hash) so the UI can display it. This does not execute any chain calls.</div>' +
|
|
1433
|
+
'<label>Skill ID (number)</label>' +
|
|
1434
|
+
'<input id="mSkillId" type="text" placeholder="e.g. 12">' +
|
|
1435
|
+
'<label>Tx hash (optional)</label>' +
|
|
1436
|
+
'<input id="mTxHash" type="text" placeholder="0x...">' +
|
|
1437
|
+
'<div class="note">File: <code>' + escapeHtml(fileName) + '</code></div>',
|
|
1438
|
+
function () {
|
|
1439
|
+
var elSid = document.getElementById('mSkillId');
|
|
1440
|
+
var elTx = document.getElementById('mTxHash');
|
|
1441
|
+
var sid = Number(String(elSid ? elSid.value : '').trim());
|
|
1442
|
+
if (!isFinite(sid) || sid <= 0) {
|
|
1443
|
+
showToast('Invalid skillId', true);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
var txHash = String(elTx ? elTx.value : '').trim();
|
|
1447
|
+
fetch(apiBase + '/api/skillcards/user/mark-onchain', {
|
|
1448
|
+
method: 'POST',
|
|
1449
|
+
headers: authHeaders(),
|
|
1450
|
+
body: JSON.stringify({ fileName: fileName, skillId: Math.floor(sid), txHash: txHash }),
|
|
1451
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
1452
|
+
.then(function (out) {
|
|
1453
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'mark failed');
|
|
1454
|
+
closeModal();
|
|
1455
|
+
showToast('Onchain set (skillId=' + Math.floor(sid) + ')');
|
|
1456
|
+
loadUserSkills();
|
|
1457
|
+
})
|
|
1458
|
+
.catch(function (e) {
|
|
1459
|
+
showToast('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
1460
|
+
});
|
|
1461
|
+
},
|
|
1462
|
+
function () {}
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// Step flow helpers for the Add tab
|
|
1467
|
+
window.toggleStep = function(headerEl) {
|
|
1468
|
+
var card = headerEl.closest('.step-card');
|
|
1469
|
+
if (!card) return;
|
|
1470
|
+
card.classList.toggle('open');
|
|
1471
|
+
};
|
|
1472
|
+
window.selectTemplate = function(kind, btnEl) {
|
|
1473
|
+
// Remove selected from siblings
|
|
1474
|
+
var grid = btnEl.closest('.template-grid');
|
|
1475
|
+
if (grid) {
|
|
1476
|
+
var btns = grid.querySelectorAll('.template-btn');
|
|
1477
|
+
for (var i = 0; i < btns.length; i++) btns[i].classList.remove('selected');
|
|
1478
|
+
}
|
|
1479
|
+
btnEl.classList.add('selected');
|
|
1480
|
+
// Load the template
|
|
1481
|
+
loadTemplate(kind);
|
|
1482
|
+
// Update badge
|
|
1483
|
+
var badge = document.getElementById('stepTemplateBadge');
|
|
1484
|
+
if (badge) { badge.textContent = kind.toUpperCase() + ' RISK'; badge.className = 'step-badge done'; }
|
|
1485
|
+
// Auto-open step 2
|
|
1486
|
+
var step2 = document.querySelector('.step-card[data-step="2"]');
|
|
1487
|
+
if (step2) { step2.classList.add('open'); step2.scrollIntoView({behavior:'smooth',block:'nearest'}); }
|
|
1488
|
+
var editBadge = document.getElementById('stepEditBadge');
|
|
1489
|
+
if (editBadge) { editBadge.textContent = 'EDIT'; editBadge.className = 'step-badge'; }
|
|
1490
|
+
};
|
|
1491
|
+
window.toggleCollapsible = function(toggleEl) {
|
|
1492
|
+
var content = toggleEl.nextElementSibling;
|
|
1493
|
+
if (!content) return;
|
|
1494
|
+
content.classList.toggle('show');
|
|
1495
|
+
var isOpen = content.classList.contains('show');
|
|
1496
|
+
toggleEl.innerHTML = isOpen ? 'Advanced: Contract Settings ▴' : 'Advanced: Contract Settings ▾';
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
loadAuth();
|
|
1500
|
+
loadV2Settings();
|
|
1501
|
+
renderIntentPreviews();
|
|
1502
|
+
renderReceiptPreview();
|
|
1503
|
+
autofillV2SettingsFromBackend();
|
|
1504
|
+
populateOnchainPanel();
|
|
1505
|
+
if (saveAuthBtn) saveAuthBtn.addEventListener('click', function(){ saveAuth(); });
|
|
1506
|
+
if (checkAuthBtn) checkAuthBtn.addEventListener('click', function(){ saveAuth(); checkAuth(); });
|
|
1507
|
+
if (clearAuthBtn) clearAuthBtn.addEventListener('click', function(){ clearAuth(); });
|
|
1508
|
+
if (validateSkillBtn) validateSkillBtn.addEventListener('click', function(){ renderPreview(); });
|
|
1509
|
+
if (addSkillBtn) addSkillBtn.addEventListener('click', function(){ saveAuth(); addUserSkill(); });
|
|
1510
|
+
if (saveV2SettingsBtn) saveV2SettingsBtn.addEventListener('click', function(){ saveV2Settings(); loadUserSkills(); });
|
|
1511
|
+
if (v2RpcUrl) v2RpcUrl.addEventListener('input', function(){ renderIntentPreviews(); renderReceiptPreview(); });
|
|
1512
|
+
if (v2Intents) v2Intents.addEventListener('input', renderIntentPreviews);
|
|
1513
|
+
if (v2Receipts) v2Receipts.addEventListener('input', renderReceiptPreview);
|
|
1514
|
+
if (intentPayload) intentPayload.addEventListener('input', renderIntentPreviews);
|
|
1515
|
+
if (intentExpiresAt) intentExpiresAt.addEventListener('input', renderIntentPreviews);
|
|
1516
|
+
if (intentCancelId) intentCancelId.addEventListener('input', renderIntentPreviews);
|
|
1517
|
+
if (copyIntentCreateBtn) copyIntentCreateBtn.addEventListener('click', function(){ copyText(getIntentCreateCmd()); showToast('Copied intent create'); });
|
|
1518
|
+
if (copyIntentCancelBtn) copyIntentCancelBtn.addEventListener('click', function(){ copyText(getIntentCancelCmd()); showToast('Copied intent cancel'); });
|
|
1519
|
+
if (receiptTraceId) receiptTraceId.addEventListener('input', renderReceiptPreview);
|
|
1520
|
+
if (copyReceiptGetBtn) copyReceiptGetBtn.addEventListener('click', function(){ copyText(getReceiptGetCmd()); showToast('Copied receipt get'); });
|
|
1521
|
+
if (fetchReceiptGetBtn) fetchReceiptGetBtn.addEventListener('click', function(){ fetchReceipt(); });
|
|
1522
|
+
if (formatJsonBtn) formatJsonBtn.addEventListener('click', function(){ formatJson(); });
|
|
1523
|
+
if (loadTemplateBtn) loadTemplateBtn.addEventListener('click', function(){
|
|
1524
|
+
var k = templateSelect ? String(templateSelect.value || '').trim() : '';
|
|
1525
|
+
if (!k) { flash('Choose a template first', true); return; }
|
|
1526
|
+
loadTemplate(k);
|
|
1527
|
+
});
|
|
1528
|
+
if (loadFromUrlBtn) loadFromUrlBtn.addEventListener('click', function(){ loadFromUrl(); });
|
|
1529
|
+
if (skillJson) skillJson.addEventListener('input', function(){ renderPreview(); });
|
|
1530
|
+
|
|
1531
|
+
if (userSkillSearch) {
|
|
1532
|
+
userSkillSearch.addEventListener('input', function () {
|
|
1533
|
+
var q = String(userSkillSearch.value || '');
|
|
1534
|
+
var filtered = userSkillsAll.filter(function (it) { return matchesUserSkill(it, q); });
|
|
1535
|
+
renderUserSkills(filtered);
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
loadUserSkills();
|
|
1539
|
+
checkAuth();
|
|
1540
|
+
|
|
1541
|
+
// Global delegated handlers for copy/delete/mark actions across all sections.
|
|
1542
|
+
document.addEventListener('click', function (ev) {
|
|
1543
|
+
// Fallback: card action buttons should still work even if per-card listeners fail.
|
|
1544
|
+
// We resolve the clicked action by walking up to the nearest .skill-card and using its data-slug.
|
|
1545
|
+
try {
|
|
1546
|
+
var actEl = ev && ev.target ? ev.target.closest('[data-action]') : null;
|
|
1547
|
+
if (actEl) {
|
|
1548
|
+
var action = actEl.getAttribute('data-action');
|
|
1549
|
+
var card = actEl.closest('.skill-card');
|
|
1550
|
+
var slug = card ? String(card.getAttribute('data-slug') || '').trim() : '';
|
|
1551
|
+
if (slug && (action === 'install' || action === 'json' || action === 'details')) {
|
|
1552
|
+
// If the skills list is still loading, don't intercept.
|
|
1553
|
+
// Let the browser follow the link (progressive enhancement).
|
|
1554
|
+
if (!importedAll || !importedAll.length) return;
|
|
1555
|
+
|
|
1556
|
+
// resolve item
|
|
1557
|
+
var it = null;
|
|
1558
|
+
for (var i = 0; i < importedAll.length; i++) {
|
|
1559
|
+
if (importedAll[i] && importedAll[i].slug === slug) { it = importedAll[i]; break; }
|
|
1560
|
+
}
|
|
1561
|
+
if (!it) return;
|
|
1562
|
+
|
|
1563
|
+
// Only prevent default if we can actually open the modal.
|
|
1564
|
+
var modalReady = false;
|
|
1565
|
+
try { modalReady = !!(modalBackdrop && modalTitle && modalBody); } catch (e) {}
|
|
1566
|
+
if (!modalReady) return;
|
|
1567
|
+
|
|
1568
|
+
ev.preventDefault();
|
|
1569
|
+
// keep URL shareable
|
|
1570
|
+
try {
|
|
1571
|
+
var enc = encodeURIComponent(slug);
|
|
1572
|
+
if (action === 'install') history.replaceState(null, '', '#install=' + enc);
|
|
1573
|
+
else if (action === 'json') history.replaceState(null, '', '#json=' + enc);
|
|
1574
|
+
else history.replaceState(null, '', '#skill=' + enc);
|
|
1575
|
+
} catch (e) {}
|
|
1576
|
+
|
|
1577
|
+
var pub = (publishedBySlug && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
1578
|
+
if (action === 'install') showInstallModal(it);
|
|
1579
|
+
else if (action === 'json') showJsonModal(it, pub);
|
|
1580
|
+
else openDetails(it, pub, '');
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
} catch (e) {}
|
|
1585
|
+
|
|
1586
|
+
var el = ev && ev.target ? ev.target.closest('[data-copy]') : null;
|
|
1587
|
+
if (el) {
|
|
1588
|
+
ev.preventDefault();
|
|
1589
|
+
copyText(el.getAttribute('data-copy'));
|
|
1590
|
+
showToast('Copied to clipboard');
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
var a = ev && ev.target ? ev.target.closest('a') : null;
|
|
1594
|
+
if (!a) return;
|
|
1595
|
+
var txt = a.getAttribute('data-copy');
|
|
1596
|
+
if (txt) {
|
|
1597
|
+
ev.preventDefault();
|
|
1598
|
+
copyText(txt);
|
|
1599
|
+
showToast('Copied to clipboard');
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
var del = a.getAttribute('data-delete');
|
|
1603
|
+
if (del) {
|
|
1604
|
+
ev.preventDefault();
|
|
1605
|
+
openModal(
|
|
1606
|
+
'Delete submitted skill',
|
|
1607
|
+
'<div class="note">This removes the stored SkillCard JSON from the library.</div>' +
|
|
1608
|
+
'<div class="danger-note" style="margin-top:10px">Delete: <code>' + escapeHtml(del) + '</code></div>',
|
|
1609
|
+
function () { closeModal(); deleteUserSkill(del); },
|
|
1610
|
+
function () {}
|
|
1611
|
+
);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
var mark = a.getAttribute('data-mark');
|
|
1615
|
+
if (mark) {
|
|
1616
|
+
ev.preventDefault();
|
|
1617
|
+
markOnchain(mark);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
})();
|