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/skills.html
ADDED
|
@@ -0,0 +1,2879 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Skills — ApeClaw</title>
|
|
7
|
+
<meta name="description" content="ApeClaw Skills: SkillCards, SkillNFT provenance, immutable versions, receipts, and Pod revenue sharing (royalties to PodVault).">
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/ui/favicon.svg">
|
|
9
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/ui/favicon-32.png">
|
|
10
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/ui/favicon-180.png">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
14
|
+
<link rel="stylesheet" href="/ui/shared/sidebar-nav.css">
|
|
15
|
+
<script defer src="/ui/shared/sidebar-nav.js"></script>
|
|
16
|
+
<link rel="stylesheet" href="/ui/shared/motion.css">
|
|
17
|
+
<script defer src="/ui/shared/motion.js"></script>
|
|
18
|
+
<style>
|
|
19
|
+
:root{
|
|
20
|
+
--bg:#0c0c0c;
|
|
21
|
+
--panel:#1a1a1a;
|
|
22
|
+
--panel-2:#212121;
|
|
23
|
+
--panel-hover:#2a2a2a;
|
|
24
|
+
--panel-border:rgba(207,255,4,.75);
|
|
25
|
+
--panel-border-soft:rgba(207,255,4,.35);
|
|
26
|
+
--glow:rgba(207,255,4,.22);
|
|
27
|
+
--muted:#a6a6a6;
|
|
28
|
+
--text:#e6e6e6;
|
|
29
|
+
--amber:#cfff04;
|
|
30
|
+
--amber-soft:rgba(207,255,4,.75);
|
|
31
|
+
--green:#00ff00;
|
|
32
|
+
--cyan:#63d7ff;
|
|
33
|
+
--danger:#ff3333;
|
|
34
|
+
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
35
|
+
--font-display:'Outfit','Inter','Segoe UI',sans-serif;
|
|
36
|
+
--terminal-gray:#474546;
|
|
37
|
+
--terminal-lightgray:#9e9e9e;
|
|
38
|
+
--accent:#cfff04;
|
|
39
|
+
--accent-soft:rgba(207,255,4,.75);
|
|
40
|
+
--gray:#474546;
|
|
41
|
+
--lightgray:#aaa;
|
|
42
|
+
}
|
|
43
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
44
|
+
html{scroll-behavior:smooth}
|
|
45
|
+
body{
|
|
46
|
+
min-height:100vh;
|
|
47
|
+
font-family:'Outfit','Inter','Segoe UI',sans-serif;
|
|
48
|
+
background:var(--bg);
|
|
49
|
+
color:var(--text);
|
|
50
|
+
position:relative;
|
|
51
|
+
overflow-x:hidden;
|
|
52
|
+
-webkit-font-smoothing:antialiased;
|
|
53
|
+
letter-spacing:.03em;
|
|
54
|
+
}
|
|
55
|
+
body::before{
|
|
56
|
+
content:"";
|
|
57
|
+
position:fixed;
|
|
58
|
+
inset:0;
|
|
59
|
+
pointer-events:none;
|
|
60
|
+
z-index:0;
|
|
61
|
+
background:
|
|
62
|
+
radial-gradient(900px 500px at 20% 10%, rgba(207,255,4,0.10) 0%, transparent 60%),
|
|
63
|
+
radial-gradient(700px 380px at 85% 75%, rgba(0,255,0,0.06) 0%, transparent 55%),
|
|
64
|
+
linear-gradient(180deg, rgba(0,0,0,0.35), rgba(0,0,0,0.85));
|
|
65
|
+
}
|
|
66
|
+
.noise{
|
|
67
|
+
position:fixed;inset:0;pointer-events:none;z-index:2;opacity:0.07;mix-blend-mode:overlay;
|
|
68
|
+
background-image:
|
|
69
|
+
repeating-linear-gradient(0deg, rgba(255,255,255,0.06) 0px, rgba(255,255,255,0.06) 1px, rgba(0,0,0,0) 2px, rgba(0,0,0,0) 4px),
|
|
70
|
+
repeating-linear-gradient(90deg, rgba(255,255,255,0.04) 0px, rgba(255,255,255,0.04) 1px, rgba(0,0,0,0) 2px, rgba(0,0,0,0) 6px);
|
|
71
|
+
}
|
|
72
|
+
.scanlines{overflow:hidden;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:1}
|
|
73
|
+
.scanlines:before,.scanlines:after{display:block;pointer-events:none;content:"";position:absolute}
|
|
74
|
+
.scanlines:before{width:100%;height:2px;z-index:2147483649;background:rgba(0,0,0,0.3);opacity:0.75;animation:scanline 6s linear infinite}
|
|
75
|
+
.scanlines:after{top:0;right:0;bottom:0;left:0;z-index:2147483648;background:linear-gradient(to bottom, transparent 50%, rgba(0,0,0,0.3) 51%);background-size:100% 4px;animation:scanlines 1s steps(60) infinite}
|
|
76
|
+
@keyframes scanline{0%{transform:translate3d(0,200000%,0)}}
|
|
77
|
+
@keyframes scanlines{0%{background-position:0 200000%}}
|
|
78
|
+
:focus-visible{outline:2px dashed var(--accent);outline-offset:3px}
|
|
79
|
+
|
|
80
|
+
/* Disable the older collage background (previous iteration). */
|
|
81
|
+
.bg-collage, .bg-fade{display:none !important}
|
|
82
|
+
.bg-collage{
|
|
83
|
+
position:fixed;
|
|
84
|
+
inset:0;
|
|
85
|
+
z-index:0;
|
|
86
|
+
opacity:0.12;
|
|
87
|
+
pointer-events:none;
|
|
88
|
+
display:grid;
|
|
89
|
+
grid-template-columns:repeat(10,1fr);
|
|
90
|
+
gap:5px;
|
|
91
|
+
padding:5px;
|
|
92
|
+
}
|
|
93
|
+
.bg-collage img{
|
|
94
|
+
width:100%;
|
|
95
|
+
aspect-ratio:1/1;
|
|
96
|
+
object-fit:cover;
|
|
97
|
+
border-radius:3px;
|
|
98
|
+
image-rendering:pixelated;
|
|
99
|
+
filter:saturate(0.7) contrast(0.85) brightness(0.9);
|
|
100
|
+
transform:rotate(var(--r));
|
|
101
|
+
}
|
|
102
|
+
.bg-fade{
|
|
103
|
+
position:fixed;inset:0;z-index:1;pointer-events:none;
|
|
104
|
+
background:
|
|
105
|
+
radial-gradient(ellipse 90% 50% at 50% 0%, rgba(2,3,5,0.15) 0%, rgba(2,3,5,0.95) 100%),
|
|
106
|
+
linear-gradient(180deg, rgba(2,3,5,0.1) 0%, rgba(2,3,5,0.7) 40%, rgba(2,3,5,1) 100%);
|
|
107
|
+
}
|
|
108
|
+
a{color:inherit;text-decoration:none}
|
|
109
|
+
a:hover{text-decoration:underline}
|
|
110
|
+
|
|
111
|
+
.wrap{position:relative;z-index:3;max-width:1180px;margin:0 auto;padding:20px 20px 28px}
|
|
112
|
+
|
|
113
|
+
.hero{position:relative;overflow:hidden;padding:64px 24px 48px;text-align:center}
|
|
114
|
+
@media(min-width:640px){.hero{padding:80px 40px 56px}}
|
|
115
|
+
.orb{position:absolute;border-radius:50%;pointer-events:none;opacity:.15}
|
|
116
|
+
.orb.o1{width:300px;height:300px;top:-80px;left:-60px;background:radial-gradient(circle,rgba(207,255,4,.3),transparent 70%);animation:drift1 20s ease-in-out infinite}
|
|
117
|
+
.orb.o2{width:200px;height:200px;bottom:-40px;right:-30px;background:radial-gradient(circle,rgba(0,255,255,.2),transparent 70%);animation:drift2 25s ease-in-out infinite}
|
|
118
|
+
.orb.o3{width:150px;height:150px;top:40%;left:60%;background:radial-gradient(circle,rgba(207,255,4,.15),transparent 70%);animation:drift3 18s ease-in-out infinite}
|
|
119
|
+
@keyframes drift1{0%,100%{transform:translate(0,0)}50%{transform:translate(30px,20px)}}
|
|
120
|
+
@keyframes drift2{0%,100%{transform:translate(0,0)}50%{transform:translate(-20px,-15px)}}
|
|
121
|
+
@keyframes drift3{0%,100%{transform:translate(0,0)}50%{transform:translate(15px,-25px)}}
|
|
122
|
+
.hero-title{font-size:clamp(28px,6vw,52px);font-weight:900;color:#fff;text-transform:uppercase;letter-spacing:-.01em;line-height:1.05;max-width:900px;margin:0 auto}
|
|
123
|
+
.hero-title .glow{color:var(--accent);text-shadow:0 0 24px rgba(207,255,4,.2)}
|
|
124
|
+
.hero-sub{margin:20px auto 0;max-width:620px;color:var(--lightgray);font-size:14px;line-height:1.7}
|
|
125
|
+
.hero-cta{display:flex;flex-direction:column;align-items:center;gap:12px;margin-top:32px}
|
|
126
|
+
@media(min-width:480px){.hero-cta{flex-direction:row;justify-content:center;flex-wrap:wrap}}
|
|
127
|
+
.eyebrow{font-size:10px;font-weight:800;color:var(--accent);letter-spacing:.2em;text-transform:uppercase;margin-bottom:12px}
|
|
128
|
+
.cta{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}
|
|
129
|
+
.btn{
|
|
130
|
+
display:inline-flex;align-items:center;justify-content:center;gap:8px;
|
|
131
|
+
padding:10px 18px;border-radius:6px;border:1px solid rgba(207,255,4,.15);
|
|
132
|
+
background:rgba(14,20,30,.8);color:#f4f7fc;font-weight:700;font-size:13px;
|
|
133
|
+
letter-spacing:.04em;text-transform:uppercase;cursor:pointer;
|
|
134
|
+
transition:all .3s ease;white-space:nowrap;
|
|
135
|
+
}
|
|
136
|
+
.btn:hover{border-color:var(--accent-soft);background:rgba(207,255,4,.06);text-decoration:none;box-shadow:0 0 20px rgba(207,255,4,.06)}
|
|
137
|
+
.btn.primary{
|
|
138
|
+
border-color:#8a5b12;background:linear-gradient(180deg,#ffb64a 0%,#ff9f1a 100%);
|
|
139
|
+
color:#1b1204;box-shadow:inset 0 0 0 1px rgba(255,231,191,0.28);
|
|
140
|
+
}
|
|
141
|
+
.btn.primary:hover{background:linear-gradient(180deg,#ffc168 0%,#ffa92f 100%);box-shadow:inset 0 0 0 1px rgba(255,240,210,0.34)}
|
|
142
|
+
|
|
143
|
+
.pill{
|
|
144
|
+
display:inline-flex;align-items:center;gap:8px;
|
|
145
|
+
padding:7px 10px;
|
|
146
|
+
border-radius:999px;
|
|
147
|
+
border:1px solid rgba(207,255,4,.22);
|
|
148
|
+
background:rgba(9,13,18,0.6);
|
|
149
|
+
color:var(--muted);
|
|
150
|
+
font-size:12px;
|
|
151
|
+
letter-spacing:.02em;
|
|
152
|
+
}
|
|
153
|
+
.dot{width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 10px rgba(207,255,4,.35)}
|
|
154
|
+
.scroll{margin-top:14px;color:var(--muted);font-size:12px;display:inline-flex;align-items:center;gap:8px;letter-spacing:.04em;text-transform:uppercase}
|
|
155
|
+
.scroll:hover{text-decoration:none;color:var(--text)}
|
|
156
|
+
.scroll .chev{color:var(--accent-soft)}
|
|
157
|
+
|
|
158
|
+
.stats{display:grid;grid-template-columns:1fr;gap:14px;margin-top:16px}
|
|
159
|
+
@media(min-width:860px){.stats{grid-template-columns:repeat(4,1fr)}}
|
|
160
|
+
.stat{
|
|
161
|
+
background:linear-gradient(145deg,rgba(28,28,28,.7) 0%,rgba(14,14,14,.85) 100%);
|
|
162
|
+
border:1px solid rgba(207,255,4,.1);
|
|
163
|
+
border-radius:8px;
|
|
164
|
+
padding:18px 16px 14px;
|
|
165
|
+
min-height:86px;
|
|
166
|
+
transition:border-color .3s,transform .3s;
|
|
167
|
+
}
|
|
168
|
+
.stat:hover{border-color:rgba(207,255,4,.3);transform:translateY(-2px)}
|
|
169
|
+
.stat .num{font-weight:900;font-size:28px;color:#fff;letter-spacing:-.01em;font-family:var(--mono)}
|
|
170
|
+
.stat .label{margin-top:6px;color:var(--accent);font-size:12px;line-height:1.4;text-transform:uppercase;letter-spacing:.15em}
|
|
171
|
+
|
|
172
|
+
.section{margin-top:22px}
|
|
173
|
+
.section h2{margin:0 0 16px;font-size:22px;font-weight:900;color:#fff;text-transform:uppercase;letter-spacing:-.01em;text-align:center}
|
|
174
|
+
.grid{display:grid;grid-template-columns:1fr;gap:14px;margin-bottom:14px}
|
|
175
|
+
@media(min-width:860px){.grid{grid-template-columns:1fr 1fr}}
|
|
176
|
+
.card{
|
|
177
|
+
background:linear-gradient(145deg,rgba(28,28,28,.85) 0%,rgba(14,14,14,.95) 100%);
|
|
178
|
+
border:1px solid rgba(207,255,4,.1);
|
|
179
|
+
border-radius:8px;
|
|
180
|
+
padding:22px 22px 18px;
|
|
181
|
+
transition:border-color .35s,box-shadow .35s,transform .35s;
|
|
182
|
+
}
|
|
183
|
+
.card:hover{border-color:rgba(207,255,4,.35);box-shadow:0 0 30px rgba(207,255,4,.06),0 8px 24px rgba(0,0,0,.3);transform:translateY(-3px)}
|
|
184
|
+
.card[data-risk="low"],.card.risk-low{border-left:3px solid #4caf50}
|
|
185
|
+
.card[data-risk="medium"],.card.risk-med{border-left:3px solid #ff9800}
|
|
186
|
+
.card[data-risk="high"],.card.risk-high{border-left:3px solid #f44336}
|
|
187
|
+
.card[data-risk="unknown"],.card.risk-unknown{border-left:3px solid #666}
|
|
188
|
+
.cardhead{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
|
189
|
+
.card h3{margin:0;font-size:13px;font-weight:700;color:var(--accent-soft);text-transform:uppercase;letter-spacing:.06em}
|
|
190
|
+
.badge{
|
|
191
|
+
font-size:10px;padding:5px 10px;border-radius:5px;border:1px solid rgba(207,255,4,.15);
|
|
192
|
+
color:var(--muted);background:rgba(9,13,18,0.7);white-space:nowrap;text-transform:uppercase;letter-spacing:.06em;font-weight:700;
|
|
193
|
+
}
|
|
194
|
+
.badge.alpha{border-color:rgba(207,255,4,.25);color:var(--accent-soft)}
|
|
195
|
+
.badge.live{border-color:rgba(0,255,0,.25);color:rgba(0,255,0,.8)}
|
|
196
|
+
.badge.found{border-color:rgba(99,215,255,.28);color:rgba(99,215,255,.85)}
|
|
197
|
+
.card p{margin:12px 0 0;color:var(--muted);font-size:13px;line-height:1.65}
|
|
198
|
+
.code{
|
|
199
|
+
margin-top:12px;padding:14px 14px;border-radius:6px;
|
|
200
|
+
border:1px solid rgba(207,255,4,.08);background:rgba(8,16,26,.85);
|
|
201
|
+
font-family:var(--mono);font-size:12px;color:#d4e6fa;overflow:auto;line-height:1.55;
|
|
202
|
+
white-space:pre-wrap;word-break:break-word;
|
|
203
|
+
}
|
|
204
|
+
.k{color:var(--cyan)}
|
|
205
|
+
|
|
206
|
+
.list{margin-top:10px;display:flex;flex-direction:column;gap:10px}
|
|
207
|
+
.item{
|
|
208
|
+
border:1px solid #2a4056;background:#0b1622;border-radius:2px;
|
|
209
|
+
padding:12px;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
|
|
210
|
+
}
|
|
211
|
+
.item strong{font-size:13px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--accent-soft)}
|
|
212
|
+
.item .meta{color:var(--muted);font-size:12px;line-height:1.4;margin-top:6px}
|
|
213
|
+
.item .links{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
|
|
214
|
+
.pill{
|
|
215
|
+
font-size:11px;padding:6px 8px;border-radius:2px;border:1px solid #2a4056;
|
|
216
|
+
color:#d4e6fa;background:rgba(9,13,18,0.5);white-space:nowrap;text-transform:uppercase;letter-spacing:.04em;
|
|
217
|
+
}
|
|
218
|
+
.pill.low{border-color:rgba(99,215,255,.35)}
|
|
219
|
+
.pill.med{border-color:rgba(207,255,4,.22)}
|
|
220
|
+
.pill.high{border-color:rgba(255,51,51,.28);color:#ffd1d1}
|
|
221
|
+
.tools{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:10px}
|
|
222
|
+
.tools input{
|
|
223
|
+
background:rgba(11,22,34,.8);border:1px solid rgba(207,255,4,.12);color:#d4e6fa;padding:9px 12px;border-radius:6px;
|
|
224
|
+
font-size:13px;min-width:260px;outline:none;transition:border-color .3s,box-shadow .3s;
|
|
225
|
+
}
|
|
226
|
+
.tools input:focus{border-color:var(--accent-soft);box-shadow:0 0 16px rgba(207,255,4,.08)}
|
|
227
|
+
.tools input::placeholder{color:rgba(166,166,166,.95)}
|
|
228
|
+
.tools textarea{
|
|
229
|
+
width:100%;
|
|
230
|
+
background:rgba(11,22,34,.8);
|
|
231
|
+
border:1px solid rgba(207,255,4,.12);
|
|
232
|
+
color:#d4e6fa;
|
|
233
|
+
padding:9px 12px;
|
|
234
|
+
border-radius:6px;
|
|
235
|
+
font-size:13px;
|
|
236
|
+
outline:none;
|
|
237
|
+
font-family:var(--mono);
|
|
238
|
+
line-height:1.5;
|
|
239
|
+
min-height:160px;
|
|
240
|
+
resize:vertical;
|
|
241
|
+
transition:border-color .3s,box-shadow .3s;
|
|
242
|
+
}
|
|
243
|
+
.tools textarea:focus{border-color:var(--accent-soft);box-shadow:0 0 16px rgba(207,255,4,.08)}
|
|
244
|
+
.tools .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;width:100%}
|
|
245
|
+
.note{
|
|
246
|
+
margin-top:10px;
|
|
247
|
+
color:var(--muted);
|
|
248
|
+
font-size:12px;
|
|
249
|
+
line-height:1.55;
|
|
250
|
+
}
|
|
251
|
+
.note code{font-family:var(--mono);font-size:12px;color:#d4e6fa}
|
|
252
|
+
.danger-note{
|
|
253
|
+
margin-top:12px;
|
|
254
|
+
border:1px solid rgba(255,51,51,.25);
|
|
255
|
+
background:rgba(255,51,51,.06);
|
|
256
|
+
border-radius:2px;
|
|
257
|
+
padding:12px;
|
|
258
|
+
color:#ffd1d1;
|
|
259
|
+
font-size:12.5px;
|
|
260
|
+
line-height:1.65;
|
|
261
|
+
}
|
|
262
|
+
.toast{
|
|
263
|
+
position:fixed;
|
|
264
|
+
left:12px;
|
|
265
|
+
right:12px;
|
|
266
|
+
bottom:12px;
|
|
267
|
+
z-index:50;
|
|
268
|
+
display:none;
|
|
269
|
+
border:1px solid var(--panel-border);
|
|
270
|
+
background:rgba(9,13,18,0.92);
|
|
271
|
+
border-radius:2px;
|
|
272
|
+
padding:10px 12px;
|
|
273
|
+
color:#d4e6fa;
|
|
274
|
+
font-size:12.5px;
|
|
275
|
+
line-height:1.4;
|
|
276
|
+
box-shadow:0 10px 36px rgba(0,0,0,0.45);
|
|
277
|
+
}
|
|
278
|
+
.toast.show{display:block}
|
|
279
|
+
.toast.err{border-color:rgba(255,51,51,.35);color:#ffd1d1}
|
|
280
|
+
|
|
281
|
+
.modal-backdrop{
|
|
282
|
+
position:fixed;inset:0;z-index:60;
|
|
283
|
+
background:rgba(0,0,0,0.65);
|
|
284
|
+
display:none;
|
|
285
|
+
padding:18px;
|
|
286
|
+
}
|
|
287
|
+
.modal-backdrop.show{display:flex;align-items:center;justify-content:center}
|
|
288
|
+
.modal{
|
|
289
|
+
width:min(720px, 100%);
|
|
290
|
+
border:1px solid rgba(207,255,4,.2);
|
|
291
|
+
background:linear-gradient(145deg,rgba(28,28,28,.97) 0%,rgba(14,14,14,.99) 100%);
|
|
292
|
+
border-radius:10px;
|
|
293
|
+
box-shadow:0 30px 80px rgba(0,0,0,0.7),0 0 60px rgba(207,255,4,.04);
|
|
294
|
+
padding:20px;
|
|
295
|
+
backdrop-filter:blur(12px);
|
|
296
|
+
}
|
|
297
|
+
.modal h3{
|
|
298
|
+
margin:0;
|
|
299
|
+
font-size:13px;
|
|
300
|
+
font-weight:900;
|
|
301
|
+
letter-spacing:.06em;
|
|
302
|
+
text-transform:uppercase;
|
|
303
|
+
color:var(--accent-soft);
|
|
304
|
+
}
|
|
305
|
+
.modal .body{margin-top:12px}
|
|
306
|
+
.modal .body .note{margin-top:8px}
|
|
307
|
+
.modal .footer{margin-top:14px;display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}
|
|
308
|
+
.modal input{
|
|
309
|
+
width:100%;
|
|
310
|
+
background:#0b1622;border:1px solid #2a4056;color:#d4e6fa;
|
|
311
|
+
padding:9px 10px;border-radius:2px;font-size:13px;outline:none;
|
|
312
|
+
}
|
|
313
|
+
.modal input:focus{border-color:var(--cyan)}
|
|
314
|
+
.modal label{
|
|
315
|
+
display:block;
|
|
316
|
+
margin-top:10px;
|
|
317
|
+
font-size:11px;
|
|
318
|
+
color:var(--muted);
|
|
319
|
+
letter-spacing:.04em;
|
|
320
|
+
text-transform:uppercase;
|
|
321
|
+
}
|
|
322
|
+
.tools select{
|
|
323
|
+
background:rgba(11,22,34,.8);border:1px solid rgba(207,255,4,.12);color:#d4e6fa;padding:9px 12px;border-radius:6px;
|
|
324
|
+
font-size:13px;outline:none;transition:border-color .3s;
|
|
325
|
+
}
|
|
326
|
+
.tools select:focus{border-color:var(--accent-soft)}
|
|
327
|
+
.tools label{
|
|
328
|
+
display:inline-flex;align-items:center;gap:8px;
|
|
329
|
+
border:1px solid #2a4056;background:rgba(9,13,18,0.5);
|
|
330
|
+
padding:7px 9px;border-radius:2px;
|
|
331
|
+
font-size:12px;color:#d4e6fa;text-transform:uppercase;letter-spacing:.04em;
|
|
332
|
+
user-select:none;
|
|
333
|
+
}
|
|
334
|
+
.tools input[type="checkbox"]{width:14px;height:14px;min-width:0;accent-color:var(--accent)}
|
|
335
|
+
|
|
336
|
+
/* ── Add Tab step flow ── */
|
|
337
|
+
.step-flow{display:flex;flex-direction:column;gap:0;margin-top:16px}
|
|
338
|
+
.step-card{
|
|
339
|
+
background:linear-gradient(180deg,var(--panel-2) 0%,var(--panel) 100%);
|
|
340
|
+
border:1px solid rgba(207,255,4,.15);
|
|
341
|
+
border-radius:8px;
|
|
342
|
+
padding:0;
|
|
343
|
+
overflow:hidden;
|
|
344
|
+
margin-bottom:14px;
|
|
345
|
+
transition:border-color .3s;
|
|
346
|
+
}
|
|
347
|
+
.step-card:hover{border-color:rgba(207,255,4,.35)}
|
|
348
|
+
.step-header{
|
|
349
|
+
display:flex;align-items:center;gap:14px;
|
|
350
|
+
padding:18px 20px;
|
|
351
|
+
cursor:pointer;
|
|
352
|
+
user-select:none;
|
|
353
|
+
}
|
|
354
|
+
.step-num{
|
|
355
|
+
width:32px;height:32px;border-radius:50%;
|
|
356
|
+
display:grid;place-items:center;
|
|
357
|
+
border:2px solid var(--accent);
|
|
358
|
+
color:var(--accent);
|
|
359
|
+
font-weight:900;font-size:14px;
|
|
360
|
+
flex-shrink:0;
|
|
361
|
+
transition:background .3s,color .3s;
|
|
362
|
+
}
|
|
363
|
+
.step-card.active .step-num{background:var(--accent);color:#111}
|
|
364
|
+
.step-title{font-size:14px;font-weight:800;color:var(--text);text-transform:uppercase;letter-spacing:.04em;flex:1}
|
|
365
|
+
.step-badge{font-size:10px;padding:4px 8px;border-radius:999px;border:1px solid rgba(207,255,4,.2);color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
366
|
+
.step-badge.done{border-color:rgba(0,255,0,.3);color:rgba(0,255,0,.8)}
|
|
367
|
+
.step-chevron{color:var(--muted);font-size:18px;transition:transform .3s}
|
|
368
|
+
.step-card.open .step-chevron{transform:rotate(180deg)}
|
|
369
|
+
.step-body{
|
|
370
|
+
display:none;
|
|
371
|
+
padding:0 20px 20px;
|
|
372
|
+
}
|
|
373
|
+
.step-card.open .step-body{display:block}
|
|
374
|
+
|
|
375
|
+
.template-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:12px}
|
|
376
|
+
@media(max-width:640px){.template-grid{grid-template-columns:1fr}}
|
|
377
|
+
.template-btn{
|
|
378
|
+
background:rgba(11,22,34,.6);
|
|
379
|
+
border:1px solid rgba(207,255,4,.12);
|
|
380
|
+
border-radius:8px;
|
|
381
|
+
padding:16px;
|
|
382
|
+
cursor:pointer;
|
|
383
|
+
text-align:left;
|
|
384
|
+
transition:all .25s;
|
|
385
|
+
}
|
|
386
|
+
.template-btn:hover{border-color:rgba(207,255,4,.4);background:rgba(207,255,4,.04);transform:translateY(-2px)}
|
|
387
|
+
.template-btn.selected{border-color:var(--accent);background:rgba(207,255,4,.08);box-shadow:0 0 20px rgba(207,255,4,.1)}
|
|
388
|
+
.template-btn .t-risk{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px}
|
|
389
|
+
.template-btn .t-risk.low{color:#4caf50}
|
|
390
|
+
.template-btn .t-risk.med{color:#ff9800}
|
|
391
|
+
.template-btn .t-risk.high{color:#f44336}
|
|
392
|
+
.template-btn .t-name{font-size:13px;font-weight:700;color:var(--text);margin-bottom:4px}
|
|
393
|
+
.template-btn .t-desc{font-size:11px;color:var(--muted);line-height:1.5}
|
|
394
|
+
|
|
395
|
+
.collapsible-toggle{
|
|
396
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
397
|
+
background:none;border:1px solid rgba(207,255,4,.12);
|
|
398
|
+
color:var(--muted);font-size:11px;padding:6px 12px;
|
|
399
|
+
border-radius:4px;cursor:pointer;text-transform:uppercase;letter-spacing:.04em;
|
|
400
|
+
transition:all .2s;margin-top:8px;
|
|
401
|
+
}
|
|
402
|
+
.collapsible-toggle:hover{border-color:rgba(207,255,4,.3);color:var(--text)}
|
|
403
|
+
.collapsible-content{display:none;margin-top:10px}
|
|
404
|
+
.collapsible-content.show{display:block}
|
|
405
|
+
|
|
406
|
+
/* ── 3D Holographic Skill Cards ── */
|
|
407
|
+
.cards{
|
|
408
|
+
margin-top:16px;
|
|
409
|
+
display:grid;
|
|
410
|
+
grid-template-columns:1fr;
|
|
411
|
+
gap:20px;
|
|
412
|
+
perspective:1200px;
|
|
413
|
+
}
|
|
414
|
+
@media(min-width:640px){.cards{grid-template-columns:repeat(2,1fr)}}
|
|
415
|
+
@media(min-width:1000px){.cards{grid-template-columns:repeat(3,1fr)}}
|
|
416
|
+
@media(min-width:1000px){
|
|
417
|
+
.cards .skill-card:last-child:nth-child(3n+1){grid-column:1 / -1}
|
|
418
|
+
}
|
|
419
|
+
@media(min-width:640px) and (max-width:999px){
|
|
420
|
+
.cards .skill-card:last-child:nth-child(2n+1){grid-column:1 / -1}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.skill-card{
|
|
424
|
+
--glow-color:207,255,4;
|
|
425
|
+
--glow-a:.12;
|
|
426
|
+
position:relative;
|
|
427
|
+
background:
|
|
428
|
+
linear-gradient(168deg,rgba(22,26,30,.96) 0%,rgba(12,12,14,.98) 55%,rgba(16,20,24,.97) 100%);
|
|
429
|
+
border:1px solid rgba(var(--glow-color),.10);
|
|
430
|
+
border-radius:14px;
|
|
431
|
+
padding:0;
|
|
432
|
+
overflow:hidden;
|
|
433
|
+
display:flex;
|
|
434
|
+
flex-direction:column;
|
|
435
|
+
min-height:290px;
|
|
436
|
+
transition:
|
|
437
|
+
transform .5s cubic-bezier(.22,.68,0,1.2),
|
|
438
|
+
box-shadow .5s cubic-bezier(.22,.68,0,1),
|
|
439
|
+
border-color .4s ease;
|
|
440
|
+
transform-style:preserve-3d;
|
|
441
|
+
will-change:transform,box-shadow;
|
|
442
|
+
box-shadow:
|
|
443
|
+
0 2px 8px rgba(0,0,0,.4),
|
|
444
|
+
0 0 1px rgba(var(--glow-color),.15),
|
|
445
|
+
inset 0 1px 0 rgba(255,255,255,.03);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.skill-card::before{
|
|
449
|
+
content:'';position:absolute;inset:-1px;border-radius:15px;
|
|
450
|
+
background:conic-gradient(
|
|
451
|
+
from var(--border-angle,0deg),
|
|
452
|
+
transparent 0%,
|
|
453
|
+
rgba(var(--glow-color),.4) 8%,
|
|
454
|
+
transparent 16%,
|
|
455
|
+
rgba(99,215,255,.3) 25%,
|
|
456
|
+
transparent 33%,
|
|
457
|
+
rgba(var(--glow-color),.2) 50%,
|
|
458
|
+
transparent 58%,
|
|
459
|
+
rgba(99,215,255,.15) 66%,
|
|
460
|
+
transparent 75%,
|
|
461
|
+
rgba(var(--glow-color),.3) 90%,
|
|
462
|
+
transparent 100%
|
|
463
|
+
);
|
|
464
|
+
opacity:0;
|
|
465
|
+
transition:opacity .5s ease;
|
|
466
|
+
pointer-events:none;
|
|
467
|
+
z-index:-1;
|
|
468
|
+
animation:borderSpin 6s linear infinite;
|
|
469
|
+
}
|
|
470
|
+
.skill-card:hover::before{opacity:1}
|
|
471
|
+
@property --border-angle{syntax:'<angle>';initial-value:0deg;inherits:false}
|
|
472
|
+
@keyframes borderSpin{to{--border-angle:360deg}}
|
|
473
|
+
|
|
474
|
+
.skill-card::after{
|
|
475
|
+
content:'';position:absolute;inset:0;border-radius:14px;
|
|
476
|
+
background:linear-gradient(
|
|
477
|
+
135deg,
|
|
478
|
+
rgba(var(--glow-color),.06) 0%,
|
|
479
|
+
transparent 35%,
|
|
480
|
+
transparent 65%,
|
|
481
|
+
rgba(99,215,255,.04) 100%
|
|
482
|
+
);
|
|
483
|
+
opacity:0;
|
|
484
|
+
transition:opacity .4s ease;
|
|
485
|
+
pointer-events:none;z-index:1;
|
|
486
|
+
}
|
|
487
|
+
.skill-card:hover::after{opacity:1}
|
|
488
|
+
|
|
489
|
+
.skill-card:hover{
|
|
490
|
+
transform:translateY(-12px) scale(1.02);
|
|
491
|
+
border-color:rgba(var(--glow-color),.35);
|
|
492
|
+
box-shadow:
|
|
493
|
+
0 30px 60px -10px rgba(0,0,0,.6),
|
|
494
|
+
0 0 40px -5px rgba(var(--glow-color),.12),
|
|
495
|
+
0 0 80px -10px rgba(var(--glow-color),.06),
|
|
496
|
+
0 0 0 1px rgba(var(--glow-color),.08),
|
|
497
|
+
inset 0 1px 0 rgba(255,255,255,.06);
|
|
498
|
+
}
|
|
499
|
+
.skill-card.onchain-card{--glow-color:0,255,100;--glow-a:.15}
|
|
500
|
+
.skill-card.risk-high{--glow-color:255,60,60;--glow-a:.12}
|
|
501
|
+
|
|
502
|
+
.skill-card .card-shine{
|
|
503
|
+
position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:14px;
|
|
504
|
+
background:radial-gradient(
|
|
505
|
+
600px circle at var(--mx,50%) var(--my,50%),
|
|
506
|
+
rgba(255,255,255,.045) 0%,
|
|
507
|
+
transparent 50%
|
|
508
|
+
);
|
|
509
|
+
opacity:0;transition:opacity .35s;
|
|
510
|
+
}
|
|
511
|
+
.skill-card:hover .card-shine{opacity:1}
|
|
512
|
+
|
|
513
|
+
.skill-card .card-scanline{
|
|
514
|
+
position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:14px;overflow:hidden;
|
|
515
|
+
opacity:0;transition:opacity .5s;
|
|
516
|
+
}
|
|
517
|
+
.skill-card:hover .card-scanline{opacity:1}
|
|
518
|
+
.skill-card .card-scanline::after{
|
|
519
|
+
content:'';position:absolute;left:0;right:0;height:1px;top:-10%;
|
|
520
|
+
background:linear-gradient(90deg,transparent 5%,rgba(var(--glow-color),.2) 30%,rgba(var(--glow-color),.35) 50%,rgba(var(--glow-color),.2) 70%,transparent 95%);
|
|
521
|
+
animation:scanDrop 3.5s ease-in-out infinite;
|
|
522
|
+
box-shadow:0 0 12px 2px rgba(var(--glow-color),.08);
|
|
523
|
+
}
|
|
524
|
+
@keyframes scanDrop{0%{top:-10%}100%{top:110%}}
|
|
525
|
+
|
|
526
|
+
.skill-card{
|
|
527
|
+
animation:cardEnter .6s cubic-bezier(.22,.68,0,1.1) both;
|
|
528
|
+
animation-delay:calc(var(--i,0) * 60ms);
|
|
529
|
+
}
|
|
530
|
+
@keyframes cardEnter{
|
|
531
|
+
from{opacity:0;transform:translateY(30px) scale(.95) rotateX(4deg)}
|
|
532
|
+
to{opacity:1;transform:translateY(0) scale(1) rotateX(0)}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.skill-tier-bar{
|
|
536
|
+
height:3px;width:100%;flex-shrink:0;
|
|
537
|
+
background:linear-gradient(90deg,var(--tier-color,#333) 0%,rgba(var(--glow-color),.15) 60%,transparent 100%);
|
|
538
|
+
box-shadow:0 0 8px var(--tier-color,transparent);
|
|
539
|
+
transition:box-shadow .4s;
|
|
540
|
+
}
|
|
541
|
+
.skill-card:hover .skill-tier-bar{box-shadow:0 0 16px var(--tier-color,transparent)}
|
|
542
|
+
.skill-tier-bar[data-tier="low"]{--tier-color:#4caf50}
|
|
543
|
+
.skill-tier-bar[data-tier="med"]{--tier-color:#ff9800}
|
|
544
|
+
.skill-tier-bar[data-tier="high"]{--tier-color:#f44336}
|
|
545
|
+
|
|
546
|
+
.skill-card-inner{
|
|
547
|
+
position:relative;z-index:4;
|
|
548
|
+
padding:20px 22px 18px;
|
|
549
|
+
display:flex;flex-direction:column;flex:1;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.skill-nft-badge{
|
|
553
|
+
display:inline-flex;align-items:center;gap:5px;
|
|
554
|
+
padding:4px 10px;border-radius:6px;
|
|
555
|
+
font-size:9px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
|
|
556
|
+
backdrop-filter:blur(4px);
|
|
557
|
+
}
|
|
558
|
+
.skill-nft-badge.onchain{
|
|
559
|
+
background:linear-gradient(135deg,rgba(0,255,100,.16),rgba(0,255,100,.04));
|
|
560
|
+
border:1px solid rgba(0,255,100,.4);color:rgba(0,255,100,.95);
|
|
561
|
+
box-shadow:0 0 18px rgba(0,255,100,.12),inset 0 0 8px rgba(0,255,100,.04);
|
|
562
|
+
}
|
|
563
|
+
a.skill-nft-badge{text-decoration:none}
|
|
564
|
+
a.skill-nft-badge:hover{filter:brightness(1.3);box-shadow:0 0 24px rgba(0,255,100,.2)}
|
|
565
|
+
.skill-nft-badge.offchain{
|
|
566
|
+
background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.08);color:rgba(255,255,255,.35);
|
|
567
|
+
}
|
|
568
|
+
.nft-pulse{
|
|
569
|
+
width:6px;height:6px;border-radius:50%;background:currentColor;
|
|
570
|
+
box-shadow:0 0 8px currentColor,0 0 16px currentColor;
|
|
571
|
+
animation:nftPulse 2s ease-in-out infinite;
|
|
572
|
+
}
|
|
573
|
+
@keyframes nftPulse{0%,100%{opacity:1;box-shadow:0 0 8px currentColor,0 0 16px currentColor}50%{opacity:.3;box-shadow:0 0 4px currentColor}}
|
|
574
|
+
|
|
575
|
+
.skill-risk{
|
|
576
|
+
display:inline-flex;align-items:center;gap:3px;font-size:8px;font-weight:800;
|
|
577
|
+
letter-spacing:.1em;text-transform:uppercase;padding:4px 8px;border-radius:5px;
|
|
578
|
+
border:1px solid;backdrop-filter:blur(4px);
|
|
579
|
+
}
|
|
580
|
+
.skill-risk.low{border-color:rgba(76,175,80,.35);color:#4caf50;background:rgba(76,175,80,.08)}
|
|
581
|
+
.skill-risk.med{border-color:rgba(255,152,0,.35);color:#ff9800;background:rgba(255,152,0,.08)}
|
|
582
|
+
.skill-risk.high{border-color:rgba(244,67,54,.35);color:#f44336;background:rgba(244,67,54,.08)}
|
|
583
|
+
.skill-risk.unknown{border-color:rgba(102,102,102,.3);color:#888;background:rgba(102,102,102,.06)}
|
|
584
|
+
|
|
585
|
+
.skill-title{
|
|
586
|
+
margin:0;font-size:15px;font-weight:900;letter-spacing:.02em;
|
|
587
|
+
text-transform:uppercase;color:#fff;line-height:1.35;
|
|
588
|
+
transition:color .3s,text-shadow .3s;
|
|
589
|
+
}
|
|
590
|
+
.skill-card:hover .skill-title{
|
|
591
|
+
color:rgba(var(--glow-color),1);
|
|
592
|
+
text-shadow:0 0 20px rgba(var(--glow-color),.2);
|
|
593
|
+
}
|
|
594
|
+
.skill-slug{
|
|
595
|
+
margin-top:4px;font-family:var(--mono);font-size:10px;
|
|
596
|
+
color:rgba(255,255,255,.3);letter-spacing:.02em;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.skill-desc{
|
|
600
|
+
margin-top:12px;color:rgba(255,255,255,.5);font-size:12px;line-height:1.7;flex:1;
|
|
601
|
+
display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.skill-chain-data{
|
|
605
|
+
margin-top:auto;padding-top:12px;
|
|
606
|
+
border-top:1px solid rgba(var(--glow-color),.06);
|
|
607
|
+
display:flex;flex-direction:column;gap:4px;
|
|
608
|
+
}
|
|
609
|
+
.skill-tx{display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:9px;color:var(--muted)}
|
|
610
|
+
.skill-tx-label{font-weight:700;color:rgba(var(--glow-color),.7);text-transform:uppercase;letter-spacing:.06em;min-width:52px;font-size:8px}
|
|
611
|
+
.skill-tx-hash{
|
|
612
|
+
color:var(--cyan);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
613
|
+
cursor:pointer;transition:color .2s,text-shadow .2s;max-width:160px;display:inline-block;text-decoration:none;
|
|
614
|
+
}
|
|
615
|
+
.skill-tx-hash:hover{color:#fff;text-shadow:0 0 8px rgba(99,215,255,.4)}
|
|
616
|
+
|
|
617
|
+
.skill-foot{
|
|
618
|
+
margin-top:14px;padding-top:12px;
|
|
619
|
+
border-top:1px solid rgba(var(--glow-color),.06);
|
|
620
|
+
display:flex;gap:6px;flex-wrap:wrap;align-items:center;
|
|
621
|
+
}
|
|
622
|
+
.skill-foot .pill{
|
|
623
|
+
font-size:9px;padding:5px 10px;border-radius:5px;
|
|
624
|
+
transition:all .25s cubic-bezier(.22,.68,0,1);
|
|
625
|
+
border:1px solid rgba(99,215,255,.12);color:rgba(99,215,255,.8);
|
|
626
|
+
background:rgba(99,215,255,.03);text-decoration:none;
|
|
627
|
+
}
|
|
628
|
+
.skill-foot .pill:hover{
|
|
629
|
+
border-color:rgba(var(--glow-color),.5);
|
|
630
|
+
background:rgba(var(--glow-color),.1);
|
|
631
|
+
color:#fff;text-decoration:none;
|
|
632
|
+
box-shadow:0 0 16px rgba(var(--glow-color),.08);
|
|
633
|
+
transform:translateY(-1px);
|
|
634
|
+
}
|
|
635
|
+
.skill-foot .pill.install-btn{
|
|
636
|
+
border-color:rgba(var(--glow-color),.4);color:rgba(var(--glow-color),.95);
|
|
637
|
+
background:rgba(var(--glow-color),.08);font-weight:800;
|
|
638
|
+
box-shadow:0 0 8px rgba(var(--glow-color),.06);
|
|
639
|
+
}
|
|
640
|
+
.skill-foot .pill.install-btn:hover{
|
|
641
|
+
border-color:rgba(var(--glow-color),.7);
|
|
642
|
+
background:rgba(var(--glow-color),.18);color:#fff;
|
|
643
|
+
box-shadow:0 0 20px rgba(var(--glow-color),.15),0 0 40px rgba(var(--glow-color),.06);
|
|
644
|
+
}
|
|
645
|
+
.skill-foot .pill.nft{border-color:rgba(0,255,0,.25);color:rgba(0,255,0,.85);background:rgba(0,255,0,.05)}
|
|
646
|
+
.skill-foot .pill.nft:hover{border-color:rgba(0,255,0,.5);background:rgba(0,255,0,.12);color:#fff}
|
|
647
|
+
|
|
648
|
+
.pagination-bar{
|
|
649
|
+
display:flex;align-items:center;justify-content:center;gap:16px;
|
|
650
|
+
margin:28px auto 8px;padding:14px 20px;
|
|
651
|
+
background:rgba(11,22,34,.6);
|
|
652
|
+
border:1px solid rgba(207,255,4,.1);border-radius:8px;
|
|
653
|
+
max-width:400px;
|
|
654
|
+
}
|
|
655
|
+
.pg-btn{
|
|
656
|
+
padding:8px 18px;border-radius:6px;border:1px solid rgba(207,255,4,.2);
|
|
657
|
+
background:rgba(207,255,4,.04);color:var(--accent);
|
|
658
|
+
font-size:13px;font-weight:700;cursor:pointer;
|
|
659
|
+
transition:all .2s;font-family:inherit;
|
|
660
|
+
}
|
|
661
|
+
.pg-btn:hover:not(:disabled){border-color:var(--accent);background:rgba(207,255,4,.1);color:#fff}
|
|
662
|
+
.pg-btn:disabled{opacity:.3;cursor:default}
|
|
663
|
+
.pg-info{
|
|
664
|
+
font-size:12px;color:var(--muted);font-weight:600;
|
|
665
|
+
letter-spacing:.04em;min-width:120px;text-align:center;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
#importedList{
|
|
669
|
+
display:grid;
|
|
670
|
+
grid-template-columns:1fr;
|
|
671
|
+
gap:24px;
|
|
672
|
+
perspective:1000px;
|
|
673
|
+
}
|
|
674
|
+
@media(min-width:580px){#importedList{grid-template-columns:repeat(2,1fr);gap:26px}}
|
|
675
|
+
@media(min-width:960px){#importedList{grid-template-columns:repeat(3,1fr);gap:28px}}
|
|
676
|
+
|
|
677
|
+
.skill-source-tag{
|
|
678
|
+
font-size:8px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
|
|
679
|
+
padding:2px 6px;border-radius:3px;border:1px solid rgba(99,215,255,.18);
|
|
680
|
+
color:var(--cyan);background:rgba(99,215,255,.04);margin-left:auto;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.skill-meta{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
684
|
+
.pill.nft{border-color:rgba(0,255,0,.25);color:rgba(0,255,0,.8)}
|
|
685
|
+
.pill.off{border-color:rgba(207,255,4,.22);color:var(--accent-soft)}
|
|
686
|
+
.skill-actions{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
687
|
+
.sec-label{display:flex;align-items:center;gap:12px;justify-content:center;margin-bottom:8px}
|
|
688
|
+
.sec-label .dash{height:1px;width:0;background:rgba(207,255,4,.3);transition:width .7s cubic-bezier(.16,1,.3,1)}
|
|
689
|
+
.ac-visible .sec-label .dash{width:32px}
|
|
690
|
+
.sec-label span{font-size:10px;font-weight:800;color:var(--accent);letter-spacing:.2em;text-transform:uppercase}
|
|
691
|
+
.sec-heading{font-size:24px;font-weight:900;color:#fff;text-transform:uppercase;letter-spacing:-.01em;text-align:center;margin:4px 0 0}
|
|
692
|
+
@media(min-width:640px){.sec-heading{font-size:32px}}
|
|
693
|
+
.sec-sub{color:var(--lightgray);font-size:13px;text-align:center;max-width:520px;margin:12px auto 0;line-height:1.65}
|
|
694
|
+
.sep{height:1px;background:linear-gradient(90deg,transparent,rgba(207,255,4,.3),transparent);transition:all 1s;transform:scaleX(0)}
|
|
695
|
+
.sep.show,.ac-visible .sep{transform:scaleX(1)}
|
|
696
|
+
.foot{padding:16px 20px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;font-size:11px;color:var(--gray);text-transform:uppercase;letter-spacing:.04em}
|
|
697
|
+
.online{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--gray);background:rgba(9,13,18,.7);color:var(--muted);font-size:11px}
|
|
698
|
+
.tab-bar{display:flex;gap:3px;margin:24px 0 20px;border:1px solid rgba(207,255,4,.15);border-radius:8px;overflow:hidden;background:rgba(0,0,0,.3);padding:3px}
|
|
699
|
+
.tab-btn{flex:1;padding:11px 18px;border:none;background:transparent;color:var(--muted);font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;transition:all .25s;border-radius:6px}
|
|
700
|
+
.tab-btn:hover{background:rgba(207,255,4,.04);color:var(--text)}
|
|
701
|
+
.tab-btn.active{background:rgba(207,255,4,.1);color:var(--accent);box-shadow:0 0 12px rgba(207,255,4,.06),inset 0 0 0 1px rgba(207,255,4,.15)}
|
|
702
|
+
.tab-panel{display:none}
|
|
703
|
+
.tab-panel.active{display:block}
|
|
704
|
+
|
|
705
|
+
</style>
|
|
706
|
+
</head>
|
|
707
|
+
<body>
|
|
708
|
+
<div class="bg-collage" aria-hidden="true" id="bgCollage"></div>
|
|
709
|
+
<div class="bg-fade" aria-hidden="true"></div>
|
|
710
|
+
<div class="scanlines" aria-hidden="true"></div>
|
|
711
|
+
<div class="noise" aria-hidden="true"></div>
|
|
712
|
+
<div id="sbNavMount"></div>
|
|
713
|
+
<div class="wrap sb-content-offset">
|
|
714
|
+
<section class="hero" id="hero">
|
|
715
|
+
<div class="orb o1" aria-hidden="true"></div>
|
|
716
|
+
<div class="orb o2" aria-hidden="true"></div>
|
|
717
|
+
<div class="orb o3" aria-hidden="true"></div>
|
|
718
|
+
<div class="eyebrow ac-observe">APECLAW SKILLS</div>
|
|
719
|
+
<h1 class="hero-title ac-observe">Library of <span class="glow">Alexandria</span></h1>
|
|
720
|
+
<div class="hero-sub ac-observe">
|
|
721
|
+
SkillCards are hashed, published onchain, and referenced immutably. Mint a SkillNFT to route revenue to PodVault (EIP-2981) and leave a permanent audit trail via ReceiptRegistry.
|
|
722
|
+
</div>
|
|
723
|
+
<div class="hero-cta ac-observe">
|
|
724
|
+
<a class="btn primary ac-glow-pulse" href="/docs?doc=V2_ONCHAIN_SKILLS.md" data-keep-query="1">Read v2 Skills Doc</a>
|
|
725
|
+
<a class="btn" href="/docs?doc=CONTRIBUTING.md" data-keep-query="1">Contribute</a>
|
|
726
|
+
<a class="btn" href="#getting-started">Get Started</a>
|
|
727
|
+
</div>
|
|
728
|
+
|
|
729
|
+
<div class="stats ac-stagger" aria-label="Library stats">
|
|
730
|
+
<div class="stat ac-observe">
|
|
731
|
+
<div class="num glow" id="statTotal">10,032</div>
|
|
732
|
+
<div class="ac-stat-shimmer"></div>
|
|
733
|
+
<div class="label">Total Skills</div>
|
|
734
|
+
</div>
|
|
735
|
+
<div class="stat ac-observe">
|
|
736
|
+
<div class="num" id="statVetted">10,009</div>
|
|
737
|
+
<div class="ac-stat-shimmer"></div>
|
|
738
|
+
<div class="label">Vetted & Verified</div>
|
|
739
|
+
</div>
|
|
740
|
+
<div class="stat ac-observe">
|
|
741
|
+
<div class="num" id="statOnchain">10,024</div>
|
|
742
|
+
<div class="ac-stat-shimmer"></div>
|
|
743
|
+
<div class="label">Published Onchain</div>
|
|
744
|
+
</div>
|
|
745
|
+
<div class="stat ac-observe">
|
|
746
|
+
<div class="num" id="statContributed">0</div>
|
|
747
|
+
<div class="ac-stat-shimmer"></div>
|
|
748
|
+
<div class="label">Community Contributed</div>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
</section>
|
|
752
|
+
|
|
753
|
+
<div class="sep ac-observe"></div>
|
|
754
|
+
|
|
755
|
+
<div class="tab-bar" role="tablist">
|
|
756
|
+
<button class="tab-btn active" role="tab" data-tab="browse" aria-selected="true">Browse</button>
|
|
757
|
+
<button class="tab-btn" role="tab" data-tab="add" aria-selected="false">Add</button>
|
|
758
|
+
<button class="tab-btn" role="tab" data-tab="onchain" aria-selected="false">Onchain</button>
|
|
759
|
+
</div>
|
|
760
|
+
|
|
761
|
+
<div class="tab-panel active" data-panel="browse">
|
|
762
|
+
<div class="ac-divider"></div>
|
|
763
|
+
<div class="section ac-observe" id="getting-started">
|
|
764
|
+
<h2>Getting Started</h2>
|
|
765
|
+
<div class="grid ac-stagger">
|
|
766
|
+
<div class="card ac-observe">
|
|
767
|
+
<div class="cardhead">
|
|
768
|
+
<h3>1) Browse the Library</h3>
|
|
769
|
+
<span class="badge live">LIVE</span>
|
|
770
|
+
</div>
|
|
771
|
+
<p>
|
|
772
|
+
Browse 10,000+ skills across DeFi, NFTs, data analysis, automation, and other categories. Each skill is vetted before inclusion.
|
|
773
|
+
</p>
|
|
774
|
+
<div class="tools">
|
|
775
|
+
<a class="pill" href="#imported">Browse All Skills</a>
|
|
776
|
+
</div>
|
|
777
|
+
</div>
|
|
778
|
+
<div class="card ac-observe">
|
|
779
|
+
<div class="cardhead">
|
|
780
|
+
<h3>2) Install a Skill</h3>
|
|
781
|
+
<span class="badge live">LIVE</span>
|
|
782
|
+
</div>
|
|
783
|
+
<p>
|
|
784
|
+
Download any SkillCard JSON from the API by slug, then drop it into your agent's skill directory. Works with any ACP-compatible agent.
|
|
785
|
+
</p>
|
|
786
|
+
<div class="code"><span class="k">curl</span> -fsSL "https://apeclaw.ai/api/skills/<slug>" -o ./<slug>.json</div>
|
|
787
|
+
<div class="tools" aria-label="Install instructions">
|
|
788
|
+
<a class="pill" href="#" data-copy='curl -fsSL "https://apeclaw.ai/api/skills/<slug>" -o ./<slug>.json'>Copy curl</a>
|
|
789
|
+
<a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Install docs</a>
|
|
790
|
+
</div>
|
|
791
|
+
<div class="note" style="margin-top:8px">Or browse the skill cards below and click <strong>JSON</strong> to view any skill directly in your browser.</div>
|
|
792
|
+
</div>
|
|
793
|
+
<div class="card ac-observe">
|
|
794
|
+
<div class="cardhead">
|
|
795
|
+
<h3>3) Mint a SkillNFT (onchain identity)</h3>
|
|
796
|
+
<span class="badge alpha">ALPHA</span>
|
|
797
|
+
</div>
|
|
798
|
+
<p>
|
|
799
|
+
Mint creates a persistent skill identity on ApeChain. Royalties can route into a Pod receiver (PodVault) for revenue share across the Pod.
|
|
800
|
+
</p>
|
|
801
|
+
<div class="code"><span class="k">ape-claw</span> v2 skill mint --royalty-receiver <PodVault address> --royalty-bps 500 --json</div>
|
|
802
|
+
<div class="tools" aria-label="Copy mint command">
|
|
803
|
+
<a class="pill" href="#" data-copy="ape-claw v2 skill mint --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --skillNft <addr> --registry <addr> --royalty-receiver <PodVault address> --royalty-bps 500 --json">Copy command</a>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
<div class="card ac-observe">
|
|
807
|
+
<div class="cardhead">
|
|
808
|
+
<h3>4) Publish immutable versions</h3>
|
|
809
|
+
<span class="badge alpha">ALPHA</span>
|
|
810
|
+
</div>
|
|
811
|
+
<p>
|
|
812
|
+
Publishing anchors an immutable version with a <code>contentHash</code> + <code>uri</code>. The chain becomes the library source of truth.
|
|
813
|
+
</p>
|
|
814
|
+
<div class="code"><span class="k">ape-claw</span> v2 skill publish --skillId 1 --file skillcards/seed/apeclaw-nft-autobuy.v1.json --json</div>
|
|
815
|
+
<div class="tools" aria-label="Copy publish command">
|
|
816
|
+
<a class="pill" href="#" data-copy="ape-claw v2 skill publish --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --registry <addr> --skillId 1 --file \"skillcards/seed/apeclaw-nft-autobuy.v1.json\" --riskTier 1 --json">Copy command</a>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
</div>
|
|
820
|
+
</div>
|
|
821
|
+
|
|
822
|
+
<div class="ac-divider"></div>
|
|
823
|
+
<div class="section ac-visible" id="imported">
|
|
824
|
+
<h2>Imported Skills</h2>
|
|
825
|
+
<p style="text-align:center;color:var(--muted);font-size:13px;margin:-8px 0 20px;max-width:600px;margin-left:auto;margin-right:auto;line-height:1.6">
|
|
826
|
+
Browse the full library of vetted SkillCards. Filter by risk tier, search by name, or show only onchain-published skills.
|
|
827
|
+
</p>
|
|
828
|
+
|
|
829
|
+
<div class="tools" style="max-width:720px;margin:0 auto 20px;flex-wrap:wrap">
|
|
830
|
+
<input id="importedSearch" type="search" placeholder="Search skills..." autocomplete="off" spellcheck="false" style="flex:1;min-width:200px">
|
|
831
|
+
<select id="riskFilter" aria-label="Filter by risk tier">
|
|
832
|
+
<option value="all">All Risk Tiers</option>
|
|
833
|
+
<option value="low">Low Risk</option>
|
|
834
|
+
<option value="med">Medium Risk</option>
|
|
835
|
+
<option value="high">High Risk</option>
|
|
836
|
+
</select>
|
|
837
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none">
|
|
838
|
+
<input id="onlyOnchain" type="checkbox">
|
|
839
|
+
<span>Onchain only</span>
|
|
840
|
+
</label>
|
|
841
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none">
|
|
842
|
+
<input id="onlyVetted" type="checkbox" checked>
|
|
843
|
+
<span>Vetted only</span>
|
|
844
|
+
</label>
|
|
845
|
+
<div style="display:flex;align-items:center;gap:8px;padding:6px 12px;background:rgba(207,255,4,.05);border:1px solid rgba(207,255,4,.2);border-radius:6px">
|
|
846
|
+
<span style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em">Results:</span>
|
|
847
|
+
<span id="importedBadge" style="font-size:11px;font-weight:700;color:var(--accent)">LOADING</span>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
|
|
851
|
+
<div id="importedList" class="cards" style="max-width:1200px;margin:0 auto"></div>
|
|
852
|
+
<div id="paginationBar" class="pagination-bar" style="display:none">
|
|
853
|
+
<button id="pgPrev" class="pg-btn" disabled>‹ Prev</button>
|
|
854
|
+
<span id="pgInfo" class="pg-info">Page 1 of 1</span>
|
|
855
|
+
<button id="pgNext" class="pg-btn">Next ›</button>
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
</div>
|
|
859
|
+
|
|
860
|
+
<div class="tab-panel" data-panel="add">
|
|
861
|
+
<div class="ac-divider"></div>
|
|
862
|
+
<div class="section ac-observe" id="your-skills">
|
|
863
|
+
<h2>Add a Skill</h2>
|
|
864
|
+
<p style="text-align:center;color:var(--muted);font-size:13px;margin:-8px 0 20px;max-width:600px;margin-left:auto;margin-right:auto;line-height:1.6">
|
|
865
|
+
Submit a SkillCard JSON to the global ApeClaw library. Once vetted, it becomes available to every agent and can be minted as a SkillNFT onchain.
|
|
866
|
+
</p>
|
|
867
|
+
|
|
868
|
+
<div class="step-flow" style="max-width:720px;margin:0 auto">
|
|
869
|
+
|
|
870
|
+
<!-- Step 1: Choose a template -->
|
|
871
|
+
<div class="step-card open active" data-step="1">
|
|
872
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
873
|
+
<div class="step-num">1</div>
|
|
874
|
+
<div class="step-title">Choose a Template</div>
|
|
875
|
+
<div class="step-badge" id="stepTemplateBadge">START HERE</div>
|
|
876
|
+
<div class="step-chevron">▾</div>
|
|
877
|
+
</div>
|
|
878
|
+
<div class="step-body">
|
|
879
|
+
<div class="template-grid">
|
|
880
|
+
<div class="template-btn" onclick="selectTemplate('low',this)">
|
|
881
|
+
<div class="t-risk low">LOW RISK</div>
|
|
882
|
+
<div class="t-name">Read-only / Browse</div>
|
|
883
|
+
<div class="t-desc">Search, summarize, or display data. No writes, no spend.</div>
|
|
884
|
+
</div>
|
|
885
|
+
<div class="template-btn" onclick="selectTemplate('med',this)">
|
|
886
|
+
<div class="t-risk med">MEDIUM RISK</div>
|
|
887
|
+
<div class="t-name">Writes / Automation</div>
|
|
888
|
+
<div class="t-desc">Create, update, or automate with caps. Confirm phrases recommended.</div>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="template-btn" onclick="selectTemplate('high',this)">
|
|
891
|
+
<div class="t-risk high">HIGH RISK</div>
|
|
892
|
+
<div class="t-name">Spend / Escrow</div>
|
|
893
|
+
<div class="t-desc">Value-moving operations. Strict opt-in with safety gates required.</div>
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
<div style="margin-top:12px;display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
897
|
+
<span style="color:var(--muted);font-size:12px">or paste your own JSON below</span>
|
|
898
|
+
<span style="color:var(--muted);font-size:12px">·</span>
|
|
899
|
+
<a href="/docs?doc=CONTRIBUTING.md" data-keep-query="1" style="color:var(--accent);font-size:12px">Read the contributing guide</a>
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
|
|
904
|
+
<!-- Hidden template select for JS compatibility -->
|
|
905
|
+
<select id="templateSelect" aria-label="SkillCard template" style="display:none">
|
|
906
|
+
<option value="">template: choose</option>
|
|
907
|
+
<option value="low">Low risk</option>
|
|
908
|
+
<option value="med">Medium risk</option>
|
|
909
|
+
<option value="high">High risk</option>
|
|
910
|
+
</select>
|
|
911
|
+
<button id="loadTemplateBtn" type="button" style="display:none">Load Template</button>
|
|
912
|
+
|
|
913
|
+
<!-- Step 2: Edit SkillCard -->
|
|
914
|
+
<div class="step-card" data-step="2">
|
|
915
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
916
|
+
<div class="step-num">2</div>
|
|
917
|
+
<div class="step-title">Edit SkillCard JSON</div>
|
|
918
|
+
<div class="step-badge" id="stepEditBadge"></div>
|
|
919
|
+
<div class="step-chevron">▾</div>
|
|
920
|
+
</div>
|
|
921
|
+
<div class="step-body">
|
|
922
|
+
<div class="danger-note" style="margin-bottom:12px">
|
|
923
|
+
Never paste private keys, API keys, or secrets. SkillCards are public metadata + execution bindings.
|
|
924
|
+
</div>
|
|
925
|
+
<textarea id="skillJson" placeholder='{"name":"My Skill","slug":"my-skill","version":"1.0.0","description":"What this skill does","bindings":[{"type":"cli","command":"..."}],"constraints":{"riskTier":2}}' style="min-height:200px"></textarea>
|
|
926
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">
|
|
927
|
+
<button class="btn" id="formatJsonBtn" type="button">Format JSON</button>
|
|
928
|
+
<button class="btn" id="validateSkillBtn" type="button">Validate</button>
|
|
929
|
+
</div>
|
|
930
|
+
<div class="note" id="skillPreview" style="margin-top:10px">Preview: —</div>
|
|
931
|
+
|
|
932
|
+
<div style="margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,.06)">
|
|
933
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
934
|
+
<input id="skillSourceUrl" type="url" placeholder="Optional: paste a URL to a SkillCard JSON" autocomplete="off" spellcheck="false" style="flex:1;min-width:200px">
|
|
935
|
+
<button class="btn" id="loadFromUrlBtn" type="button">Load from URL</button>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<!-- Step 3: Authenticate & Submit -->
|
|
942
|
+
<div class="step-card" data-step="3">
|
|
943
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
944
|
+
<div class="step-num">3</div>
|
|
945
|
+
<div class="step-title">Submit to Library</div>
|
|
946
|
+
<div class="step-badge" id="stepSubmitBadge"></div>
|
|
947
|
+
<div class="step-chevron">▾</div>
|
|
948
|
+
</div>
|
|
949
|
+
<div class="step-body">
|
|
950
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:12px">
|
|
951
|
+
<input id="authAgentId" type="text" placeholder="Your agent ID" autocomplete="off" spellcheck="false" style="flex:1;min-width:140px">
|
|
952
|
+
<input id="authAgentToken" type="password" placeholder="Agent token" autocomplete="off" spellcheck="false" style="flex:1;min-width:140px">
|
|
953
|
+
<button class="btn" id="saveAuthBtn" type="button">Save</button>
|
|
954
|
+
<button class="btn" id="checkAuthBtn" type="button">Check</button>
|
|
955
|
+
<button class="btn" id="clearAuthBtn" type="button">Clear</button>
|
|
956
|
+
</div>
|
|
957
|
+
<div class="note" id="authStatus" style="margin-bottom:14px">Auth: not set</div>
|
|
958
|
+
|
|
959
|
+
<button class="btn primary" id="addSkillBtn" type="button" style="width:100%;padding:14px;font-size:14px">Add to Global Library</button>
|
|
960
|
+
<div class="note" id="addSkillStatus" style="margin-top:8px;text-align:center"></div>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
|
|
964
|
+
<!-- Step 4: Your Submitted Skills -->
|
|
965
|
+
<div class="step-card" data-step="4">
|
|
966
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
967
|
+
<div class="step-num">4</div>
|
|
968
|
+
<div class="step-title">Your Submitted Skills</div>
|
|
969
|
+
<div class="step-badge" id="userBadge">AUTO</div>
|
|
970
|
+
<div class="step-chevron">▾</div>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="step-body">
|
|
973
|
+
<div class="tools" aria-label="Submitted skills search" style="margin-top:0;margin-bottom:10px">
|
|
974
|
+
<input id="userSkillSearch" type="search" placeholder="Search your skills..." autocomplete="off" spellcheck="false" style="width:100%">
|
|
975
|
+
</div>
|
|
976
|
+
<div class="list" id="userSkillList">
|
|
977
|
+
<div style="text-align:center;padding:32px 16px;color:var(--muted)">
|
|
978
|
+
<div style="font-size:32px;margin-bottom:12px;opacity:.4">📦</div>
|
|
979
|
+
<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:6px">No submissions yet</div>
|
|
980
|
+
<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>
|
|
981
|
+
</div>
|
|
982
|
+
</div>
|
|
983
|
+
|
|
984
|
+
<button class="collapsible-toggle" onclick="toggleCollapsible(this)" type="button" style="margin-top:16px">
|
|
985
|
+
Advanced: Contract Settings ▾
|
|
986
|
+
</button>
|
|
987
|
+
<div class="collapsible-content">
|
|
988
|
+
<div class="note" style="margin-bottom:8px">Auto-filled from backend. Only edit if you need custom addresses.</div>
|
|
989
|
+
<div class="tools" aria-label="Mint/publish settings" style="flex-direction:column">
|
|
990
|
+
<input id="v2RpcUrl" type="text" placeholder="RPC URL (auto-filled)" autocomplete="off" spellcheck="false">
|
|
991
|
+
<input id="v2SkillNft" type="text" placeholder="SkillNFT address (auto-filled)" autocomplete="off" spellcheck="false">
|
|
992
|
+
<input id="v2Registry" type="text" placeholder="SkillRegistry address (auto-filled)" autocomplete="off" spellcheck="false">
|
|
993
|
+
<input id="v2Intents" type="text" placeholder="IntentRegistry address" autocomplete="off" spellcheck="false">
|
|
994
|
+
<input id="v2Receipts" type="text" placeholder="ReceiptRegistry address" autocomplete="off" spellcheck="false">
|
|
995
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
996
|
+
<input id="royaltyReceiver" type="text" placeholder="Royalty receiver (PodVault)" autocomplete="off" spellcheck="false" style="flex:1;min-width:200px">
|
|
997
|
+
<input id="royaltyBps" type="text" placeholder="BPS (500)" autocomplete="off" spellcheck="false" style="width:80px">
|
|
998
|
+
</div>
|
|
999
|
+
<button class="btn" id="saveV2SettingsBtn" type="button">Save Settings</button>
|
|
1000
|
+
</div>
|
|
1001
|
+
<div class="note" id="v2SettingsNote" style="margin-top:6px">Mint/publish commands use env var APE_CLAW_V2_PRIVATE_KEY (never paste keys here).</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
|
|
1009
|
+
<div class="ac-divider"></div>
|
|
1010
|
+
<div class="section ac-observe" id="how-it-works">
|
|
1011
|
+
<h2>How It Works</h2>
|
|
1012
|
+
<div class="grid ac-stagger" style="max-width:720px;margin:0 auto">
|
|
1013
|
+
<div class="card ac-observe" style="border-left:3px solid #4caf50">
|
|
1014
|
+
<div class="cardhead">
|
|
1015
|
+
<h3>Immutable Versions</h3>
|
|
1016
|
+
<span class="badge live">LIVE</span>
|
|
1017
|
+
</div>
|
|
1018
|
+
<p>
|
|
1019
|
+
Skills are published as immutable versions with a <code>contentHash</code> and <code>uri</code>. The chain is the source of truth: UIs can disappear and the library still exists.
|
|
1020
|
+
</p>
|
|
1021
|
+
</div>
|
|
1022
|
+
<div class="card ac-observe" style="border-left:3px solid #ff9800">
|
|
1023
|
+
<div class="cardhead">
|
|
1024
|
+
<h3>Revenue Share</h3>
|
|
1025
|
+
<span class="badge alpha">COMING SOON</span>
|
|
1026
|
+
</div>
|
|
1027
|
+
<p>
|
|
1028
|
+
SkillNFT royalties route to a shared PodVault. Contribute skills, earn revenue when they are used onchain.
|
|
1029
|
+
</p>
|
|
1030
|
+
</div>
|
|
1031
|
+
<div class="card ac-observe" style="border-left:3px solid var(--cyan)">
|
|
1032
|
+
<div class="cardhead">
|
|
1033
|
+
<h3>Onchain Receipts</h3>
|
|
1034
|
+
<span class="badge alpha">ALPHA</span>
|
|
1035
|
+
</div>
|
|
1036
|
+
<p>
|
|
1037
|
+
Append-only audit anchors keyed by <code>traceIdHash</code>. What happened truth without trusting a backend.
|
|
1038
|
+
</p>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="card ac-observe" style="border-left:3px solid #f44336">
|
|
1041
|
+
<div class="cardhead">
|
|
1042
|
+
<h3>Safety Posture</h3>
|
|
1043
|
+
<span class="badge live">ENFORCED</span>
|
|
1044
|
+
</div>
|
|
1045
|
+
<p>
|
|
1046
|
+
High-risk skills are strict opt-in. Pod default is dry-run with a kill switch, runtime cap, and auditable logs.
|
|
1047
|
+
</p>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
</div>
|
|
1052
|
+
|
|
1053
|
+
<div class="tab-panel" data-panel="onchain">
|
|
1054
|
+
<div class="ac-divider"></div>
|
|
1055
|
+
<div class="section ac-observe" id="onchain-status">
|
|
1056
|
+
<h2>ApeChain Network</h2>
|
|
1057
|
+
<p style="text-align:center;color:var(--muted);font-size:13px;margin:-8px 0 20px;max-width:620px;margin-left:auto;margin-right:auto;line-height:1.6">
|
|
1058
|
+
All ApeClaw v2 contracts are deployed on ApeChain (Chain ID 33139). Intents, receipts, and skill versions are immutable onchain primitives.
|
|
1059
|
+
</p>
|
|
1060
|
+
|
|
1061
|
+
<!-- Live deployment stats -->
|
|
1062
|
+
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;max-width:720px;margin:0 auto 24px" id="onchainStatsGrid">
|
|
1063
|
+
<div class="stat ac-observe" style="min-height:auto;padding:14px">
|
|
1064
|
+
<div class="num glow" id="ocStatNfts" style="font-size:22px">—</div>
|
|
1065
|
+
<div class="label" style="font-size:10px">SkillNFTs</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="stat ac-observe" style="min-height:auto;padding:14px">
|
|
1068
|
+
<div class="num" id="ocStatOnchain" style="font-size:22px">—</div>
|
|
1069
|
+
<div class="label" style="font-size:10px">Published</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
<div class="stat ac-observe" style="min-height:auto;padding:14px">
|
|
1072
|
+
<div class="num" id="ocStatVetted" style="font-size:22px">—</div>
|
|
1073
|
+
<div class="label" style="font-size:10px">Vetted</div>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div class="stat ac-observe" style="min-height:auto;padding:14px">
|
|
1076
|
+
<div class="num" id="ocStatChain" style="font-size:22px;font-size:13px;letter-spacing:.02em">ApeChain</div>
|
|
1077
|
+
<div class="label" style="font-size:10px">Network</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
</div>
|
|
1080
|
+
|
|
1081
|
+
<!-- Deployed contracts -->
|
|
1082
|
+
<div style="max-width:720px;margin:0 auto">
|
|
1083
|
+
<div class="step-card open" data-step="contracts">
|
|
1084
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
1085
|
+
<div class="step-num" style="border-color:rgba(0,255,0,.5);color:rgba(0,255,0,.8);font-size:11px">◆</div>
|
|
1086
|
+
<div class="step-title">Deployed Contracts</div>
|
|
1087
|
+
<div class="step-badge done" id="ocContractsBadge">LOADING</div>
|
|
1088
|
+
<div class="step-chevron">▾</div>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="step-body">
|
|
1091
|
+
<div id="ocContractsList" style="font-family:var(--mono);font-size:12px;color:var(--muted);line-height:2">
|
|
1092
|
+
Loading deployment data...
|
|
1093
|
+
</div>
|
|
1094
|
+
<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">
|
|
1095
|
+
<a class="pill" href="https://apescan.io" target="_blank" rel="noopener" style="font-size:11px">ApeScan Explorer</a>
|
|
1096
|
+
<a class="pill" href="/docs?doc=ONCHAIN_V2_GUIDE.md" data-keep-query="1" style="font-size:11px">Onchain Guide</a>
|
|
1097
|
+
<a class="pill" href="/docs?doc=V2_ONCHAIN_SKILLS.md" data-keep-query="1" style="font-size:11px">V2 Skills Docs</a>
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
</div>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
|
|
1104
|
+
<div class="ac-divider"></div>
|
|
1105
|
+
<div class="section ac-observe" id="intents">
|
|
1106
|
+
<h2>Intents</h2>
|
|
1107
|
+
<p style="text-align:center;color:var(--muted);font-size:13px;margin:-8px 0 20px;max-width:600px;margin-left:auto;margin-right:auto;line-height:1.6">
|
|
1108
|
+
IntentRegistry is a minimal primitive for solver-style architectures. This UI never signs onchain — it generates CLI commands you run locally.
|
|
1109
|
+
</p>
|
|
1110
|
+
|
|
1111
|
+
<div style="max-width:720px;margin:0 auto">
|
|
1112
|
+
|
|
1113
|
+
<div class="step-card open" data-step="intent-create">
|
|
1114
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
1115
|
+
<div class="step-num">1</div>
|
|
1116
|
+
<div class="step-title">Create Intent</div>
|
|
1117
|
+
<div class="step-badge">SAFE</div>
|
|
1118
|
+
<div class="step-chevron">▾</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
<div class="step-body">
|
|
1121
|
+
<p style="color:var(--muted);font-size:12px;line-height:1.6;margin-bottom:12px">
|
|
1122
|
+
Post a work order for solvers to discover and fulfill. Define the task goal, constraints, and optional expiry.
|
|
1123
|
+
</p>
|
|
1124
|
+
<textarea id="intentPayload" placeholder='{"type":"task","goal":"import 50 skills","constraints":{"maxSpendApe":0}}' style="min-height:100px"></textarea>
|
|
1125
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px">
|
|
1126
|
+
<input id="intentExpiresAt" type="text" placeholder="expiresAt (unix seconds, optional)" autocomplete="off" spellcheck="false" style="flex:1;min-width:200px">
|
|
1127
|
+
<button class="btn" id="copyIntentCreateBtn" type="button">Copy Command</button>
|
|
1128
|
+
</div>
|
|
1129
|
+
<div class="code" id="intentCreatePreview" style="margin-top:10px"><span class="k">ape-claw</span> v2 intent create --rpc <url> --privateKey "$APE_CLAW_V2_PRIVATE_KEY" --intents <addr> --payload '{...}' --json</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
|
|
1133
|
+
<div class="step-card" data-step="intent-cancel">
|
|
1134
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
1135
|
+
<div class="step-num">2</div>
|
|
1136
|
+
<div class="step-title">Cancel Intent</div>
|
|
1137
|
+
<div class="step-badge">SAFE</div>
|
|
1138
|
+
<div class="step-chevron">▾</div>
|
|
1139
|
+
</div>
|
|
1140
|
+
<div class="step-body">
|
|
1141
|
+
<p style="color:var(--muted);font-size:12px;line-height:1.6;margin-bottom:12px">
|
|
1142
|
+
Revoke a stale or unwanted intent by its ID. Only the creator can cancel.
|
|
1143
|
+
</p>
|
|
1144
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
1145
|
+
<input id="intentCancelId" type="text" placeholder="intentId (number)" autocomplete="off" spellcheck="false" style="flex:1;min-width:140px">
|
|
1146
|
+
<button class="btn" id="copyIntentCancelBtn" type="button">Copy Command</button>
|
|
1147
|
+
</div>
|
|
1148
|
+
<div class="code" id="intentCancelPreview" style="margin-top:10px"><span class="k">ape-claw</span> v2 intent cancel --rpc <url> --privateKey "$APE_CLAW_V2_PRIVATE_KEY" --intents <addr> --intentId <id> --json</div>
|
|
1149
|
+
<div class="note" style="margin-top:10px">Solver network is the missing piece (P2). This ships the primitive + command surface.</div>
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
|
|
1153
|
+
</div>
|
|
1154
|
+
<div style="display:flex;gap:8px;justify-content:center;margin-top:12px;flex-wrap:wrap">
|
|
1155
|
+
<a class="pill" href="/docs?doc=ONCHAIN_V2_GUIDE.md" data-keep-query="1" style="font-size:11px">Onchain guide</a>
|
|
1156
|
+
<a class="pill" href="/docs?doc=WEB4_PLAN_STATUS.md" data-keep-query="1" style="font-size:11px">Plan status</a>
|
|
1157
|
+
<a class="pill" href="/docs?doc=WEB4_SWARM_MODEL.md" data-keep-query="1" style="font-size:11px">Swarm model</a>
|
|
1158
|
+
</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
|
|
1161
|
+
<div class="ac-divider"></div>
|
|
1162
|
+
<div class="section ac-observe" id="receipts">
|
|
1163
|
+
<h2>Receipts</h2>
|
|
1164
|
+
<p style="text-align:center;color:var(--muted);font-size:13px;margin:-8px 0 20px;max-width:600px;margin-left:auto;margin-right:auto;line-height:1.6">
|
|
1165
|
+
Receipts are the onchain memory anchor primitive. Append-only audit trail keyed by <code style="font-size:12px">traceIdHash</code>. This UI reads only — it never signs transactions.
|
|
1166
|
+
</p>
|
|
1167
|
+
|
|
1168
|
+
<div style="max-width:720px;margin:0 auto">
|
|
1169
|
+
|
|
1170
|
+
<div class="step-card open" data-step="receipt-explorer">
|
|
1171
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
1172
|
+
<div class="step-num" style="border-color:var(--cyan);color:var(--cyan)">◆</div>
|
|
1173
|
+
<div class="step-title">Receipt Explorer</div>
|
|
1174
|
+
<div class="step-badge">READ-ONLY</div>
|
|
1175
|
+
<div class="step-chevron">▾</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div class="step-body">
|
|
1178
|
+
<p style="color:var(--muted);font-size:12px;line-height:1.6;margin-bottom:12px">
|
|
1179
|
+
Look up any receipt by its traceId. You can copy the CLI command to run locally, or fetch directly from the backend API if configured.
|
|
1180
|
+
</p>
|
|
1181
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
|
1182
|
+
<input id="receiptTraceId" type="text" placeholder="traceId (e.g. nft_buy_abc123...)" autocomplete="off" spellcheck="false" style="flex:1;min-width:200px">
|
|
1183
|
+
<button class="btn" id="copyReceiptGetBtn" type="button">Copy CLI</button>
|
|
1184
|
+
<button class="btn primary" id="fetchReceiptGetBtn" type="button">Fetch</button>
|
|
1185
|
+
</div>
|
|
1186
|
+
<div class="code" id="receiptGetPreview" style="margin-top:10px"><span class="k">ape-claw</span> v2 receipt get --rpc <url> --receipts <addr> --traceId "<traceId>" --json</div>
|
|
1187
|
+
<div class="code" id="receiptGetResult" style="margin-top:8px;max-height:300px;overflow:auto">Result: —</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
|
|
1191
|
+
<div class="step-card" data-step="receipt-record">
|
|
1192
|
+
<div class="step-header" onclick="toggleStep(this)">
|
|
1193
|
+
<div class="step-num" style="border-color:var(--cyan);color:var(--cyan)">+</div>
|
|
1194
|
+
<div class="step-title">Record a Receipt (CLI)</div>
|
|
1195
|
+
<div class="step-badge">WRITE</div>
|
|
1196
|
+
<div class="step-chevron">▾</div>
|
|
1197
|
+
</div>
|
|
1198
|
+
<div class="step-body">
|
|
1199
|
+
<p style="color:var(--muted);font-size:12px;line-height:1.6;margin-bottom:12px">
|
|
1200
|
+
Anchor a new receipt onchain. This is a write operation — run it via CLI with your private key. The receipt becomes immutable and verifiable.
|
|
1201
|
+
</p>
|
|
1202
|
+
<div class="code"><span class="k">ape-claw</span> v2 receipt record \<br> --traceId "nft_buy_abc123" \<br> --subject "agent:my-bot" \<br> --payload '{"kind":"nft.buy.confirmed","price":"0.5 APE"}' \<br> --json</div>
|
|
1203
|
+
<div class="note" style="margin-top:10px">Receipts are append-only. Once recorded, they cannot be modified or deleted. The <code>traceIdHash</code> + <code>contentHash</code> pair is the onchain truth.</div>
|
|
1204
|
+
</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
|
|
1207
|
+
</div>
|
|
1208
|
+
<div style="display:flex;gap:8px;justify-content:center;margin-top:12px;flex-wrap:wrap">
|
|
1209
|
+
<a class="pill" href="/docs?doc=ONCHAIN_V2_GUIDE.md" data-keep-query="1" style="font-size:11px">Onchain guide</a>
|
|
1210
|
+
<a class="pill" href="/docs?doc=WEB4_SWARM_MODEL.md" data-keep-query="1" style="font-size:11px">Swarm model</a>
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
|
|
1215
|
+
<div class="toast" id="toast"></div>
|
|
1216
|
+
|
|
1217
|
+
<div class="modal-backdrop" id="modalBackdrop" aria-hidden="true">
|
|
1218
|
+
<div class="modal">
|
|
1219
|
+
<h3 id="modalTitle">Modal</h3>
|
|
1220
|
+
<div class="body" id="modalBody"></div>
|
|
1221
|
+
<div class="footer">
|
|
1222
|
+
<a class="pill" href="#" id="modalCancelBtn">Close</a>
|
|
1223
|
+
<a class="pill install-btn" href="#" id="modalOkBtn" style="display:none">OK</a>
|
|
1224
|
+
</div>
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1227
|
+
|
|
1228
|
+
<script>
|
|
1229
|
+
window.addEventListener('error', (e) => { console.error('[ApeClaw] Uncaught error:', e.error); });
|
|
1230
|
+
window.addEventListener('unhandledrejection', (e) => { console.error('[ApeClaw] Unhandled rejection:', e.reason); });
|
|
1231
|
+
|
|
1232
|
+
(function () {
|
|
1233
|
+
// Lightweight collage background to match the Stonk terminal feel.
|
|
1234
|
+
try {
|
|
1235
|
+
var c = document.getElementById('bgCollage');
|
|
1236
|
+
if (c && !c.hasChildNodes()) {
|
|
1237
|
+
var N = 80;
|
|
1238
|
+
for (var i = 0; i < N; i++) {
|
|
1239
|
+
var img = document.createElement('img');
|
|
1240
|
+
img.src = '/ui/favicon-lobster.png';
|
|
1241
|
+
img.alt = '';
|
|
1242
|
+
img.style.setProperty('--r', (Math.round((Math.random() * 10 - 5) * 10) / 10) + 'deg');
|
|
1243
|
+
c.appendChild(img);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
} catch (e) {}
|
|
1247
|
+
|
|
1248
|
+
// Preserve ?api=... when navigating within the product.
|
|
1249
|
+
var search = (window.location && window.location.search) ? String(window.location.search) : '';
|
|
1250
|
+
if (search) {
|
|
1251
|
+
try {
|
|
1252
|
+
var as = document.querySelectorAll('a[data-keep-query="1"]');
|
|
1253
|
+
for (var i = 0; i < as.length; i++) {
|
|
1254
|
+
var href = String(as[i].getAttribute('href') || '');
|
|
1255
|
+
if (!href || href.indexOf('http') === 0 || href.indexOf('#') === 0) continue;
|
|
1256
|
+
if (href.indexOf('?') !== -1) continue;
|
|
1257
|
+
as[i].setAttribute('href', href + search);
|
|
1258
|
+
}
|
|
1259
|
+
} catch (e) {}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function escapeHtml(v) {
|
|
1263
|
+
return String(v || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
1264
|
+
}
|
|
1265
|
+
function pillForRisk(n) {
|
|
1266
|
+
var r = Number(n || 0);
|
|
1267
|
+
if (r >= 3) return '<span class="pill high">risk: high</span>';
|
|
1268
|
+
if (r === 2) return '<span class="pill med">risk: med</span>';
|
|
1269
|
+
return '<span class="pill low">risk: low</span>';
|
|
1270
|
+
}
|
|
1271
|
+
function fmtInt(n) {
|
|
1272
|
+
try { return new Intl.NumberFormat().format(Number(n || 0)); } catch (e) { return String(n || 0); }
|
|
1273
|
+
}
|
|
1274
|
+
function riskBucket(n) {
|
|
1275
|
+
var r = Number(n || 0);
|
|
1276
|
+
if (r >= 3) return 'high';
|
|
1277
|
+
if (r === 2) return 'med';
|
|
1278
|
+
return 'low';
|
|
1279
|
+
}
|
|
1280
|
+
function toSlug(input) {
|
|
1281
|
+
return String(input || '')
|
|
1282
|
+
.toLowerCase()
|
|
1283
|
+
.trim()
|
|
1284
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1285
|
+
.replace(/^-+|-+$/g, '');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
var api = '';
|
|
1289
|
+
try {
|
|
1290
|
+
var u = new URL(window.location.href);
|
|
1291
|
+
api = (u.searchParams.get('api') || '').trim();
|
|
1292
|
+
} catch (e) {}
|
|
1293
|
+
var apiBase = api ? api.replace(/\/+$/, '') : '';
|
|
1294
|
+
var apiNote = document.getElementById('apiNote');
|
|
1295
|
+
if (apiNote) apiNote.textContent = 'Backend: ' + apiBase;
|
|
1296
|
+
|
|
1297
|
+
// Toast + modal (stonk-style: explicit, non-blocking UI feedback).
|
|
1298
|
+
var toastEl = document.getElementById('toast');
|
|
1299
|
+
var toastT = null;
|
|
1300
|
+
function showToast(msg, isErr) {
|
|
1301
|
+
if (!toastEl) return;
|
|
1302
|
+
try {
|
|
1303
|
+
toastEl.className = 'toast' + (isErr ? ' show err' : ' show');
|
|
1304
|
+
toastEl.textContent = String(msg || '');
|
|
1305
|
+
if (toastT) clearTimeout(toastT);
|
|
1306
|
+
toastT = setTimeout(function () {
|
|
1307
|
+
toastEl.className = 'toast';
|
|
1308
|
+
toastEl.textContent = '';
|
|
1309
|
+
}, isErr ? 2800 : 1600);
|
|
1310
|
+
} catch (e) {}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
var modalBackdrop = document.getElementById('modalBackdrop');
|
|
1314
|
+
var modalTitle = document.getElementById('modalTitle');
|
|
1315
|
+
var modalBody = document.getElementById('modalBody');
|
|
1316
|
+
var modalCancelBtn = document.getElementById('modalCancelBtn');
|
|
1317
|
+
var modalOkBtn = document.getElementById('modalOkBtn');
|
|
1318
|
+
var modalState = { onOk: null, onCancel: null };
|
|
1319
|
+
function closeModal() {
|
|
1320
|
+
if (!modalBackdrop) return;
|
|
1321
|
+
modalBackdrop.classList.remove('show');
|
|
1322
|
+
modalBackdrop.setAttribute('aria-hidden', 'true');
|
|
1323
|
+
if (modalBody) modalBody.innerHTML = '';
|
|
1324
|
+
modalState.onOk = null;
|
|
1325
|
+
modalState.onCancel = null;
|
|
1326
|
+
}
|
|
1327
|
+
function openModal(title, html, onOk, onCancel) {
|
|
1328
|
+
if (!modalBackdrop || !modalTitle || !modalBody) return;
|
|
1329
|
+
modalTitle.textContent = String(title || 'Modal');
|
|
1330
|
+
modalBody.innerHTML = String(html || '');
|
|
1331
|
+
modalState.onOk = onOk || null;
|
|
1332
|
+
modalState.onCancel = onCancel || null;
|
|
1333
|
+
modalBackdrop.classList.add('show');
|
|
1334
|
+
modalBackdrop.setAttribute('aria-hidden', 'false');
|
|
1335
|
+
// Focus first input if present.
|
|
1336
|
+
try {
|
|
1337
|
+
var first = modalBody.querySelector('input,textarea,select,button,a');
|
|
1338
|
+
if (first && first.focus) first.focus();
|
|
1339
|
+
} catch (e) {}
|
|
1340
|
+
}
|
|
1341
|
+
if (modalCancelBtn) modalCancelBtn.addEventListener('click', function () {
|
|
1342
|
+
try { if (modalState.onCancel) modalState.onCancel(); } catch (e) {}
|
|
1343
|
+
closeModal();
|
|
1344
|
+
});
|
|
1345
|
+
if (modalOkBtn) modalOkBtn.addEventListener('click', function () {
|
|
1346
|
+
try { if (modalState.onOk) modalState.onOk(); } catch (e) {}
|
|
1347
|
+
});
|
|
1348
|
+
if (modalBackdrop) modalBackdrop.addEventListener('click', function (ev) {
|
|
1349
|
+
if (ev && ev.target === modalBackdrop) closeModal();
|
|
1350
|
+
});
|
|
1351
|
+
document.addEventListener('keydown', function (ev) {
|
|
1352
|
+
if (!modalBackdrop) return;
|
|
1353
|
+
if (!modalBackdrop.classList.contains('show')) return;
|
|
1354
|
+
if (ev && ev.key === 'Escape') closeModal();
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
var seed = [
|
|
1358
|
+
{
|
|
1359
|
+
name: 'StonkBrokers \u2014 Full DeFi Suite',
|
|
1360
|
+
slug: 'stonkbrokers-launcher',
|
|
1361
|
+
file: '/api/skills/stonkbrokers-launcher',
|
|
1362
|
+
riskTier: 2,
|
|
1363
|
+
desc: 'End-to-end DeFi on Robinhood Chain: faucet, token launcher, Uniswap v3 swaps, LP, covered-call options.'
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
name: 'ApeClaw NFT Autobuy',
|
|
1367
|
+
slug: 'apeclaw-nft-autobuy',
|
|
1368
|
+
file: '/api/skills/apeclaw-nft-autobuy',
|
|
1369
|
+
riskTier: 1,
|
|
1370
|
+
desc: 'Collect NFTs while you sleep (policy gated).'
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
name: 'ACP Browse (Discover Providers)',
|
|
1374
|
+
slug: 'acp-browse',
|
|
1375
|
+
file: '/api/skills/acp-browse',
|
|
1376
|
+
riskTier: 1,
|
|
1377
|
+
desc: 'Find specialist agents before posting bounties.'
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
name: 'ACP Bounty (Post Work Request)',
|
|
1381
|
+
slug: 'acp-bounty-post',
|
|
1382
|
+
file: '/api/skills/acp-bounty-post',
|
|
1383
|
+
riskTier: 3,
|
|
1384
|
+
desc: 'Post a bounty with explicit USDC budget (strict opt-in).'
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
name: 'ACP Bounty (Poll + Match Lifecycle)',
|
|
1388
|
+
slug: 'acp-bounty-poll',
|
|
1389
|
+
file: '/api/skills/acp-bounty-poll',
|
|
1390
|
+
riskTier: 2,
|
|
1391
|
+
desc: 'Poll candidates/jobs and surface deliverables.'
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
name: 'ACP Fulfillment (Earn USDC → PodVault)',
|
|
1395
|
+
slug: 'acp-fulfill-and-route',
|
|
1396
|
+
file: '/api/skills/acp-fulfill-and-route',
|
|
1397
|
+
riskTier: 3,
|
|
1398
|
+
desc: 'Fulfill jobs and route earnings into PodVault (strict opt-in).'
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
name: 'ApeClaw Receipt Recorder',
|
|
1402
|
+
slug: 'apeclaw-receipt-recorder',
|
|
1403
|
+
file: '/api/skills/apeclaw-receipt-recorder',
|
|
1404
|
+
riskTier: 2,
|
|
1405
|
+
desc: 'Anchor audit receipts onchain (ReceiptRegistry).'
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
name: 'ApeClaw Bridge Relay',
|
|
1409
|
+
slug: 'apeclaw-bridge-relay',
|
|
1410
|
+
file: '/api/skills/apeclaw-bridge-relay',
|
|
1411
|
+
riskTier: 2,
|
|
1412
|
+
desc: 'Bridge execution wrapper with confirm phrases and caps.'
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: 'Otherside Navigator',
|
|
1416
|
+
slug: 'otherside-navigator',
|
|
1417
|
+
file: '/api/skills/otherside-navigator',
|
|
1418
|
+
riskTier: 3,
|
|
1419
|
+
desc: 'Mac mini Pod loop (dry-run scaffold; strict opt-in).'
|
|
1420
|
+
}
|
|
1421
|
+
];
|
|
1422
|
+
|
|
1423
|
+
var statTotal = document.getElementById('statTotal');
|
|
1424
|
+
var statVetted = document.getElementById('statVetted');
|
|
1425
|
+
var statOnchain = document.getElementById('statOnchain');
|
|
1426
|
+
var statContributed = document.getElementById('statContributed');
|
|
1427
|
+
|
|
1428
|
+
function setStatCountUp(el, val) {
|
|
1429
|
+
if (!el) return;
|
|
1430
|
+
el.removeAttribute('data-countup-started');
|
|
1431
|
+
el.setAttribute('data-countup', String(val));
|
|
1432
|
+
el.textContent = '0';
|
|
1433
|
+
if (window.acTriggerCountUp) window.acTriggerCountUp(el);
|
|
1434
|
+
setTimeout(function () {
|
|
1435
|
+
if (!el.getAttribute('data-countup-started') || el.textContent === '0') {
|
|
1436
|
+
el.textContent = fmtInt(val);
|
|
1437
|
+
el.setAttribute('data-countup-started', '1');
|
|
1438
|
+
}
|
|
1439
|
+
}, 2500);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Load global stats from API
|
|
1443
|
+
(function loadGlobalStats() {
|
|
1444
|
+
fetch(apiBase + '/api/skills/stats', { headers: { 'accept': 'application/json' } })
|
|
1445
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1446
|
+
.then(function (d) {
|
|
1447
|
+
if (!d || !d.ok) return;
|
|
1448
|
+
setStatCountUp(statTotal, d.total || 0);
|
|
1449
|
+
setStatCountUp(statVetted, d.vetted || 0);
|
|
1450
|
+
setStatCountUp(statOnchain, d.onchain || 0);
|
|
1451
|
+
setStatCountUp(statContributed, d.user || 0);
|
|
1452
|
+
})
|
|
1453
|
+
.catch(function () {
|
|
1454
|
+
setStatCountUp(statTotal, 10032);
|
|
1455
|
+
setStatCountUp(statVetted, 10009);
|
|
1456
|
+
setStatCountUp(statOnchain, 7056);
|
|
1457
|
+
setStatCountUp(statContributed, 0);
|
|
1458
|
+
});
|
|
1459
|
+
})();
|
|
1460
|
+
|
|
1461
|
+
var seedAll = seed.slice();
|
|
1462
|
+
var seedSearch = document.getElementById('seedSearch');
|
|
1463
|
+
var seedList = document.getElementById('seedList');
|
|
1464
|
+
var seedBadge = document.getElementById('seedBadge');
|
|
1465
|
+
function matchesSeed(s, q) {
|
|
1466
|
+
if (!q) return true;
|
|
1467
|
+
var t = q.trim().toLowerCase();
|
|
1468
|
+
if (!t) return true;
|
|
1469
|
+
var hay = [s.name, s.slug].map(function (x) { return String(x || '').toLowerCase(); }).join(' ');
|
|
1470
|
+
return hay.indexOf(t) !== -1;
|
|
1471
|
+
}
|
|
1472
|
+
function renderSeed(items) {
|
|
1473
|
+
if (!seedList) return;
|
|
1474
|
+
var arr = Array.isArray(items) ? items : [];
|
|
1475
|
+
if (seedBadge) seedBadge.textContent = arr.length ? ('FOUND ' + String(arr.length)) : 'NONE';
|
|
1476
|
+
if (!arr.length) {
|
|
1477
|
+
seedList.innerHTML = (
|
|
1478
|
+
'<div class="item">' +
|
|
1479
|
+
'<div>' +
|
|
1480
|
+
'<strong>No seed matches</strong> <span class="pill med">tip</span>' +
|
|
1481
|
+
'<div class="meta">Try clearing search, or browse the imported library below.</div>' +
|
|
1482
|
+
'</div>' +
|
|
1483
|
+
'<div class="links">' +
|
|
1484
|
+
'<a class="pill" href="#imported-library">Imported</a>' +
|
|
1485
|
+
'</div>' +
|
|
1486
|
+
'</div>'
|
|
1487
|
+
);
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
seedList.innerHTML = arr.map(function (s) {
|
|
1491
|
+
var raw = 'https://raw.githubusercontent.com/simplefarmer69/ape-claw/main' + s.file;
|
|
1492
|
+
var rpc = localStorage.getItem('apeclaw_v2_rpc') || '';
|
|
1493
|
+
var reg = localStorage.getItem('apeclaw_v2_registry') || '';
|
|
1494
|
+
var pubCmd = (rpc && reg)
|
|
1495
|
+
? ('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')
|
|
1496
|
+
: ('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');
|
|
1497
|
+
return (
|
|
1498
|
+
'<div class="item">' +
|
|
1499
|
+
'<div>' +
|
|
1500
|
+
'<strong>' + escapeHtml(s.name) + '</strong> ' + pillForRisk(s.riskTier) +
|
|
1501
|
+
'<div class="meta">' + escapeHtml(s.desc) + '</div>' +
|
|
1502
|
+
'<div class="meta">' + escapeHtml('slug: ' + s.slug) + '</div>' +
|
|
1503
|
+
'</div>' +
|
|
1504
|
+
'<div class="links">' +
|
|
1505
|
+
'<a class="pill" href="' + escapeHtml(s.file) + '" target="_blank" rel="noopener">JSON</a>' +
|
|
1506
|
+
'<a class="pill" href="' + escapeHtml(raw) + '" target="_blank" rel="noopener">Raw</a>' +
|
|
1507
|
+
'<a class="pill" href="#" data-copy="' + escapeHtml(pubCmd) + '">Copy publish</a>' +
|
|
1508
|
+
'<a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Docs</a>' +
|
|
1509
|
+
'</div>' +
|
|
1510
|
+
'</div>'
|
|
1511
|
+
);
|
|
1512
|
+
}).join('');
|
|
1513
|
+
}
|
|
1514
|
+
renderSeed(seedAll);
|
|
1515
|
+
if (seedSearch) {
|
|
1516
|
+
seedSearch.addEventListener('input', function () {
|
|
1517
|
+
var q = String(seedSearch.value || '');
|
|
1518
|
+
var filtered = seedAll.filter(function (s) { return matchesSeed(s, q); });
|
|
1519
|
+
renderSeed(filtered);
|
|
1520
|
+
if (statTotal) statTotal.textContent = fmtInt(filtered.length);
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function renderImported(items, publishedBySlug) {
|
|
1525
|
+
var out = document.getElementById('importedList');
|
|
1526
|
+
if (!out) return;
|
|
1527
|
+
if (!items || !items.length) {
|
|
1528
|
+
out.innerHTML = (
|
|
1529
|
+
'<div class="skill-card" style="min-height:180px;--i:0">' +
|
|
1530
|
+
'<div class="card-shine"></div>' +
|
|
1531
|
+
'<div class="skill-tier-bar" data-tier="unknown"></div>' +
|
|
1532
|
+
'<div class="skill-card-inner">' +
|
|
1533
|
+
'<div class="skill-nft-badge offchain"><span class="nft-pulse"></span> NO RESULTS</div>' +
|
|
1534
|
+
'<div class="skill-title">No imported matches</div>' +
|
|
1535
|
+
'<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>' +
|
|
1536
|
+
'<div class="skill-foot"><a class="pill" href="/docs?doc=SKILLCARDS_AND_IMPORTER.md" data-keep-query="1">Docs</a></div>' +
|
|
1537
|
+
'</div>' +
|
|
1538
|
+
'</div>'
|
|
1539
|
+
);
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// Best-effort cache for showing descriptions/bindings from SkillCard JSON.
|
|
1544
|
+
if (!window.__apeclawSkillcardCache) window.__apeclawSkillcardCache = {};
|
|
1545
|
+
var cache = window.__apeclawSkillcardCache;
|
|
1546
|
+
|
|
1547
|
+
function shortHash(h) {
|
|
1548
|
+
var s = String(h || '').trim();
|
|
1549
|
+
if (!s) return '';
|
|
1550
|
+
if (s.length <= 14) return s;
|
|
1551
|
+
return s.slice(0, 10) + '…' + s.slice(-4);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function guessSummary(it) {
|
|
1555
|
+
// Index.json often has no description; give the user something meaningful.
|
|
1556
|
+
var name = String(it && it.name ? it.name : '').toLowerCase();
|
|
1557
|
+
var slug = String(it && it.slug ? it.slug : '').toLowerCase();
|
|
1558
|
+
var s = name + ' ' + slug;
|
|
1559
|
+
if (s.indexOf('bounty') !== -1) return 'ACP workflow: discover/post/poll/fulfill bounties (value-moving; strict opt-in).';
|
|
1560
|
+
if (s.indexOf('bridge') !== -1) return 'Cross-chain workflow: quote and execute bridging with caps and confirmations.';
|
|
1561
|
+
if (s.indexOf('nft') !== -1 || s.indexOf('opensea') !== -1) return 'NFT workflow: search/list/quote/buy with allowlist + policy gates.';
|
|
1562
|
+
if (s.indexOf('wallet') !== -1 || s.indexOf('transfer') !== -1 || s.indexOf('swap') !== -1) return 'Wallet workflow: signing, transfers, swaps, or account management.';
|
|
1563
|
+
return 'SkillCard imported from an external library. Click Details to view what it does.';
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function detailsHtml(it, pub, cardObj) {
|
|
1567
|
+
var title = escapeHtml(it.name || it.slug || 'Skill');
|
|
1568
|
+
var slug = escapeHtml(it.slug || '');
|
|
1569
|
+
var ver = escapeHtml(it.version || '');
|
|
1570
|
+
var source = escapeHtml(it.source || it.mode || '');
|
|
1571
|
+
var srcUrl = String(it.sourceUrl || '').trim();
|
|
1572
|
+
var risk = Number(it.riskTier || 2);
|
|
1573
|
+
var desc = '';
|
|
1574
|
+
var bindings = 0;
|
|
1575
|
+
var inputs = '';
|
|
1576
|
+
if (cardObj && typeof cardObj === 'object') {
|
|
1577
|
+
desc = String(cardObj.description || cardObj.desc || '').trim();
|
|
1578
|
+
bindings = Array.isArray(cardObj.bindings) ? cardObj.bindings.length : 0;
|
|
1579
|
+
try {
|
|
1580
|
+
var req = (cardObj.inputs_schema && cardObj.inputs_schema.required && Array.isArray(cardObj.inputs_schema.required)) ? cardObj.inputs_schema.required : [];
|
|
1581
|
+
inputs = req.length ? req.join(', ') : '';
|
|
1582
|
+
} catch (e) {}
|
|
1583
|
+
}
|
|
1584
|
+
if (!desc) desc = String(it.description || it.desc || '').trim() || guessSummary(it);
|
|
1585
|
+
|
|
1586
|
+
var onchain = (pub && pub.skillId)
|
|
1587
|
+
? ('<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>')
|
|
1588
|
+
: '<span class="pill off">Offchain</span>';
|
|
1589
|
+
var mintTx = (pub && pub.txs && pub.txs.mint) ? String(pub.txs.mint) : '';
|
|
1590
|
+
var pubTx = (pub && pub.txs && pub.txs.publish) ? String(pub.txs.publish) : '';
|
|
1591
|
+
var uri = (pub && pub.uri) ? String(pub.uri) : '';
|
|
1592
|
+
|
|
1593
|
+
var html = '';
|
|
1594
|
+
html += '<div class="note"><strong>' + title + '</strong></div>';
|
|
1595
|
+
html += '<div class="note">' + onchain + ' ' + pillForRisk(risk) + '</div>';
|
|
1596
|
+
html += '<div class="note">slug: <code>' + slug + '</code> · v<code>' + ver + '</code>' + (source ? (' · source: <code>' + source + '</code>') : '') + '</div>';
|
|
1597
|
+
if (desc) html += '<div class="note" style="margin-top:10px">' + escapeHtml(desc) + '</div>';
|
|
1598
|
+
if (bindings) html += '<div class="note">bindings: <code>' + String(bindings) + '</code></div>';
|
|
1599
|
+
if (inputs) html += '<div class="note">inputs: <code>' + escapeHtml(inputs) + '</code></div>';
|
|
1600
|
+
if (srcUrl) html += '<div class="note">source: <a href="' + escapeHtml(srcUrl) + '" target="_blank" rel="noopener">' + escapeHtml(srcUrl) + '</a></div>';
|
|
1601
|
+
if (uri) html += '<div class="note">onchain uri: <code>' + escapeHtml(uri) + '</code></div>';
|
|
1602
|
+
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>';
|
|
1603
|
+
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>';
|
|
1604
|
+
if (slug) {
|
|
1605
|
+
var installCmd = 'curl -fsSL "' + apiBase + '/api/skills/' + slug + '" -o ./' + slug + '.json';
|
|
1606
|
+
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">';
|
|
1607
|
+
html += '<strong style="color:#cfff04">Install this skill</strong><br>';
|
|
1608
|
+
html += '<code style="font-size:.75rem;word-break:break-all">' + escapeHtml(installCmd) + '</code>';
|
|
1609
|
+
html += '</div>';
|
|
1610
|
+
}
|
|
1611
|
+
return html;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function openDetails(it, pub, fileHref) {
|
|
1615
|
+
var key = String(it.slug || fileHref || it.fileName || '');
|
|
1616
|
+
if (key && cache[key]) {
|
|
1617
|
+
openModal('Skill details', detailsHtml(it, pub, cache[key]), function () { closeModal(); }, function () {});
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
openModal('Skill details', '<div class="loading">Loading SkillCard JSON…</div>', function () { closeModal(); }, function () {});
|
|
1621
|
+
var slug = String(it.slug || '').trim();
|
|
1622
|
+
if (!slug) {
|
|
1623
|
+
openModal('Skill details', detailsHtml(it, pub, null), function () { closeModal(); }, function () {});
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
fetch(apiBase + '/api/skills/' + encodeURIComponent(slug), { headers: { 'accept': 'application/json' } })
|
|
1627
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1628
|
+
.then(function (j) {
|
|
1629
|
+
var card = (j && j.card) ? j.card : (j && j.skill) ? j.skill : null;
|
|
1630
|
+
if (key) cache[key] = card || {};
|
|
1631
|
+
openModal('Skill details', detailsHtml(it, pub, card), function () { closeModal(); }, function () {});
|
|
1632
|
+
})
|
|
1633
|
+
.catch(function () {
|
|
1634
|
+
openModal('Skill details', detailsHtml(it, pub, null), function () { closeModal(); }, function () {});
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function showInstallModal(it) {
|
|
1639
|
+
var rawSlug = String(it.slug || '');
|
|
1640
|
+
var slug = escapeHtml(rawSlug);
|
|
1641
|
+
var fileName = it.fileName || rawSlug + '.json';
|
|
1642
|
+
var ghRaw = 'https://raw.githubusercontent.com/simplefarmer69/ape-claw/main/data/skills/' + encodeURIComponent(rawSlug) + '.json';
|
|
1643
|
+
var installCmd = 'curl -fsSL "' + apiBase + '/api/skills/' + rawSlug + '" -o ./' + fileName;
|
|
1644
|
+
var installCmdSimple = 'curl -fsSL "' + ghRaw + '" -o ./' + rawSlug + '.json';
|
|
1645
|
+
var html = '';
|
|
1646
|
+
html += '<div style="margin-bottom:16px"><strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong></div>';
|
|
1647
|
+
html += '<div class="note" style="margin-bottom:14px;color:var(--muted);font-size:12px">Run one of the commands below in your terminal to install this skill.</div>';
|
|
1648
|
+
html += '<div style="margin-bottom:6px;color:var(--lime);font-size:11px;font-weight:700;letter-spacing:.5px">INSTALL VIA API</div>';
|
|
1649
|
+
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:14px">';
|
|
1650
|
+
html += '<code style="font-size:11px;color:var(--cyan);word-break:break-all;line-height:1.6;display:block">' + escapeHtml(installCmd) + '</code>';
|
|
1651
|
+
html += '</div>';
|
|
1652
|
+
html += '<div style="margin-bottom:6px;color:var(--lime);font-size:11px;font-weight:700;letter-spacing:.5px">ALTERNATIVE (GitHub raw)</div>';
|
|
1653
|
+
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:14px">';
|
|
1654
|
+
html += '<code style="font-size:11px;color:var(--cyan);word-break:break-all;line-height:1.6;display:block">' + escapeHtml(installCmdSimple) + '</code>';
|
|
1655
|
+
html += '</div>';
|
|
1656
|
+
html += '<div style="display:flex;gap:8px;flex-wrap:wrap">';
|
|
1657
|
+
html += '<a class="pill install-btn" href="#" data-copy-raw="install" style="font-weight:800;font-size:11px;padding:8px 14px">COPY INSTALL CMD</a>';
|
|
1658
|
+
html += '<a class="pill" href="#" data-copy-raw="quick" style="font-size:11px;padding:8px 14px">Copy quick download</a>';
|
|
1659
|
+
if (it.sourceUrl) {
|
|
1660
|
+
html += '<a class="pill" href="' + escapeHtml(it.sourceUrl) + '" target="_blank" rel="noopener" style="font-size:11px;padding:8px 14px">View source</a>';
|
|
1661
|
+
}
|
|
1662
|
+
if (it.onchainTokenId) {
|
|
1663
|
+
html += '<a class="pill nft" href="https://apescan.io/token/0x6c8e75568a3470f8c8e6f8ed29d5fd61c7b7e11d?a=' + escapeHtml(String(it.onchainTokenId)) + '" target="_blank" rel="noopener" style="font-size:11px;padding:8px 14px">View on ApeScan</a>';
|
|
1664
|
+
}
|
|
1665
|
+
html += '</div>';
|
|
1666
|
+
openModal('Install Skill', html, function () { closeModal(); }, function () {});
|
|
1667
|
+
try {
|
|
1668
|
+
var btns = document.querySelectorAll('[data-copy-raw]');
|
|
1669
|
+
for (var bi = 0; bi < btns.length; bi++) {
|
|
1670
|
+
(function(btn) {
|
|
1671
|
+
btn.addEventListener('click', function (ev) {
|
|
1672
|
+
ev.preventDefault();
|
|
1673
|
+
var which = btn.getAttribute('data-copy-raw');
|
|
1674
|
+
copyText(which === 'quick' ? installCmdSimple : installCmd);
|
|
1675
|
+
showToast('Copied to clipboard');
|
|
1676
|
+
});
|
|
1677
|
+
})(btns[bi]);
|
|
1678
|
+
}
|
|
1679
|
+
} catch (e) {}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function showJsonModal(it, pub) {
|
|
1683
|
+
var obj = {
|
|
1684
|
+
name: it.name || '',
|
|
1685
|
+
slug: it.slug || '',
|
|
1686
|
+
description: it.description || it.desc || '',
|
|
1687
|
+
version: it.version || '1',
|
|
1688
|
+
riskTier: it.riskTier || 2,
|
|
1689
|
+
source: it.source || '',
|
|
1690
|
+
};
|
|
1691
|
+
if (it.sourceUrl) obj.sourceUrl = it.sourceUrl;
|
|
1692
|
+
if (pub && pub.skillId) {
|
|
1693
|
+
obj.onchain = { skillId: pub.skillId, network: 'apechain', chainId: 33139 };
|
|
1694
|
+
if (pub.txs && pub.txs.mint) obj.onchain.mintTx = pub.txs.mint;
|
|
1695
|
+
if (pub.txs && pub.txs.publish) obj.onchain.publishTx = pub.txs.publish;
|
|
1696
|
+
}
|
|
1697
|
+
var json = JSON.stringify(obj, null, 2);
|
|
1698
|
+
var html = '';
|
|
1699
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">';
|
|
1700
|
+
html += '<strong style="color:#cfff04;font-size:14px">' + escapeHtml(it.name || it.slug) + '</strong>';
|
|
1701
|
+
html += '<a class="pill install-btn" href="#" data-copy-raw="json" style="font-size:9px;padding:5px 10px;font-weight:800">COPY JSON</a>';
|
|
1702
|
+
html += '</div>';
|
|
1703
|
+
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>';
|
|
1704
|
+
openModal('Skill JSON', html, function () { closeModal(); }, function () {});
|
|
1705
|
+
try {
|
|
1706
|
+
var copyBtn = document.querySelector('[data-copy-raw="json"]');
|
|
1707
|
+
if (copyBtn) {
|
|
1708
|
+
copyBtn.addEventListener('click', function (ev) {
|
|
1709
|
+
ev.preventDefault();
|
|
1710
|
+
copyText(json);
|
|
1711
|
+
showToast('Copied to clipboard');
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
} catch (e) {}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
out.innerHTML = items.map(function (it) {
|
|
1718
|
+
var risk = Number(it.riskTier || 2);
|
|
1719
|
+
var pub = (publishedBySlug && it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
1720
|
+
var sourceHref = String(it.sourceUrl || '').trim();
|
|
1721
|
+
var slugRaw = String(it.slug || '').trim();
|
|
1722
|
+
var skillGetUrl = '';
|
|
1723
|
+
try {
|
|
1724
|
+
skillGetUrl = (apiBase || 'https://apeclaw.ai') + '/api/skills/' + encodeURIComponent(slugRaw);
|
|
1725
|
+
} catch (e) {
|
|
1726
|
+
skillGetUrl = 'https://apeclaw.ai/api/skills/' + encodeURIComponent(slugRaw);
|
|
1727
|
+
}
|
|
1728
|
+
var githubHref = '';
|
|
1729
|
+
try {
|
|
1730
|
+
if (String(it.source || '') === 'seed' && it.fileName) {
|
|
1731
|
+
githubHref = 'https://github.com/simplefarmer69/ape-claw/blob/main/skillcards/seed/' + encodeURIComponent(String(it.fileName));
|
|
1732
|
+
} else if (sourceHref && sourceHref.indexOf('https://github.com/') === 0) {
|
|
1733
|
+
// If we have a direct GitHub file URL, also provide the repo root.
|
|
1734
|
+
var m = sourceHref.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\//);
|
|
1735
|
+
githubHref = m ? ('https://github.com/' + m[1] + '/' + m[2]) : 'https://github.com/openclaw/skills';
|
|
1736
|
+
} else {
|
|
1737
|
+
githubHref = 'https://github.com/openclaw/skills';
|
|
1738
|
+
}
|
|
1739
|
+
} catch (e) { githubHref = 'https://github.com/openclaw/skills'; }
|
|
1740
|
+
var title = it.name || it.slug || 'Imported Skill';
|
|
1741
|
+
var desc = String(it.description || it.desc || '').trim();
|
|
1742
|
+
if (!desc) desc = guessSummary(it);
|
|
1743
|
+
|
|
1744
|
+
var isOnchain = !!(pub && pub.skillId);
|
|
1745
|
+
var mintTx = (pub && pub.txs && pub.txs.mint) ? String(pub.txs.mint) : '';
|
|
1746
|
+
var publishTx = (pub && pub.txs && pub.txs.publish) ? String(pub.txs.publish) : '';
|
|
1747
|
+
var riskBkt = riskBucket(risk);
|
|
1748
|
+
var riskLabel = riskBkt === 'low' ? 'LOW' : riskBkt === 'high' ? 'HIGH' : riskBkt === 'med' ? 'MED' : '—';
|
|
1749
|
+
var slugLine = (it.slug || '') + (it.version ? (' · v' + it.version) : '') + (it.source ? (' · ' + it.source) : '');
|
|
1750
|
+
var cardIdx = items.indexOf(it);
|
|
1751
|
+
|
|
1752
|
+
var chainHtml = '';
|
|
1753
|
+
if (isOnchain) {
|
|
1754
|
+
chainHtml = '<div class="skill-chain-data">';
|
|
1755
|
+
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>';
|
|
1756
|
+
chainHtml += '</div>';
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
return (
|
|
1760
|
+
'<div class="skill-card' + (isOnchain ? ' onchain-card' : '') + (riskBkt === 'high' ? ' risk-high' : '') + '" data-slug="' + escapeHtml(it.slug || '') + '" style="--i:' + (cardIdx % 12) + '">' +
|
|
1761
|
+
'<div class="card-shine"></div>' +
|
|
1762
|
+
'<div class="card-scanline"></div>' +
|
|
1763
|
+
'<div class="skill-tier-bar" data-tier="' + riskBkt + '"></div>' +
|
|
1764
|
+
'<div class="skill-card-inner">' +
|
|
1765
|
+
'<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:10px">' +
|
|
1766
|
+
(isOnchain
|
|
1767
|
+
? ('<a class="skill-nft-badge onchain" href="https://apescan.io/token/0x6c8e75568a3470f8c8e6f8ed29d5fd61c7b7e11d?a=' + escapeHtml(String(pub.skillId)) + '" target="_blank" rel="noopener">' +
|
|
1768
|
+
'<span class="nft-pulse"></span> NFT #' + escapeHtml(String(pub.skillId)) + ' ↗</a>')
|
|
1769
|
+
: ('<div class="skill-nft-badge offchain"><span class="nft-pulse"></span> OFFCHAIN</div>')) +
|
|
1770
|
+
'<div class="skill-risk ' + riskBkt + '">' + riskLabel + '</div>' +
|
|
1771
|
+
'</div>' +
|
|
1772
|
+
'<div class="skill-title">' + escapeHtml(title) + '</div>' +
|
|
1773
|
+
'<div class="skill-slug">' + escapeHtml(slugLine) + '</div>' +
|
|
1774
|
+
'<div class="skill-desc">' + escapeHtml(desc) + '</div>' +
|
|
1775
|
+
chainHtml +
|
|
1776
|
+
'<div class="skill-foot">' +
|
|
1777
|
+
(slugRaw ? '<a class="pill install-btn" href="' + escapeHtml(skillGetUrl) + '" data-action="install" title="Get install command">Install</a>' : '') +
|
|
1778
|
+
(slugRaw ? '<a class="pill" href="' + escapeHtml(skillGetUrl) + '" data-action="json" title="View skill metadata">JSON</a>' : '') +
|
|
1779
|
+
(githubHref ? ('<a class="pill" href="' + escapeHtml(githubHref) + '" target="_blank" rel="noopener">GitHub</a>') : '') +
|
|
1780
|
+
(sourceHref ? ('<a class="pill" href="' + escapeHtml(sourceHref) + '" target="_blank" rel="noopener">Source</a>') : '') +
|
|
1781
|
+
(slugRaw ? '<a class="pill" href="' + escapeHtml(skillGetUrl) + '" data-action="details">Details</a>' : '') +
|
|
1782
|
+
'</div>' +
|
|
1783
|
+
'</div>' +
|
|
1784
|
+
'</div>'
|
|
1785
|
+
);
|
|
1786
|
+
}).join('');
|
|
1787
|
+
|
|
1788
|
+
try {
|
|
1789
|
+
var cards = out.querySelectorAll('.skill-card');
|
|
1790
|
+
for (var i = 0; i < cards.length; i++) {
|
|
1791
|
+
(function (idx) {
|
|
1792
|
+
var card = cards[idx];
|
|
1793
|
+
card.addEventListener('click', function (ev) {
|
|
1794
|
+
var t = (ev && ev.target && ev.target.closest) ? ev.target : null;
|
|
1795
|
+
var target = t ? t.closest('[data-action]') : null;
|
|
1796
|
+
if (!target) return;
|
|
1797
|
+
ev.preventDefault();
|
|
1798
|
+
var action = target.getAttribute('data-action');
|
|
1799
|
+
var it = items[idx];
|
|
1800
|
+
var pub = (publishedBySlug && it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
1801
|
+
try {
|
|
1802
|
+
if (it && it.slug) {
|
|
1803
|
+
var slug = encodeURIComponent(String(it.slug));
|
|
1804
|
+
if (action === 'install') history.replaceState(null, '', '#install=' + slug);
|
|
1805
|
+
else if (action === 'json') history.replaceState(null, '', '#json=' + slug);
|
|
1806
|
+
else if (action === 'details') history.replaceState(null, '', '#skill=' + slug);
|
|
1807
|
+
}
|
|
1808
|
+
} catch (e) {}
|
|
1809
|
+
if (action === 'install') showInstallModal(it);
|
|
1810
|
+
else if (action === 'json') showJsonModal(it, pub);
|
|
1811
|
+
else if (action === 'details') openDetails(it, pub, '');
|
|
1812
|
+
});
|
|
1813
|
+
})(i);
|
|
1814
|
+
}
|
|
1815
|
+
} catch (e) {}
|
|
1816
|
+
|
|
1817
|
+
try {
|
|
1818
|
+
var allCards = out.querySelectorAll('.skill-card');
|
|
1819
|
+
for (var ci = 0; ci < allCards.length; ci++) {
|
|
1820
|
+
(function (card) {
|
|
1821
|
+
var shine = card.querySelector('.card-shine');
|
|
1822
|
+
card.addEventListener('mousemove', function (e) {
|
|
1823
|
+
var rect = card.getBoundingClientRect();
|
|
1824
|
+
var x = e.clientX - rect.left;
|
|
1825
|
+
var y = e.clientY - rect.top;
|
|
1826
|
+
var px = (x / rect.width) * 100;
|
|
1827
|
+
var py = (y / rect.height) * 100;
|
|
1828
|
+
var rotY = ((px - 50) / 50) * 8;
|
|
1829
|
+
var rotX = ((py - 50) / 50) * -6;
|
|
1830
|
+
card.style.transform = 'perspective(800px) rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg) translateY(-8px) scale(1.02)';
|
|
1831
|
+
if (shine) { shine.style.setProperty('--mx', px + '%'); shine.style.setProperty('--my', py + '%'); }
|
|
1832
|
+
});
|
|
1833
|
+
card.addEventListener('mouseleave', function () {
|
|
1834
|
+
card.style.transform = '';
|
|
1835
|
+
});
|
|
1836
|
+
})(allCards[ci]);
|
|
1837
|
+
}
|
|
1838
|
+
} catch (e) {}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function normalize(s) { return String(s || '').toLowerCase(); }
|
|
1842
|
+
function matchesSearch(it, q) {
|
|
1843
|
+
if (!q) return true;
|
|
1844
|
+
var s = q.trim().toLowerCase();
|
|
1845
|
+
if (!s) return true;
|
|
1846
|
+
var hay = [
|
|
1847
|
+
it.name, it.slug, it.description, it.desc, it.source, it.mode, it.sourceUrl, it.fileName,
|
|
1848
|
+
].map(normalize).join(' ');
|
|
1849
|
+
return hay.indexOf(s) !== -1;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// Optional: render imported skillcards if present (best-effort).
|
|
1853
|
+
var importedSearch = document.getElementById('importedSearch');
|
|
1854
|
+
var riskFilter = document.getElementById('riskFilter');
|
|
1855
|
+
var onlyOnchain = document.getElementById('onlyOnchain');
|
|
1856
|
+
var onlyVetted = document.getElementById('onlyVetted');
|
|
1857
|
+
var importedBadge = document.getElementById('importedBadge');
|
|
1858
|
+
var importedAll = [];
|
|
1859
|
+
var publishedBySlug = {};
|
|
1860
|
+
var PAGE_SIZE = 51;
|
|
1861
|
+
var currentPage = 1;
|
|
1862
|
+
var lastFiltered = [];
|
|
1863
|
+
|
|
1864
|
+
var pgBar = document.getElementById('paginationBar');
|
|
1865
|
+
var pgPrev = document.getElementById('pgPrev');
|
|
1866
|
+
var pgNext = document.getElementById('pgNext');
|
|
1867
|
+
var pgInfo = document.getElementById('pgInfo');
|
|
1868
|
+
|
|
1869
|
+
function totalPages() { return Math.max(1, Math.ceil(lastFiltered.length / PAGE_SIZE)); }
|
|
1870
|
+
|
|
1871
|
+
function renderPage() {
|
|
1872
|
+
var start = (currentPage - 1) * PAGE_SIZE;
|
|
1873
|
+
var pageItems = lastFiltered.slice(start, start + PAGE_SIZE);
|
|
1874
|
+
renderImported(pageItems, publishedBySlug);
|
|
1875
|
+
|
|
1876
|
+
var tp = totalPages();
|
|
1877
|
+
if (pgBar) pgBar.style.display = lastFiltered.length > PAGE_SIZE ? 'flex' : 'none';
|
|
1878
|
+
if (pgInfo) pgInfo.textContent = 'Page ' + currentPage + ' of ' + tp + ' (' + lastFiltered.length + ' skills)';
|
|
1879
|
+
if (pgPrev) pgPrev.disabled = currentPage <= 1;
|
|
1880
|
+
if (pgNext) pgNext.disabled = currentPage >= tp;
|
|
1881
|
+
|
|
1882
|
+
var grid = document.getElementById('importedList');
|
|
1883
|
+
if (grid) grid.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function goToPage(page) {
|
|
1887
|
+
var tp = totalPages();
|
|
1888
|
+
currentPage = Math.max(1, Math.min(page, tp));
|
|
1889
|
+
renderPage();
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
if (pgPrev) pgPrev.addEventListener('click', function () { goToPage(currentPage - 1); });
|
|
1893
|
+
if (pgNext) pgNext.addEventListener('click', function () { goToPage(currentPage + 1); });
|
|
1894
|
+
|
|
1895
|
+
function parseSkillTime(it) {
|
|
1896
|
+
if (!it) return 0;
|
|
1897
|
+
var raw = it.addedAt || it.importedAt || it.createdAt || it.updatedAt || '';
|
|
1898
|
+
if (!raw) return 0;
|
|
1899
|
+
var ts = Date.parse(String(raw));
|
|
1900
|
+
return Number.isFinite(ts) ? ts : 0;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function applyImportedFilters() {
|
|
1904
|
+
var q = importedSearch ? String(importedSearch.value || '') : '';
|
|
1905
|
+
var bucket = riskFilter ? String(riskFilter.value || 'all') : 'all';
|
|
1906
|
+
var onchainOnly = Boolean(onlyOnchain && onlyOnchain.checked);
|
|
1907
|
+
var vettedOnly = Boolean(!onlyVetted || onlyVetted.checked);
|
|
1908
|
+
var filtered = importedAll.filter(function (it) {
|
|
1909
|
+
if (!matchesSearch(it, q)) return false;
|
|
1910
|
+
var b = riskBucket(it.riskTier || 2);
|
|
1911
|
+
if (bucket !== 'all' && b !== bucket) return false;
|
|
1912
|
+
if (vettedOnly) {
|
|
1913
|
+
var ok = (it && (it.vettedOk === true || (it.vetted && it.vetted.ok === true)));
|
|
1914
|
+
if (!ok) return false;
|
|
1915
|
+
}
|
|
1916
|
+
if (onchainOnly) {
|
|
1917
|
+
var pub = (it && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
1918
|
+
if (!(pub && pub.skillId)) return false;
|
|
1919
|
+
}
|
|
1920
|
+
return true;
|
|
1921
|
+
});
|
|
1922
|
+
filtered.sort(function (a, b) {
|
|
1923
|
+
var tA = parseSkillTime(a);
|
|
1924
|
+
var tB = parseSkillTime(b);
|
|
1925
|
+
if (tA !== tB) return tB - tA;
|
|
1926
|
+
var iA = Number(a && a._indexOrder || 0);
|
|
1927
|
+
var iB = Number(b && b._indexOrder || 0);
|
|
1928
|
+
return iB - iA;
|
|
1929
|
+
});
|
|
1930
|
+
lastFiltered = filtered;
|
|
1931
|
+
currentPage = 1;
|
|
1932
|
+
renderPage();
|
|
1933
|
+
if (statVetted) statVetted.textContent = fmtInt(filtered.length);
|
|
1934
|
+
if (importedBadge) {
|
|
1935
|
+
var publishedCount = 0;
|
|
1936
|
+
try {
|
|
1937
|
+
for (var i = 0; i < filtered.length; i++) {
|
|
1938
|
+
var p = (filtered[i] && filtered[i].slug) ? publishedBySlug[filtered[i].slug] : null;
|
|
1939
|
+
if (p && p.skillId) publishedCount++;
|
|
1940
|
+
}
|
|
1941
|
+
} catch (e) {}
|
|
1942
|
+
importedBadge.textContent = filtered.length ? ('FOUND ' + filtered.length + (publishedCount ? (' · ONCHAIN ' + publishedCount) : '')) : 'NONE';
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// Hash-based fallback: if JS handlers fail, card links still carry intent.
|
|
1947
|
+
// Supports deep links like:
|
|
1948
|
+
// - #install=<slug>
|
|
1949
|
+
// - #json=<slug>
|
|
1950
|
+
// - #skill=<slug>
|
|
1951
|
+
function parseSkillHash() {
|
|
1952
|
+
try {
|
|
1953
|
+
var h = String(location.hash || '').replace(/^#/, '').trim();
|
|
1954
|
+
if (!h) return null;
|
|
1955
|
+
var m = h.match(/^(install|json|skill)=(.+)$/);
|
|
1956
|
+
if (!m) return null;
|
|
1957
|
+
return { action: m[1], slug: decodeURIComponent(m[2] || '') };
|
|
1958
|
+
} catch (e) { return null; }
|
|
1959
|
+
}
|
|
1960
|
+
function openFromHash() {
|
|
1961
|
+
var parsed = parseSkillHash();
|
|
1962
|
+
if (!parsed || !parsed.slug) return;
|
|
1963
|
+
if (!importedAll || !importedAll.length) return; // wait until data is loaded
|
|
1964
|
+
var slug = String(parsed.slug || '').trim();
|
|
1965
|
+
if (!slug) return;
|
|
1966
|
+
var it = null;
|
|
1967
|
+
for (var i = 0; i < importedAll.length; i++) {
|
|
1968
|
+
if (importedAll[i] && importedAll[i].slug === slug) { it = importedAll[i]; break; }
|
|
1969
|
+
}
|
|
1970
|
+
if (!it) return;
|
|
1971
|
+
var pub = (publishedBySlug && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
1972
|
+
if (parsed.action === 'install') showInstallModal(it);
|
|
1973
|
+
else if (parsed.action === 'json') showJsonModal(it, pub);
|
|
1974
|
+
else openDetails(it, pub, '');
|
|
1975
|
+
}
|
|
1976
|
+
window.addEventListener('hashchange', function () {
|
|
1977
|
+
try { openFromHash(); } catch (e) {}
|
|
1978
|
+
});
|
|
1979
|
+
fetch(apiBase + '/api/skills/search?limit=5000', { headers: { 'accept': 'application/json' } })
|
|
1980
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1981
|
+
.then(function (j) {
|
|
1982
|
+
if (!j || !j.ok) {
|
|
1983
|
+
if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
var results = j.results || [];
|
|
1987
|
+
importedAll = results.map(function (s, idx) {
|
|
1988
|
+
return {
|
|
1989
|
+
name: s.name || s.slug || 'Skill',
|
|
1990
|
+
slug: s.slug || '',
|
|
1991
|
+
description: s.description || s.desc || '',
|
|
1992
|
+
desc: s.description || s.desc || '',
|
|
1993
|
+
riskTier: s.riskTier || 2,
|
|
1994
|
+
source: s.source || 'imported',
|
|
1995
|
+
sourceUrl: s.sourceUrl || '',
|
|
1996
|
+
fileName: s.fileName || '',
|
|
1997
|
+
version: s.version || '1',
|
|
1998
|
+
importOk: true,
|
|
1999
|
+
vettedOk: Boolean(s.vettedOk !== false && s.vetted !== false),
|
|
2000
|
+
addedAt: s.addedAt || s.importedAt || s.createdAt || '',
|
|
2001
|
+
importedAt: s.importedAt || s.addedAt || '',
|
|
2002
|
+
createdAt: s.createdAt || '',
|
|
2003
|
+
_indexOrder: idx,
|
|
2004
|
+
onchainTokenId: s.onchainTokenId || null,
|
|
2005
|
+
onchainMintTx: s.onchainMintTx || null,
|
|
2006
|
+
onchainPublishTx: s.onchainPublishTx || null
|
|
2007
|
+
};
|
|
2008
|
+
});
|
|
2009
|
+
publishedBySlug = {};
|
|
2010
|
+
for (var pi = 0; pi < importedAll.length; pi++) {
|
|
2011
|
+
var si = importedAll[pi];
|
|
2012
|
+
if (si && si.slug && si.onchainTokenId) {
|
|
2013
|
+
publishedBySlug[si.slug] = {
|
|
2014
|
+
skillId: si.onchainTokenId,
|
|
2015
|
+
txs: { mint: si.onchainMintTx || null, publish: si.onchainPublishTx || null }
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
applyImportedFilters();
|
|
2020
|
+
if (importedBadge) importedBadge.textContent = importedAll.length ? (importedAll.length + ' SKILLS') : 'NONE';
|
|
2021
|
+
try { openFromHash(); } catch (e) {}
|
|
2022
|
+
})
|
|
2023
|
+
.catch(function () {
|
|
2024
|
+
if (importedBadge) importedBadge.textContent = 'UNAVAILABLE';
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
if (importedSearch) {
|
|
2028
|
+
importedSearch.addEventListener('input', applyImportedFilters);
|
|
2029
|
+
}
|
|
2030
|
+
if (riskFilter) riskFilter.addEventListener('change', applyImportedFilters);
|
|
2031
|
+
if (onlyOnchain) onlyOnchain.addEventListener('change', applyImportedFilters);
|
|
2032
|
+
if (onlyVetted) onlyVetted.addEventListener('change', applyImportedFilters);
|
|
2033
|
+
|
|
2034
|
+
// ── User-submitted skills (server-side library) ────────────────────────
|
|
2035
|
+
var authAgentId = document.getElementById('authAgentId');
|
|
2036
|
+
var authAgentToken = document.getElementById('authAgentToken');
|
|
2037
|
+
var saveAuthBtn = document.getElementById('saveAuthBtn');
|
|
2038
|
+
var checkAuthBtn = document.getElementById('checkAuthBtn');
|
|
2039
|
+
var clearAuthBtn = document.getElementById('clearAuthBtn');
|
|
2040
|
+
var authStatus = document.getElementById('authStatus');
|
|
2041
|
+
|
|
2042
|
+
var skillJson = document.getElementById('skillJson');
|
|
2043
|
+
var skillSourceUrl = document.getElementById('skillSourceUrl');
|
|
2044
|
+
var loadFromUrlBtn = document.getElementById('loadFromUrlBtn');
|
|
2045
|
+
var validateSkillBtn = document.getElementById('validateSkillBtn');
|
|
2046
|
+
var addSkillBtn = document.getElementById('addSkillBtn');
|
|
2047
|
+
var skillPreview = document.getElementById('skillPreview');
|
|
2048
|
+
var addSkillStatus = document.getElementById('addSkillStatus');
|
|
2049
|
+
var templateSelect = document.getElementById('templateSelect');
|
|
2050
|
+
var loadTemplateBtn = document.getElementById('loadTemplateBtn');
|
|
2051
|
+
var formatJsonBtn = document.getElementById('formatJsonBtn');
|
|
2052
|
+
|
|
2053
|
+
var userBadge = document.getElementById('userBadge');
|
|
2054
|
+
var userSkillSearch = document.getElementById('userSkillSearch');
|
|
2055
|
+
var userSkillList = document.getElementById('userSkillList');
|
|
2056
|
+
|
|
2057
|
+
var v2RpcUrl = document.getElementById('v2RpcUrl');
|
|
2058
|
+
var v2SkillNft = document.getElementById('v2SkillNft');
|
|
2059
|
+
var v2Registry = document.getElementById('v2Registry');
|
|
2060
|
+
var v2Intents = document.getElementById('v2Intents');
|
|
2061
|
+
var v2Receipts = document.getElementById('v2Receipts');
|
|
2062
|
+
var royaltyReceiver = document.getElementById('royaltyReceiver');
|
|
2063
|
+
var royaltyBps = document.getElementById('royaltyBps');
|
|
2064
|
+
var saveV2SettingsBtn = document.getElementById('saveV2SettingsBtn');
|
|
2065
|
+
var v2SettingsNote = document.getElementById('v2SettingsNote');
|
|
2066
|
+
|
|
2067
|
+
var intentPayload = document.getElementById('intentPayload');
|
|
2068
|
+
var intentExpiresAt = document.getElementById('intentExpiresAt');
|
|
2069
|
+
var intentCancelId = document.getElementById('intentCancelId');
|
|
2070
|
+
var copyIntentCreateBtn = document.getElementById('copyIntentCreateBtn');
|
|
2071
|
+
var copyIntentCancelBtn = document.getElementById('copyIntentCancelBtn');
|
|
2072
|
+
var intentCreatePreview = document.getElementById('intentCreatePreview');
|
|
2073
|
+
var intentCancelPreview = document.getElementById('intentCancelPreview');
|
|
2074
|
+
|
|
2075
|
+
var receiptTraceId = document.getElementById('receiptTraceId');
|
|
2076
|
+
var copyReceiptGetBtn = document.getElementById('copyReceiptGetBtn');
|
|
2077
|
+
var fetchReceiptGetBtn = document.getElementById('fetchReceiptGetBtn');
|
|
2078
|
+
var receiptGetPreview = document.getElementById('receiptGetPreview');
|
|
2079
|
+
var receiptGetResult = document.getElementById('receiptGetResult');
|
|
2080
|
+
|
|
2081
|
+
var userSkillsAll = [];
|
|
2082
|
+
|
|
2083
|
+
function setText(el, v) { if (el) el.textContent = String(v || ''); }
|
|
2084
|
+
function setHtml(el, v) { if (el) el.innerHTML = String(v || ''); }
|
|
2085
|
+
function flash(msg, isErr) {
|
|
2086
|
+
setText(addSkillStatus, msg);
|
|
2087
|
+
if (!addSkillStatus) return;
|
|
2088
|
+
try { addSkillStatus.style.color = isErr ? '#ffd1d1' : '#d4e6fa'; } catch (e) {}
|
|
2089
|
+
setTimeout(function(){ setText(addSkillStatus, ''); }, isErr ? 2400 : 1400);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function loadAuth() {
|
|
2093
|
+
try {
|
|
2094
|
+
var id = localStorage.getItem('apeclaw_skill_agent_id') || '';
|
|
2095
|
+
var tok = localStorage.getItem('apeclaw_skill_agent_token') || '';
|
|
2096
|
+
if (authAgentId) authAgentId.value = id;
|
|
2097
|
+
if (authAgentToken) authAgentToken.value = tok;
|
|
2098
|
+
if (authStatus) authStatus.textContent = (id && tok) ? ('Auth: set for ' + id) : 'Auth: not set';
|
|
2099
|
+
} catch (e) {
|
|
2100
|
+
if (authStatus) authStatus.textContent = 'Auth: unavailable';
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
function saveAuth() {
|
|
2104
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2105
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2106
|
+
try {
|
|
2107
|
+
localStorage.setItem('apeclaw_skill_agent_id', id);
|
|
2108
|
+
localStorage.setItem('apeclaw_skill_agent_token', tok);
|
|
2109
|
+
} catch (e) {}
|
|
2110
|
+
if (authStatus) authStatus.textContent = (id && tok) ? ('Auth: set for ' + id) : 'Auth: not set';
|
|
2111
|
+
}
|
|
2112
|
+
function clearAuth() {
|
|
2113
|
+
try {
|
|
2114
|
+
localStorage.removeItem('apeclaw_skill_agent_id');
|
|
2115
|
+
localStorage.removeItem('apeclaw_skill_agent_token');
|
|
2116
|
+
} catch (e) {}
|
|
2117
|
+
if (authAgentId) authAgentId.value = '';
|
|
2118
|
+
if (authAgentToken) authAgentToken.value = '';
|
|
2119
|
+
if (authStatus) authStatus.textContent = 'Auth: not set';
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
function checkAuth() {
|
|
2123
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2124
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2125
|
+
if (!(id && tok)) {
|
|
2126
|
+
if (authStatus) authStatus.textContent = 'Auth: not set';
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (authStatus) authStatus.textContent = 'Auth: checking...';
|
|
2130
|
+
fetch(apiBase + '/api/skillcards/user/auth-check', { headers: authHeaders() })
|
|
2131
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
2132
|
+
.then(function (out) {
|
|
2133
|
+
if (out.ok && out.json && out.json.ok) {
|
|
2134
|
+
if (authStatus) authStatus.textContent = 'Auth: verified (' + String(out.json.mode || 'ok') + ') for ' + id;
|
|
2135
|
+
} else {
|
|
2136
|
+
if (authStatus) authStatus.textContent = 'Auth: invalid for ' + id;
|
|
2137
|
+
}
|
|
2138
|
+
})
|
|
2139
|
+
.catch(function () {
|
|
2140
|
+
if (authStatus) authStatus.textContent = 'Auth: check failed';
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
function loadV2Settings() {
|
|
2145
|
+
try {
|
|
2146
|
+
var rpc = localStorage.getItem('apeclaw_v2_rpc') || '';
|
|
2147
|
+
var nft = localStorage.getItem('apeclaw_v2_skillnft') || '';
|
|
2148
|
+
var reg = localStorage.getItem('apeclaw_v2_registry') || '';
|
|
2149
|
+
var intents = localStorage.getItem('apeclaw_v2_intents') || '';
|
|
2150
|
+
var receipts = localStorage.getItem('apeclaw_v2_receipts') || '';
|
|
2151
|
+
var rr = localStorage.getItem('apeclaw_v2_royalty_receiver') || '';
|
|
2152
|
+
var rb = localStorage.getItem('apeclaw_v2_royalty_bps') || '500';
|
|
2153
|
+
if (v2RpcUrl) v2RpcUrl.value = rpc;
|
|
2154
|
+
if (v2SkillNft) v2SkillNft.value = nft;
|
|
2155
|
+
if (v2Registry) v2Registry.value = reg;
|
|
2156
|
+
if (v2Intents) v2Intents.value = intents;
|
|
2157
|
+
if (v2Receipts) v2Receipts.value = receipts;
|
|
2158
|
+
if (royaltyReceiver) royaltyReceiver.value = rr;
|
|
2159
|
+
if (royaltyBps) royaltyBps.value = rb;
|
|
2160
|
+
if (v2SettingsNote) v2SettingsNote.textContent = 'Mint/publish commands use env var `APE_CLAW_V2_PRIVATE_KEY` (never paste keys here).';
|
|
2161
|
+
} catch (e) {}
|
|
2162
|
+
}
|
|
2163
|
+
function saveV2Settings() {
|
|
2164
|
+
try {
|
|
2165
|
+
localStorage.setItem('apeclaw_v2_rpc', v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '');
|
|
2166
|
+
localStorage.setItem('apeclaw_v2_skillnft', v2SkillNft ? String(v2SkillNft.value || '').trim() : '');
|
|
2167
|
+
localStorage.setItem('apeclaw_v2_registry', v2Registry ? String(v2Registry.value || '').trim() : '');
|
|
2168
|
+
localStorage.setItem('apeclaw_v2_intents', v2Intents ? String(v2Intents.value || '').trim() : '');
|
|
2169
|
+
localStorage.setItem('apeclaw_v2_receipts', v2Receipts ? String(v2Receipts.value || '').trim() : '');
|
|
2170
|
+
localStorage.setItem('apeclaw_v2_royalty_receiver', royaltyReceiver ? String(royaltyReceiver.value || '').trim() : '');
|
|
2171
|
+
localStorage.setItem('apeclaw_v2_royalty_bps', royaltyBps ? String(royaltyBps.value || '').trim() : '');
|
|
2172
|
+
} catch (e) {}
|
|
2173
|
+
flash('Saved v2 settings');
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
function autofillV2SettingsFromBackend() {
|
|
2177
|
+
// Only fill empty fields to avoid clobbering explicit operator input.
|
|
2178
|
+
fetch(apiBase + '/api/v2/config', { headers: { 'accept': 'application/json' } })
|
|
2179
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
2180
|
+
.then(function (out) {
|
|
2181
|
+
if (!out.ok || !out.json || !out.json.ok) return;
|
|
2182
|
+
var dep = out.json.deployment || {};
|
|
2183
|
+
var rr = out.json.receiptsRead || {};
|
|
2184
|
+
var podVaultAddr = out.json.podVault || out.json.record?.podVault || dep?.podVault || null;
|
|
2185
|
+
if (v2RpcUrl && !String(v2RpcUrl.value || '').trim() && rr && rr.rpcUrl) v2RpcUrl.value = String(rr.rpcUrl);
|
|
2186
|
+
if (v2SkillNft && !String(v2SkillNft.value || '').trim() && dep && dep.skillNft) v2SkillNft.value = String(dep.skillNft);
|
|
2187
|
+
if (v2Registry && !String(v2Registry.value || '').trim() && dep && dep.registry) v2Registry.value = String(dep.registry);
|
|
2188
|
+
if (v2Intents && !String(v2Intents.value || '').trim() && dep && dep.intents) v2Intents.value = String(dep.intents);
|
|
2189
|
+
if (v2Receipts && !String(v2Receipts.value || '').trim() && (rr && rr.receiptsAddress)) v2Receipts.value = String(rr.receiptsAddress);
|
|
2190
|
+
if (royaltyReceiver && !String(royaltyReceiver.value || '').trim() && podVaultAddr) royaltyReceiver.value = String(podVaultAddr);
|
|
2191
|
+
// Persist if anything was filled.
|
|
2192
|
+
saveV2Settings();
|
|
2193
|
+
renderIntentPreviews();
|
|
2194
|
+
renderReceiptPreview();
|
|
2195
|
+
try {
|
|
2196
|
+
if (v2SettingsNote) {
|
|
2197
|
+
var note = 'Mint/publish commands use env var `APE_CLAW_V2_PRIVATE_KEY` (never paste keys here).';
|
|
2198
|
+
if (rr && rr.inferredRpc) note += ' v2 settings auto-filled from backend.';
|
|
2199
|
+
v2SettingsNote.textContent = note;
|
|
2200
|
+
}
|
|
2201
|
+
} catch (e) {}
|
|
2202
|
+
})
|
|
2203
|
+
.catch(function () {});
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function populateOnchainPanel() {
|
|
2207
|
+
var contractNames = {
|
|
2208
|
+
skillNft: 'SkillNFT',
|
|
2209
|
+
registry: 'SkillRegistry',
|
|
2210
|
+
intents: 'IntentRegistry',
|
|
2211
|
+
receiptsAddress: 'ReceiptRegistry',
|
|
2212
|
+
policyEngine: 'PolicyEngine',
|
|
2213
|
+
agentAccount: 'AgentAccount',
|
|
2214
|
+
podVault: 'PodVault'
|
|
2215
|
+
};
|
|
2216
|
+
|
|
2217
|
+
fetch(apiBase + '/api/v2/config', { headers: { 'accept': 'application/json' } })
|
|
2218
|
+
.then(function (r) { return r.json(); })
|
|
2219
|
+
.then(function (j) {
|
|
2220
|
+
if (!j || !j.ok) return;
|
|
2221
|
+
var dep = j.deployment || {};
|
|
2222
|
+
var rr = j.receiptsRead || {};
|
|
2223
|
+
var list = document.getElementById('ocContractsList');
|
|
2224
|
+
var badge = document.getElementById('ocContractsBadge');
|
|
2225
|
+
if (!list) return;
|
|
2226
|
+
|
|
2227
|
+
var addrs = {};
|
|
2228
|
+
if (dep.skillNft) addrs.skillNft = dep.skillNft;
|
|
2229
|
+
if (dep.registry) addrs.registry = dep.registry;
|
|
2230
|
+
if (dep.intents) addrs.intents = dep.intents;
|
|
2231
|
+
if (rr.receiptsAddress || dep.receipts) addrs.receiptsAddress = rr.receiptsAddress || dep.receipts;
|
|
2232
|
+
if (dep.policy || dep.policyEngine) addrs.policyEngine = dep.policy || dep.policyEngine;
|
|
2233
|
+
if (dep.agentAccount) addrs.agentAccount = dep.agentAccount;
|
|
2234
|
+
var pv = j.podVault || dep.podVault || null;
|
|
2235
|
+
if (pv) addrs.podVault = pv;
|
|
2236
|
+
|
|
2237
|
+
var count = Object.keys(addrs).length;
|
|
2238
|
+
if (count === 0) {
|
|
2239
|
+
list.textContent = 'No deployment data available. Ensure the server has v2 environment variables configured.';
|
|
2240
|
+
if (badge) { badge.textContent = 'OFFLINE'; badge.className = 'step-badge'; }
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
var html = '<div style="display:grid;grid-template-columns:1fr;gap:6px">';
|
|
2245
|
+
Object.keys(addrs).forEach(function (key) {
|
|
2246
|
+
var addr = addrs[key];
|
|
2247
|
+
var name = contractNames[key] || key;
|
|
2248
|
+
var short = addr.slice(0, 6) + '\u2026' + addr.slice(-4);
|
|
2249
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 10px;background:rgba(255,255,255,.03);border-radius:6px">';
|
|
2250
|
+
html += '<span style="color:var(--fg);font-size:12px;font-weight:500">' + name + '</span>';
|
|
2251
|
+
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>';
|
|
2252
|
+
html += '</div>';
|
|
2253
|
+
});
|
|
2254
|
+
html += '</div>';
|
|
2255
|
+
list.innerHTML = html;
|
|
2256
|
+
if (badge) { badge.textContent = count + ' LIVE'; badge.className = 'step-badge done'; }
|
|
2257
|
+
})
|
|
2258
|
+
.catch(function () {
|
|
2259
|
+
var list = document.getElementById('ocContractsList');
|
|
2260
|
+
if (list) list.textContent = 'Could not load deployment data.';
|
|
2261
|
+
});
|
|
2262
|
+
|
|
2263
|
+
fetch(apiBase + '/api/skills/stats', { headers: { 'accept': 'application/json' } })
|
|
2264
|
+
.then(function (r) { return r.json(); })
|
|
2265
|
+
.then(function (d) {
|
|
2266
|
+
if (!d || !d.ok) return;
|
|
2267
|
+
var nfts = document.getElementById('ocStatNfts');
|
|
2268
|
+
var published = document.getElementById('ocStatOnchain');
|
|
2269
|
+
var vetted = document.getElementById('ocStatVetted');
|
|
2270
|
+
if (nfts) setStatCountUp(nfts, d.onchain || 1023);
|
|
2271
|
+
if (published) setStatCountUp(published, d.onchain || 1023);
|
|
2272
|
+
if (vetted) setStatCountUp(vetted, d.vetted || 1014);
|
|
2273
|
+
})
|
|
2274
|
+
.catch(function () {
|
|
2275
|
+
var nfts = document.getElementById('ocStatNfts');
|
|
2276
|
+
var published = document.getElementById('ocStatOnchain');
|
|
2277
|
+
var vetted = document.getElementById('ocStatVetted');
|
|
2278
|
+
if (nfts) setStatCountUp(nfts, 1023);
|
|
2279
|
+
if (published) setStatCountUp(published, 1023);
|
|
2280
|
+
if (vetted) setStatCountUp(vetted, 1014);
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
function getIntentCreateCmd() {
|
|
2285
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
2286
|
+
var intents = v2Intents ? String(v2Intents.value || '').trim() : '';
|
|
2287
|
+
var payloadRaw = intentPayload ? String(intentPayload.value || '').trim() : '';
|
|
2288
|
+
var expRaw = intentExpiresAt ? String(intentExpiresAt.value || '').trim() : '';
|
|
2289
|
+
var exp = expRaw ? (' --expiresAt ' + expRaw) : '';
|
|
2290
|
+
if (!payloadRaw) payloadRaw = '{"type":"task","goal":"..."}';
|
|
2291
|
+
// Validate JSON (we still pass it as a string).
|
|
2292
|
+
try { JSON.parse(payloadRaw); } catch (e) {}
|
|
2293
|
+
if (!rpc) rpc = '<url>';
|
|
2294
|
+
if (!intents) intents = '<addr>';
|
|
2295
|
+
return 'ape-claw v2 intent create --rpc "' + rpc + '" --privateKey "$APE_CLAW_V2_PRIVATE_KEY" --intents "' + intents + '" --payload \'' + payloadRaw.replace(/'/g, "\\\\'") + '\'' + exp + ' --json';
|
|
2296
|
+
}
|
|
2297
|
+
function getIntentCancelCmd() {
|
|
2298
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
2299
|
+
var intents = v2Intents ? String(v2Intents.value || '').trim() : '';
|
|
2300
|
+
var id = intentCancelId ? String(intentCancelId.value || '').trim() : '';
|
|
2301
|
+
if (!rpc) rpc = '<url>';
|
|
2302
|
+
if (!intents) intents = '<addr>';
|
|
2303
|
+
if (!id) id = '<id>';
|
|
2304
|
+
return 'ape-claw v2 intent cancel --rpc \"' + rpc + '\" --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --intents \"' + intents + '\" --intentId ' + id + ' --json';
|
|
2305
|
+
}
|
|
2306
|
+
function renderIntentPreviews() {
|
|
2307
|
+
if (intentCreatePreview) {
|
|
2308
|
+
var c1 = getIntentCreateCmd();
|
|
2309
|
+
intentCreatePreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c1.replace(/^ape-claw\\s+/,''));
|
|
2310
|
+
}
|
|
2311
|
+
if (intentCancelPreview) {
|
|
2312
|
+
var c2 = getIntentCancelCmd();
|
|
2313
|
+
intentCancelPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c2.replace(/^ape-claw\\s+/,''));
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function getReceiptGetCmd() {
|
|
2318
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
2319
|
+
var receipts = v2Receipts ? String(v2Receipts.value || '').trim() : '';
|
|
2320
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
2321
|
+
if (!rpc) rpc = '<url>';
|
|
2322
|
+
if (!receipts) receipts = '<addr>';
|
|
2323
|
+
if (!traceId) traceId = '<traceId>';
|
|
2324
|
+
return 'ape-claw v2 receipt get --rpc \"' + rpc + '\" --receipts \"' + receipts + '\" --traceId \"' + traceId.replace(/\"/g, '\\\\\"') + '\" --json';
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function renderReceiptPreview() {
|
|
2328
|
+
if (receiptGetPreview) {
|
|
2329
|
+
var c = getReceiptGetCmd();
|
|
2330
|
+
receiptGetPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c.replace(/^ape-claw\\s+/,''));
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
function fetchReceipt() {
|
|
2335
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
2336
|
+
if (!traceId) { showToast('Enter a traceId first', true); return; }
|
|
2337
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: fetching...';
|
|
2338
|
+
fetch(apiBase + '/api/v2/receipt/get?traceId=' + encodeURIComponent(traceId), { headers: { 'accept': 'application/json' } })
|
|
2339
|
+
.then(function (r) { return r.text().then(function (t) { return { ok: r.ok, status: r.status, text: t }; }); })
|
|
2340
|
+
.then(function (out) {
|
|
2341
|
+
var obj = null;
|
|
2342
|
+
try { obj = JSON.parse(out.text || ''); } catch (e) {}
|
|
2343
|
+
if (!out.ok) {
|
|
2344
|
+
var msg = (obj && (obj.error || obj.reason)) ? (obj.error || obj.reason) : ('HTTP ' + out.status);
|
|
2345
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: error: ' + msg;
|
|
2346
|
+
showToast('Receipt fetch failed', true);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: ' + JSON.stringify(obj, null, 2);
|
|
2350
|
+
showToast('Receipt fetched');
|
|
2351
|
+
})
|
|
2352
|
+
.catch(function (e) {
|
|
2353
|
+
if (receiptGetResult) receiptGetResult.textContent = 'Result: error: ' + (e && e.message ? e.message : 'failed');
|
|
2354
|
+
showToast('Receipt fetch failed', true);
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function getReceiptGetCmd() {
|
|
2359
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
2360
|
+
var receipts = v2Receipts ? String(v2Receipts.value || '').trim() : '';
|
|
2361
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
2362
|
+
if (!rpc) rpc = '<url>';
|
|
2363
|
+
if (!receipts) receipts = '<addr>';
|
|
2364
|
+
if (!traceId) traceId = '<traceId>';
|
|
2365
|
+
// Use double quotes for traceId; escape " for shell safety.
|
|
2366
|
+
return 'ape-claw v2 receipt get --rpc \"' + rpc + '\" --receipts \"' + receipts + '\" --traceId \"' + traceId.replace(/\"/g, '\\\\\"') + '\" --json';
|
|
2367
|
+
}
|
|
2368
|
+
function renderReceiptPreview() {
|
|
2369
|
+
if (receiptGetPreview) {
|
|
2370
|
+
var c = getReceiptGetCmd();
|
|
2371
|
+
receiptGetPreview.innerHTML = '<span class="k">ape-claw</span> ' + escapeHtml(c.replace(/^ape-claw\\s+/, ''));
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
function setReceiptResult(msg, isErr) {
|
|
2375
|
+
if (!receiptGetResult) return;
|
|
2376
|
+
receiptGetResult.textContent = String(msg || 'Result: —');
|
|
2377
|
+
try { receiptGetResult.style.color = isErr ? '#ffd1d1' : '#d4e6fa'; } catch (e) {}
|
|
2378
|
+
}
|
|
2379
|
+
function fetchReceiptGet() {
|
|
2380
|
+
var traceId = receiptTraceId ? String(receiptTraceId.value || '').trim() : '';
|
|
2381
|
+
if (!traceId) { showToast('Enter a traceId first', true); return; }
|
|
2382
|
+
setReceiptResult('Result: fetching...', false);
|
|
2383
|
+
fetch(apiBase + '/api/v2/receipt/get?traceId=' + encodeURIComponent(traceId), { headers: { 'accept': 'application/json' } })
|
|
2384
|
+
.then(function (r) { return r.text().then(function (t) { return { ok: r.ok, status: r.status, text: t }; }); })
|
|
2385
|
+
.then(function (out) {
|
|
2386
|
+
var j = null;
|
|
2387
|
+
try { j = JSON.parse(out.text || '{}'); } catch (e) {}
|
|
2388
|
+
if (!out.ok || !j || !j.ok) {
|
|
2389
|
+
var msg = (j && (j.error || j.reason)) ? (j.error || j.reason) : ('HTTP ' + out.status);
|
|
2390
|
+
setReceiptResult('Result: error\n' + msg, true);
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
setReceiptResult('Result:\n' + JSON.stringify(j, null, 2), false);
|
|
2394
|
+
})
|
|
2395
|
+
.catch(function (e) {
|
|
2396
|
+
setReceiptResult('Result: error\n' + (e && e.message ? e.message : 'fetch failed'), true);
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
function authHeaders() {
|
|
2401
|
+
var h = { 'content-type': 'application/json', 'accept': 'application/json' };
|
|
2402
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2403
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2404
|
+
if (id && tok) {
|
|
2405
|
+
h['x-agent-id'] = id;
|
|
2406
|
+
h['x-agent-token'] = tok;
|
|
2407
|
+
}
|
|
2408
|
+
return h;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
function parseSkillCardJson() {
|
|
2412
|
+
var raw = skillJson ? String(skillJson.value || '') : '';
|
|
2413
|
+
if (!raw.trim()) throw new Error('paste a SkillCard JSON first');
|
|
2414
|
+
var obj;
|
|
2415
|
+
try { obj = JSON.parse(raw); } catch (e) { throw new Error('invalid JSON'); }
|
|
2416
|
+
if (!obj || typeof obj !== 'object') throw new Error('SkillCard must be a JSON object');
|
|
2417
|
+
var name = String(obj.name || '').trim();
|
|
2418
|
+
if (!name) throw new Error('skillcard.name is required');
|
|
2419
|
+
var slug = toSlug(obj.slug || name);
|
|
2420
|
+
if (!slug) throw new Error('skillcard.slug is required');
|
|
2421
|
+
var version = String(obj.version || '1.0.0').trim();
|
|
2422
|
+
if (!/^[0-9]+(\.[0-9]+){0,3}([+\-][0-9A-Za-z._-]+)?$/.test(version)) throw new Error('skillcard.version must look like semver');
|
|
2423
|
+
var riskTier = Number(obj && obj.constraints && typeof obj.constraints.riskTier !== 'undefined' ? obj.constraints.riskTier : (obj.riskTier || 2));
|
|
2424
|
+
if (!isFinite(riskTier)) riskTier = 2;
|
|
2425
|
+
riskTier = Math.max(1, Math.min(3, Math.round(riskTier)));
|
|
2426
|
+
var bindings = Array.isArray(obj.bindings) ? obj.bindings : [];
|
|
2427
|
+
return { obj: obj, name: name, slug: slug, version: version, riskTier: riskTier, bindingsCount: bindings.length };
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
function containsSecretLikeText(raw) {
|
|
2431
|
+
var s = String(raw || '');
|
|
2432
|
+
// Lightweight heuristic: we only warn; we do not block if user insists.
|
|
2433
|
+
return /(privateKey|private_key|mnemonic|seed phrase|apiKey|api_key|x-agent-token|authorization\\s*:|bearer\\s+|-----BEGIN)/i.test(s);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
function renderPreview() {
|
|
2437
|
+
try {
|
|
2438
|
+
var p = parseSkillCardJson();
|
|
2439
|
+
var warn = containsSecretLikeText(skillJson ? skillJson.value : '') ? ' · WARNING: looks like secrets present' : '';
|
|
2440
|
+
setText(skillPreview, 'Preview: ' + p.name + ' · slug: ' + p.slug + ' · v' + p.version + ' · risk: ' + p.riskTier + ' · bindings: ' + p.bindingsCount + warn);
|
|
2441
|
+
} catch (e) {
|
|
2442
|
+
setText(skillPreview, 'Preview: ' + (e && e.message ? e.message : 'invalid'));
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
function formatJson() {
|
|
2447
|
+
if (!skillJson) return;
|
|
2448
|
+
try {
|
|
2449
|
+
var p = parseSkillCardJson();
|
|
2450
|
+
skillJson.value = JSON.stringify(p.obj, null, 2);
|
|
2451
|
+
renderPreview();
|
|
2452
|
+
flash('Formatted JSON');
|
|
2453
|
+
} catch (e) {
|
|
2454
|
+
flash('Format failed: ' + (e && e.message ? e.message : 'invalid'), true);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
function loadTemplate(kind) {
|
|
2459
|
+
var riskTier = 2;
|
|
2460
|
+
var desc = 'Describe what this skill does.';
|
|
2461
|
+
var bindings = [{ type: 'cli', command: 'echo \"replace with your command\"' }];
|
|
2462
|
+
if (kind === 'low') { riskTier = 1; desc = 'Read-only / browse / summarize. No spend, no writes.'; }
|
|
2463
|
+
if (kind === 'high') { riskTier = 3; desc = 'Spend / escrow / irreversible writes. Strict opt-in.'; }
|
|
2464
|
+
if (kind === 'med') { riskTier = 2; desc = 'Writes / automation with caps. Confirm phrases recommended.'; }
|
|
2465
|
+
var obj = {
|
|
2466
|
+
name: 'New Skill',
|
|
2467
|
+
slug: 'new-skill',
|
|
2468
|
+
version: '1.0.0',
|
|
2469
|
+
description: desc,
|
|
2470
|
+
bindings: bindings,
|
|
2471
|
+
constraints: { riskTier: riskTier },
|
|
2472
|
+
};
|
|
2473
|
+
if (skillJson) skillJson.value = JSON.stringify(obj, null, 2);
|
|
2474
|
+
renderPreview();
|
|
2475
|
+
flash('Loaded template');
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
function loadFromUrl() {
|
|
2479
|
+
var url = skillSourceUrl ? String(skillSourceUrl.value || '').trim() : '';
|
|
2480
|
+
if (!url) { flash('Enter a URL first', true); return; }
|
|
2481
|
+
flash('Loading URL...');
|
|
2482
|
+
fetch(url, { headers: { 'accept': 'application/json' } })
|
|
2483
|
+
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
2484
|
+
.then(function (t) {
|
|
2485
|
+
if (skillJson) skillJson.value = t;
|
|
2486
|
+
renderPreview();
|
|
2487
|
+
flash('Loaded JSON from URL');
|
|
2488
|
+
})
|
|
2489
|
+
.catch(function (e) { flash('Load URL failed: ' + (e && e.message ? e.message : 'failed'), true); });
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
function matchesUserSkill(it, q) {
|
|
2493
|
+
if (!q) return true;
|
|
2494
|
+
var s = q.trim().toLowerCase();
|
|
2495
|
+
if (!s) return true;
|
|
2496
|
+
var hay = [it.name, it.slug, it.version, it.description].map(function (x) { return String(x || '').toLowerCase(); }).join(' ');
|
|
2497
|
+
return hay.indexOf(s) !== -1;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
function copyText(txt) {
|
|
2501
|
+
var s = String(txt || '');
|
|
2502
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2503
|
+
return navigator.clipboard.writeText(s).catch(function () { fallbackCopy(s); });
|
|
2504
|
+
}
|
|
2505
|
+
fallbackCopy(s);
|
|
2506
|
+
return Promise.resolve();
|
|
2507
|
+
}
|
|
2508
|
+
function fallbackCopy(s) {
|
|
2509
|
+
try {
|
|
2510
|
+
var ta = document.createElement('textarea');
|
|
2511
|
+
ta.value = s;
|
|
2512
|
+
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
|
2513
|
+
document.body.appendChild(ta);
|
|
2514
|
+
ta.select();
|
|
2515
|
+
document.execCommand('copy');
|
|
2516
|
+
document.body.removeChild(ta);
|
|
2517
|
+
} catch (e) {}
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function renderUserSkills(items) {
|
|
2521
|
+
if (!userSkillList) return;
|
|
2522
|
+
var arr = Array.isArray(items) ? items : [];
|
|
2523
|
+
if (userBadge) userBadge.textContent = arr.length ? ('FOUND ' + arr.length) : 'NONE';
|
|
2524
|
+
if (statContributed) statContributed.textContent = fmtInt(arr.length);
|
|
2525
|
+
if (!arr.length) {
|
|
2526
|
+
setHtml(userSkillList,
|
|
2527
|
+
'<div style="text-align:center;padding:32px 16px;color:var(--muted)">' +
|
|
2528
|
+
'<div style="font-size:32px;margin-bottom:12px;opacity:.4">📦</div>' +
|
|
2529
|
+
'<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:6px">No submissions yet</div>' +
|
|
2530
|
+
'<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>' +
|
|
2531
|
+
'</div>'
|
|
2532
|
+
);
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
setHtml(userSkillList, arr.map(function (it) {
|
|
2536
|
+
var risk = Number(it.riskTier || 2);
|
|
2537
|
+
var fileHref = it.slug ? ('/api/skills/' + encodeURIComponent(it.slug)) : '';
|
|
2538
|
+
var created = it.createdAt ? (' · ' + String(it.createdAt)) : '';
|
|
2539
|
+
var onchainMeta = (it.onchain && it.onchain.skillId) ? (' · onchain: skillId=' + String(it.onchain.skillId)) : '';
|
|
2540
|
+
var meta = ['slug: ' + (it.slug || '?'), 'v' + (it.version || '?')].join(' · ') + onchainMeta + created;
|
|
2541
|
+
var dlCmd = it.slug ? ('curl -fsSL \"' + apiBase + '/api/skills/' + encodeURIComponent(it.slug) + '\" -o \"./' + (it.fileName || it.slug + '.json') + '\"') : '';
|
|
2542
|
+
var rpc = v2RpcUrl ? String(v2RpcUrl.value || '').trim() : '';
|
|
2543
|
+
var nft = v2SkillNft ? String(v2SkillNft.value || '').trim() : '';
|
|
2544
|
+
var reg = v2Registry ? String(v2Registry.value || '').trim() : '';
|
|
2545
|
+
var rr = royaltyReceiver ? String(royaltyReceiver.value || '').trim() : '';
|
|
2546
|
+
var rb = royaltyBps ? String(royaltyBps.value || '').trim() : '';
|
|
2547
|
+
var mintCmd = (rpc && nft && reg)
|
|
2548
|
+
? ('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')
|
|
2549
|
+
: ('ape-claw v2 skill mint --rpc <url> --privateKey \"$APE_CLAW_V2_PRIVATE_KEY\" --skillNft <addr> --registry <addr> --json');
|
|
2550
|
+
var localFile = it.fileName || (it.slug ? it.slug + '.json' : '');
|
|
2551
|
+
var pubCmd = localFile
|
|
2552
|
+
? ((rpc && reg)
|
|
2553
|
+
? ('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')
|
|
2554
|
+
: ('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'))
|
|
2555
|
+
: '';
|
|
2556
|
+
return (
|
|
2557
|
+
'<div class="item">' +
|
|
2558
|
+
'<div>' +
|
|
2559
|
+
'<strong>' + escapeHtml(it.name || it.slug || 'Skill') + '</strong> ' + pillForRisk(risk) +
|
|
2560
|
+
'<div class="meta">' + escapeHtml(meta) + '</div>' +
|
|
2561
|
+
(it.description ? ('<div class="meta">' + escapeHtml(String(it.description)) + '</div>') : '') +
|
|
2562
|
+
'</div>' +
|
|
2563
|
+
'<div class="links">' +
|
|
2564
|
+
(fileHref ? ('<a class="pill" href="' + escapeHtml(fileHref) + '" target="_blank" rel="noopener">JSON</a>') : '') +
|
|
2565
|
+
(fileHref ? ('<a class="pill" href="' + escapeHtml(fileHref) + '" download>Download</a>') : '') +
|
|
2566
|
+
(dlCmd ? ('<a class="pill" href="#" data-copy="' + escapeHtml(dlCmd) + '">Copy curl</a>') : '') +
|
|
2567
|
+
('<a class="pill" href="#" data-copy="' + escapeHtml(mintCmd) + '">Copy mint</a>') +
|
|
2568
|
+
(pubCmd ? ('<a class="pill" href="#" data-copy="' + escapeHtml(pubCmd) + '">Copy publish</a>') : '') +
|
|
2569
|
+
(it.fileName ? ('<a class="pill" href="#" data-mark="' + escapeHtml(String(it.fileName)) + '">Set onchain</a>') : '') +
|
|
2570
|
+
(it.fileName ? ('<a class="pill" href="#" data-delete="' + escapeHtml(String(it.fileName)) + '">Delete</a>') : '') +
|
|
2571
|
+
'</div>' +
|
|
2572
|
+
'</div>'
|
|
2573
|
+
);
|
|
2574
|
+
}).join(''));
|
|
2575
|
+
|
|
2576
|
+
// Attach copy/delete handlers.
|
|
2577
|
+
// Handlers are delegated globally (see below).
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
function loadUserSkills() {
|
|
2581
|
+
fetch(apiBase + '/api/skillcards/user', { headers: { 'accept': 'application/json' } })
|
|
2582
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
2583
|
+
.then(function (j) {
|
|
2584
|
+
userSkillsAll = (j && j.skills && Array.isArray(j.skills)) ? j.skills : [];
|
|
2585
|
+
renderUserSkills(userSkillsAll);
|
|
2586
|
+
})
|
|
2587
|
+
.catch(function () {
|
|
2588
|
+
if (userBadge) userBadge.textContent = 'NONE';
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
function addUserSkill() {
|
|
2593
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2594
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2595
|
+
if (!(id && tok)) {
|
|
2596
|
+
flash('Set auth (x-agent-id/x-agent-token) to submit skills.', true);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
var parsed;
|
|
2600
|
+
try { parsed = parseSkillCardJson(); } catch (e) { flash(e.message || 'invalid', true); return; }
|
|
2601
|
+
if (containsSecretLikeText(skillJson ? skillJson.value : '')) {
|
|
2602
|
+
var ok = confirm('This SkillCard looks like it may contain secrets. SkillCards should be public. Continue anyway?');
|
|
2603
|
+
if (!ok) return;
|
|
2604
|
+
}
|
|
2605
|
+
var source = skillSourceUrl ? String(skillSourceUrl.value || '').trim() : '';
|
|
2606
|
+
fetch(apiBase + '/api/skillcards/user/add', {
|
|
2607
|
+
method: 'POST',
|
|
2608
|
+
headers: authHeaders(),
|
|
2609
|
+
body: JSON.stringify({ skillcard: parsed.obj, sourceUrl: source }),
|
|
2610
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
2611
|
+
.then(function (out) {
|
|
2612
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'submit failed');
|
|
2613
|
+
flash('Added: ' + (out.json.entry && out.json.entry.fileName ? out.json.entry.fileName : 'ok'));
|
|
2614
|
+
showToast('Skill added to library!');
|
|
2615
|
+
if (skillJson) skillJson.value = '';
|
|
2616
|
+
if (skillSourceUrl) skillSourceUrl.value = '';
|
|
2617
|
+
renderPreview();
|
|
2618
|
+
loadUserSkills();
|
|
2619
|
+
// Auto-open Step 4 (Your Submitted Skills) and update badge
|
|
2620
|
+
var step4 = document.querySelector('.step-card[data-step="4"]');
|
|
2621
|
+
if (step4) { step4.classList.add('open'); step4.scrollIntoView({behavior:'smooth',block:'nearest'}); }
|
|
2622
|
+
var submitBadge = document.getElementById('stepSubmitBadge');
|
|
2623
|
+
if (submitBadge) { submitBadge.textContent = 'DONE'; submitBadge.className = 'step-badge done'; }
|
|
2624
|
+
})
|
|
2625
|
+
.catch(function (e) {
|
|
2626
|
+
flash('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
function deleteUserSkill(fileName) {
|
|
2631
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2632
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2633
|
+
if (!(id && tok)) {
|
|
2634
|
+
flash('Set auth (x-agent-id/x-agent-token) to delete skills.', true);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
fetch(apiBase + '/api/skillcards/user/delete', {
|
|
2638
|
+
method: 'POST',
|
|
2639
|
+
headers: authHeaders(),
|
|
2640
|
+
body: JSON.stringify({ fileName: fileName }),
|
|
2641
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
2642
|
+
.then(function (out) {
|
|
2643
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'delete failed');
|
|
2644
|
+
flash('Deleted: ' + fileName);
|
|
2645
|
+
loadUserSkills();
|
|
2646
|
+
})
|
|
2647
|
+
.catch(function (e) {
|
|
2648
|
+
flash('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
function markOnchain(fileName) {
|
|
2653
|
+
var id = authAgentId ? String(authAgentId.value || '').trim() : '';
|
|
2654
|
+
var tok = authAgentToken ? String(authAgentToken.value || '').trim() : '';
|
|
2655
|
+
if (!(id && tok)) {
|
|
2656
|
+
flash('Set auth (x-agent-id/x-agent-token) to mark onchain status.', true);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
openModal(
|
|
2660
|
+
'Set onchain status',
|
|
2661
|
+
'<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>' +
|
|
2662
|
+
'<label>Skill ID (number)</label>' +
|
|
2663
|
+
'<input id="mSkillId" type="text" placeholder="e.g. 12">' +
|
|
2664
|
+
'<label>Tx hash (optional)</label>' +
|
|
2665
|
+
'<input id="mTxHash" type="text" placeholder="0x...">' +
|
|
2666
|
+
'<div class="note">File: <code>' + escapeHtml(fileName) + '</code></div>',
|
|
2667
|
+
function () {
|
|
2668
|
+
var elSid = document.getElementById('mSkillId');
|
|
2669
|
+
var elTx = document.getElementById('mTxHash');
|
|
2670
|
+
var sid = Number(String(elSid ? elSid.value : '').trim());
|
|
2671
|
+
if (!isFinite(sid) || sid <= 0) {
|
|
2672
|
+
showToast('Invalid skillId', true);
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
var txHash = String(elTx ? elTx.value : '').trim();
|
|
2676
|
+
fetch(apiBase + '/api/skillcards/user/mark-onchain', {
|
|
2677
|
+
method: 'POST',
|
|
2678
|
+
headers: authHeaders(),
|
|
2679
|
+
body: JSON.stringify({ fileName: fileName, skillId: Math.floor(sid), txHash: txHash }),
|
|
2680
|
+
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
|
2681
|
+
.then(function (out) {
|
|
2682
|
+
if (!out.ok || !out.json || !out.json.ok) throw new Error((out.json && out.json.error) ? out.json.error : 'mark failed');
|
|
2683
|
+
closeModal();
|
|
2684
|
+
showToast('Onchain set (skillId=' + Math.floor(sid) + ')');
|
|
2685
|
+
loadUserSkills();
|
|
2686
|
+
})
|
|
2687
|
+
.catch(function (e) {
|
|
2688
|
+
showToast('Error: ' + (e && e.message ? e.message : 'failed'), true);
|
|
2689
|
+
});
|
|
2690
|
+
},
|
|
2691
|
+
function () {}
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// Step flow helpers for the Add tab
|
|
2696
|
+
window.toggleStep = function(headerEl) {
|
|
2697
|
+
var card = headerEl.closest('.step-card');
|
|
2698
|
+
if (!card) return;
|
|
2699
|
+
card.classList.toggle('open');
|
|
2700
|
+
};
|
|
2701
|
+
window.selectTemplate = function(kind, btnEl) {
|
|
2702
|
+
// Remove selected from siblings
|
|
2703
|
+
var grid = btnEl.closest('.template-grid');
|
|
2704
|
+
if (grid) {
|
|
2705
|
+
var btns = grid.querySelectorAll('.template-btn');
|
|
2706
|
+
for (var i = 0; i < btns.length; i++) btns[i].classList.remove('selected');
|
|
2707
|
+
}
|
|
2708
|
+
btnEl.classList.add('selected');
|
|
2709
|
+
// Load the template
|
|
2710
|
+
loadTemplate(kind);
|
|
2711
|
+
// Update badge
|
|
2712
|
+
var badge = document.getElementById('stepTemplateBadge');
|
|
2713
|
+
if (badge) { badge.textContent = kind.toUpperCase() + ' RISK'; badge.className = 'step-badge done'; }
|
|
2714
|
+
// Auto-open step 2
|
|
2715
|
+
var step2 = document.querySelector('.step-card[data-step="2"]');
|
|
2716
|
+
if (step2) { step2.classList.add('open'); step2.scrollIntoView({behavior:'smooth',block:'nearest'}); }
|
|
2717
|
+
var editBadge = document.getElementById('stepEditBadge');
|
|
2718
|
+
if (editBadge) { editBadge.textContent = 'EDIT'; editBadge.className = 'step-badge'; }
|
|
2719
|
+
};
|
|
2720
|
+
window.toggleCollapsible = function(toggleEl) {
|
|
2721
|
+
var content = toggleEl.nextElementSibling;
|
|
2722
|
+
if (!content) return;
|
|
2723
|
+
content.classList.toggle('show');
|
|
2724
|
+
var isOpen = content.classList.contains('show');
|
|
2725
|
+
toggleEl.innerHTML = isOpen ? 'Advanced: Contract Settings ▴' : 'Advanced: Contract Settings ▾';
|
|
2726
|
+
};
|
|
2727
|
+
|
|
2728
|
+
loadAuth();
|
|
2729
|
+
loadV2Settings();
|
|
2730
|
+
renderIntentPreviews();
|
|
2731
|
+
renderReceiptPreview();
|
|
2732
|
+
autofillV2SettingsFromBackend();
|
|
2733
|
+
populateOnchainPanel();
|
|
2734
|
+
if (saveAuthBtn) saveAuthBtn.addEventListener('click', function(){ saveAuth(); });
|
|
2735
|
+
if (checkAuthBtn) checkAuthBtn.addEventListener('click', function(){ saveAuth(); checkAuth(); });
|
|
2736
|
+
if (clearAuthBtn) clearAuthBtn.addEventListener('click', function(){ clearAuth(); });
|
|
2737
|
+
if (validateSkillBtn) validateSkillBtn.addEventListener('click', function(){ renderPreview(); });
|
|
2738
|
+
if (addSkillBtn) addSkillBtn.addEventListener('click', function(){ saveAuth(); addUserSkill(); });
|
|
2739
|
+
if (saveV2SettingsBtn) saveV2SettingsBtn.addEventListener('click', function(){ saveV2Settings(); loadUserSkills(); });
|
|
2740
|
+
if (v2RpcUrl) v2RpcUrl.addEventListener('input', function(){ renderIntentPreviews(); renderReceiptPreview(); });
|
|
2741
|
+
if (v2Intents) v2Intents.addEventListener('input', renderIntentPreviews);
|
|
2742
|
+
if (v2Receipts) v2Receipts.addEventListener('input', renderReceiptPreview);
|
|
2743
|
+
if (intentPayload) intentPayload.addEventListener('input', renderIntentPreviews);
|
|
2744
|
+
if (intentExpiresAt) intentExpiresAt.addEventListener('input', renderIntentPreviews);
|
|
2745
|
+
if (intentCancelId) intentCancelId.addEventListener('input', renderIntentPreviews);
|
|
2746
|
+
if (copyIntentCreateBtn) copyIntentCreateBtn.addEventListener('click', function(){ copyText(getIntentCreateCmd()); showToast('Copied intent create'); });
|
|
2747
|
+
if (copyIntentCancelBtn) copyIntentCancelBtn.addEventListener('click', function(){ copyText(getIntentCancelCmd()); showToast('Copied intent cancel'); });
|
|
2748
|
+
if (receiptTraceId) receiptTraceId.addEventListener('input', renderReceiptPreview);
|
|
2749
|
+
if (copyReceiptGetBtn) copyReceiptGetBtn.addEventListener('click', function(){ copyText(getReceiptGetCmd()); showToast('Copied receipt get'); });
|
|
2750
|
+
if (fetchReceiptGetBtn) fetchReceiptGetBtn.addEventListener('click', function(){ fetchReceipt(); });
|
|
2751
|
+
if (formatJsonBtn) formatJsonBtn.addEventListener('click', function(){ formatJson(); });
|
|
2752
|
+
if (loadTemplateBtn) loadTemplateBtn.addEventListener('click', function(){
|
|
2753
|
+
var k = templateSelect ? String(templateSelect.value || '').trim() : '';
|
|
2754
|
+
if (!k) { flash('Choose a template first', true); return; }
|
|
2755
|
+
loadTemplate(k);
|
|
2756
|
+
});
|
|
2757
|
+
if (loadFromUrlBtn) loadFromUrlBtn.addEventListener('click', function(){ loadFromUrl(); });
|
|
2758
|
+
if (skillJson) skillJson.addEventListener('input', function(){ renderPreview(); });
|
|
2759
|
+
|
|
2760
|
+
if (userSkillSearch) {
|
|
2761
|
+
userSkillSearch.addEventListener('input', function () {
|
|
2762
|
+
var q = String(userSkillSearch.value || '');
|
|
2763
|
+
var filtered = userSkillsAll.filter(function (it) { return matchesUserSkill(it, q); });
|
|
2764
|
+
renderUserSkills(filtered);
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
loadUserSkills();
|
|
2768
|
+
checkAuth();
|
|
2769
|
+
|
|
2770
|
+
// Global delegated handlers for copy/delete/mark actions across all sections.
|
|
2771
|
+
document.addEventListener('click', function (ev) {
|
|
2772
|
+
// Fallback: card action buttons should still work even if per-card listeners fail.
|
|
2773
|
+
// We resolve the clicked action by walking up to the nearest .skill-card and using its data-slug.
|
|
2774
|
+
try {
|
|
2775
|
+
var actEl = ev && ev.target ? ev.target.closest('[data-action]') : null;
|
|
2776
|
+
if (actEl) {
|
|
2777
|
+
var action = actEl.getAttribute('data-action');
|
|
2778
|
+
var card = actEl.closest('.skill-card');
|
|
2779
|
+
var slug = card ? String(card.getAttribute('data-slug') || '').trim() : '';
|
|
2780
|
+
if (slug && (action === 'install' || action === 'json' || action === 'details')) {
|
|
2781
|
+
// If the skills list is still loading, don't intercept.
|
|
2782
|
+
// Let the browser follow the link (progressive enhancement).
|
|
2783
|
+
if (!importedAll || !importedAll.length) return;
|
|
2784
|
+
|
|
2785
|
+
// resolve item
|
|
2786
|
+
var it = null;
|
|
2787
|
+
for (var i = 0; i < importedAll.length; i++) {
|
|
2788
|
+
if (importedAll[i] && importedAll[i].slug === slug) { it = importedAll[i]; break; }
|
|
2789
|
+
}
|
|
2790
|
+
if (!it) return;
|
|
2791
|
+
|
|
2792
|
+
// Only prevent default if we can actually open the modal.
|
|
2793
|
+
var modalReady = false;
|
|
2794
|
+
try { modalReady = !!(modalBackdrop && modalTitle && modalBody); } catch (e) {}
|
|
2795
|
+
if (!modalReady) return;
|
|
2796
|
+
|
|
2797
|
+
ev.preventDefault();
|
|
2798
|
+
// keep URL shareable
|
|
2799
|
+
try {
|
|
2800
|
+
var enc = encodeURIComponent(slug);
|
|
2801
|
+
if (action === 'install') history.replaceState(null, '', '#install=' + enc);
|
|
2802
|
+
else if (action === 'json') history.replaceState(null, '', '#json=' + enc);
|
|
2803
|
+
else history.replaceState(null, '', '#skill=' + enc);
|
|
2804
|
+
} catch (e) {}
|
|
2805
|
+
|
|
2806
|
+
var pub = (publishedBySlug && it.slug && publishedBySlug[it.slug]) ? publishedBySlug[it.slug] : null;
|
|
2807
|
+
if (action === 'install') showInstallModal(it);
|
|
2808
|
+
else if (action === 'json') showJsonModal(it, pub);
|
|
2809
|
+
else openDetails(it, pub, '');
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
} catch (e) {}
|
|
2814
|
+
|
|
2815
|
+
var el = ev && ev.target ? ev.target.closest('[data-copy]') : null;
|
|
2816
|
+
if (el) {
|
|
2817
|
+
ev.preventDefault();
|
|
2818
|
+
copyText(el.getAttribute('data-copy'));
|
|
2819
|
+
showToast('Copied to clipboard');
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
var a = ev && ev.target ? ev.target.closest('a') : null;
|
|
2823
|
+
if (!a) return;
|
|
2824
|
+
var txt = a.getAttribute('data-copy');
|
|
2825
|
+
if (txt) {
|
|
2826
|
+
ev.preventDefault();
|
|
2827
|
+
copyText(txt);
|
|
2828
|
+
showToast('Copied to clipboard');
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
var del = a.getAttribute('data-delete');
|
|
2832
|
+
if (del) {
|
|
2833
|
+
ev.preventDefault();
|
|
2834
|
+
openModal(
|
|
2835
|
+
'Delete submitted skill',
|
|
2836
|
+
'<div class="note">This removes the stored SkillCard JSON from the library.</div>' +
|
|
2837
|
+
'<div class="danger-note" style="margin-top:10px">Delete: <code>' + escapeHtml(del) + '</code></div>',
|
|
2838
|
+
function () { closeModal(); deleteUserSkill(del); },
|
|
2839
|
+
function () {}
|
|
2840
|
+
);
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
var mark = a.getAttribute('data-mark');
|
|
2844
|
+
if (mark) {
|
|
2845
|
+
ev.preventDefault();
|
|
2846
|
+
markOnchain(mark);
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
|
|
2850
|
+
})();
|
|
2851
|
+
|
|
2852
|
+
</script>
|
|
2853
|
+
<script>
|
|
2854
|
+
(function(){
|
|
2855
|
+
var tabs = document.querySelectorAll('.tab-btn');
|
|
2856
|
+
var panels = document.querySelectorAll('.tab-panel');
|
|
2857
|
+
function switchTab(name) {
|
|
2858
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
2859
|
+
tabs[i].classList.toggle('active', tabs[i].getAttribute('data-tab') === name);
|
|
2860
|
+
tabs[i].setAttribute('aria-selected', tabs[i].getAttribute('data-tab') === name ? 'true' : 'false');
|
|
2861
|
+
}
|
|
2862
|
+
for (var j = 0; j < panels.length; j++) {
|
|
2863
|
+
panels[j].classList.toggle('active', panels[j].getAttribute('data-panel') === name);
|
|
2864
|
+
}
|
|
2865
|
+
history.replaceState(null, '', '#' + name);
|
|
2866
|
+
}
|
|
2867
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
2868
|
+
tabs[i].addEventListener('click', function() { switchTab(this.getAttribute('data-tab')); });
|
|
2869
|
+
}
|
|
2870
|
+
var hash = (location.hash || '').replace('#', '');
|
|
2871
|
+
if (hash === 'add' || hash === 'onchain') switchTab(hash);
|
|
2872
|
+
else if (hash === 'intents' || hash === 'receipts') switchTab('onchain');
|
|
2873
|
+
else if (hash === 'your-skills') switchTab('add');
|
|
2874
|
+
})();
|
|
2875
|
+
</script>
|
|
2876
|
+
|
|
2877
|
+
</body>
|
|
2878
|
+
</html>
|
|
2879
|
+
|