@@ -347,7 +347,7 @@ <h1>Bring one ENS name.<br><span class="grad">Generate 10 verifiable agents.</sp
347347 <!-- STEP 3: CAPABILITY SELECTION -->
348348 < div class ="wizard-panel " id ="panel-3 ">
349349 < div class ="panel-step "> Step 3 of 8</ div >
350- < div class ="panel-title "> Enter tenant / ENS name </ div >
350+ < div class ="panel-title "> Namespace setup </ div >
351351 < div class ="panel-sub " id ="capSubtext "> This is your organization or agent identity used for namespace generation.</ div >
352352 < div class ="mode-tabs ">
353353 < button class ="mode-tab active " id ="tabPacks " onclick ="setCapMode('packs') "> Recommended Packs</ button >
@@ -358,11 +358,26 @@ <h1>Bring one ENS name.<br><span class="grad">Generate 10 verifiable agents.</sp
358358 < div class ="cherry-counter "> Selected: < span id ="cherryCount "> 0</ span > / 10</ div >
359359 < div id ="cherryGroups "> </ div >
360360 </ div >
361- < div class ="field " style ="margin-top:14px ">
362- < label > ENS Name</ label >
363- < input type ="text " id ="ensInput " placeholder ="acme.eth " autocomplete ="off " spellcheck ="false " />
361+ < div class ="field " style ="margin-top:14px " id ="tenantField ">
362+ < label > Tenant label</ label >
363+ < input type ="text " id ="tenantInput " placeholder ="hydroseal " autocomplete ="off " spellcheck ="false " />
364+ < span class ="field-error " id ="tenantError "> Use 3-32 lowercase letters, numbers, or hyphens; no .eth; cannot start/end with hyphen.</ span >
365+ < span class ="field-hint "> Used for subnames under CommandLayer canonical parents.</ span >
366+ </ div >
367+ < div class ="field " id ="canonicalSearchField ">
368+ < label > Search CommandLayer capability namespaces</ label >
369+ < input type ="text " id ="canonicalSearchInput " placeholder ="Type a letter, example: a, v, s " autocomplete ="off " spellcheck ="false " />
370+ < div class ="field-hint "> Select up to 10 canonical parents.</ div >
371+ < div id ="canonicalSearchResults " class ="name-preview " style ="display:block "> </ div >
372+ < div id ="canonicalSelected " class ="name-preview "> </ div >
373+ < div class ="field-hint "> CommandLayer namespace mode creates subnames under CommandLayer-controlled canonical parents, such as hydroseal.approveagent.eth. It does not grant ownership of approveagent.eth.</ div >
374+ </ div >
375+ < div class ="field " id ="ownEnsField " style ="display:none ">
376+ < label > ENS namespace</ label >
377+ < input type ="text " id ="ensInput " placeholder ="hydroseal.eth " autocomplete ="off " spellcheck ="false " />
364378 < span class ="field-error " id ="ensError "> Please enter a valid .eth name</ span >
365- < span class ="field-hint "> Must end in .eth · The name you own or control</ span >
379+ < span class ="field-hint "> To activate records under your ENS name, the connected wallet must control that ENS name. Ownership/control verification is required before activation.</ span >
380+ < span class ="field-hint "> Manual package only. Activation request submission for user-owned ENS namespaces requires ENS control verification.</ span >
366381 </ div >
367382 < div class ="btn-row ">
368383 < button class ="btn btn-ghost " onclick ="goToStep(2) "> ← Back</ button >
@@ -618,10 +633,11 @@ <h3>Payment and provisioning are coming next.</h3>
618633 classify :'classifyagent.eth' , translate :'translateagent.eth' , validate :'validateagent.eth' ,
619634 report :'reportagent.eth'
620635} ;
636+ const TRUST_PARENTS = Array . from ( new Set ( Object . values ( VERB_TO_AGENT ) . filter ( v => [ 'signagent.eth' , 'attestagent.eth' , 'authorizeagent.eth' , 'approveagent.eth' , 'rejectagent.eth' , 'permitagent.eth' , 'grantagent.eth' , 'authenticateagent.eth' , 'endorseagent.eth' , 'verifyagent.eth' ] . includes ( v ) ) ) ) ;
621637
622638// ── STATE ─────────────────────────────────────────────────────────────────────
623639let state = {
624- step :1 , ens :'' , activationMode :'cl' ,
640+ step :1 , ens :'' , tenantLabel : '' , activationMode :'cl' ,
625641 capMode :'packs' , selectedPack :null , cherryVerbs :[ ] ,
626642 pubKeyB64 :'' , privKeyB64 :'' , kid :'' ,
627643 keyGenerated :false , keyDownloaded :false , keyAcked :false ,
@@ -641,7 +657,7 @@ <h3>Payment and provisioning are coming next.</h3>
641657
642658// Generate the agent name for a verb based on activation mode
643659function agentName ( verb ) {
644- const base = state . ens . replace ( '.eth' , '' ) ;
660+ const base = state . activationMode === 'cl' ? state . tenantLabel : state . ens . replace ( '.eth' , '' ) ;
645661 if ( state . activationMode === 'cl' ) {
646662 // acme.approveagent.eth
647663 const canonical = VERB_TO_AGENT [ verb ] || ( verb + 'agent.eth' ) ;
@@ -667,6 +683,9 @@ <h3>Payment and provisioning are coming next.</h3>
667683 state . step = n ;
668684 document . getElementById ( 'panel-' + n ) . classList . add ( 'active' ) ;
669685 updateProgress ( ) ;
686+ document . getElementById ( 'canonicalSearchInput' ) . addEventListener ( 'input' , renderCanonicalSearch ) ;
687+ renderCanonicalSearch ( ) ;
688+ updateStep3ModeUI ( ) ;
670689 if ( n === 8 ) buildSummary ( ) ;
671690 window . scrollTo ( { top :0 , behavior :'smooth' } ) ;
672691}
@@ -690,10 +709,11 @@ <h3>Payment and provisioning are coming next.</h3>
690709 goToStep ( 2 ) ;
691710}
692711document . getElementById ( 'ensInput' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) goStep3 ( ) ; } ) ;
712+ document . getElementById ( 'tenantInput' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) goStep3 ( ) ; } ) ;
693713
694714// ── STEP 2: MODE ──────────────────────────────────────────────────────────────
695715function updateModeExamples ( ) {
696- const base = state . ens . replace ( '.eth' , '' ) ;
716+ const base = state . activationMode === 'cl' ? state . tenantLabel : state . ens . replace ( '.eth' , '' ) ;
697717 document . getElementById ( 'eg-cl' ) . textContent = base + '.approveagent.eth' ;
698718 document . getElementById ( 'eg-own' ) . textContent = 'approve.' + state . ens ;
699719 document . getElementById ( 'eg-single' ) . textContent = state . ens ;
@@ -706,6 +726,7 @@ <h3>Payment and provisioning are coming next.</h3>
706726 } ) ;
707727 updateNamePreview ( ) ;
708728 updateSiweModeHint ( ) ;
729+ updateStep3ModeUI ( ) ;
709730}
710731
711732function updateNamePreview ( ) {
@@ -724,6 +745,7 @@ <h3>Payment and provisioning are coming next.</h3>
724745
725746function goStep2 ( ) {
726747 if ( ! state . activationMode ) { alert ( 'Please choose an activation mode.' ) ; return ; }
748+ updateStep3ModeUI ( ) ;
727749 // default to CL mode if none selected
728750 if ( ! document . querySelector ( '.mode-card.selected' ) ) selectMode ( 'cl' ) ;
729751updateSiweModeHint ( ) ;
@@ -788,18 +810,43 @@ <h3>Payment and provisioning are coming next.</h3>
788810 updateNamePreview ( ) ;
789811}
790812
813+
814+ function validTenantLabel ( label ) { return / ^ [ a - z 0 - 9 - ] { 3 , 32 } $ / . test ( label ) && ! label . includes ( '.eth' ) && ! label . startsWith ( '-' ) && ! label . endsWith ( '-' ) ; }
815+ function updateStep3ModeUI ( ) {
816+ const isCl = state . activationMode === 'cl' ;
817+ document . getElementById ( 'tenantField' ) . style . display = isCl ?'grid' :'none' ;
818+ document . getElementById ( 'canonicalSearchField' ) . style . display = isCl ?'grid' :'none' ;
819+ document . getElementById ( 'ownEnsField' ) . style . display = isCl ?'none' :'grid' ;
820+ }
821+ function renderCanonicalSearch ( ) {
822+ const q = ( document . getElementById ( 'canonicalSearchInput' ) . value || '' ) . trim ( ) . toLowerCase ( ) ;
823+ const out = TRUST_PARENTS . filter ( p => ! q || p . includes ( q ) ) . slice ( 0 , 10 ) ;
824+ document . getElementById ( 'canonicalSearchResults' ) . innerHTML = out . map ( p => `<button type="button" class="btn btn-ghost" style="margin:4px" onclick="addCanonicalParent('${ p } ')">${ p } </button>` ) . join ( '' ) || '<div class="field-hint">No matches.</div>' ;
825+ const selected = ( state . selectedCanonicalParents || [ ] ) ;
826+ document . getElementById ( 'canonicalSelected' ) . classList . toggle ( 'show' , selected . length > 0 ) ;
827+ document . getElementById ( 'canonicalSelected' ) . innerHTML = selected . length ?`<div class="name-preview-label">Selected canonical parents (${ selected . length } /10)</div><div class="name-preview-list">${ selected . map ( p => `<span class="name-preview-item">${ state . tenantLabel || 'tenant' } .${ p } </span>` ) . join ( '' ) } </div>` :'' ;
828+ }
829+ function addCanonicalParent ( parent ) {
830+ state . selectedCanonicalParents = state . selectedCanonicalParents || [ ] ;
831+ if ( state . selectedCanonicalParents . includes ( parent ) || state . selectedCanonicalParents . length >= 10 ) return ;
832+ state . selectedCanonicalParents . push ( parent ) ;
833+ renderCanonicalSearch ( ) ;
834+ }
835+
791836function goStep3 ( ) {
792- const val = document . getElementById ( 'ensInput' ) . value . trim ( ) . toLowerCase ( ) ;
793- const err = document . getElementById ( 'ensError' ) ;
794- if ( ! val . endsWith ( '.eth' ) || val . length < 6 ) {
795- err . classList . add ( 'show' ) ;
796- document . getElementById ( 'ensInput' ) . classList . add ( 'error' ) ;
797- return ;
837+ if ( state . activationMode === 'cl' ) {
838+ const label = document . getElementById ( 'tenantInput' ) . value . trim ( ) . toLowerCase ( ) ;
839+ if ( ! validTenantLabel ( label ) ) { document . getElementById ( 'tenantError' ) . classList . add ( 'show' ) ; return ; }
840+ document . getElementById ( 'tenantError' ) . classList . remove ( 'show' ) ;
841+ state . tenantLabel = label ;
842+ state . ens = label + '.eth' ;
843+ } else {
844+ const val = document . getElementById ( 'ensInput' ) . value . trim ( ) . toLowerCase ( ) ;
845+ const err = document . getElementById ( 'ensError' ) ;
846+ if ( ! val . endsWith ( '.eth' ) || val . length < 6 ) { err . classList . add ( 'show' ) ; document . getElementById ( 'ensInput' ) . classList . add ( 'error' ) ; return ; }
847+ err . classList . remove ( 'show' ) ; document . getElementById ( 'ensInput' ) . classList . remove ( 'error' ) ; document . getElementById ( 'ensInput' ) . classList . add ( 'valid' ) ;
848+ state . ens = val ;
798849 }
799- err . classList . remove ( 'show' ) ;
800- document . getElementById ( 'ensInput' ) . classList . remove ( 'error' ) ;
801- document . getElementById ( 'ensInput' ) . classList . add ( 'valid' ) ;
802- state . ens = val ;
803850 updateModeExamples ( ) ;
804851 buildPacksGrid ( ) ;
805852 buildCherryGroups ( ) ;
@@ -918,6 +965,7 @@ <h3>Payment and provisioning are coming next.</h3>
918965 const address = toChecksumAddress ( state . connectedWalletAddress ) ;
919966 if ( ! address ) { throw new Error ( 'Connect wallet first.' ) ; }
920967 updateSiweModeHint ( ) ;
968+ updateStep3ModeUI ( ) ;
921969 statusEl . textContent = 'Status: Requesting SIWE nonce...' ;
922970 setAuthStatusClasses ( 'connected' , 'connected' , false ) ;
923971 const chainIdHex = await window . ethereum . request ( { method :'eth_chainId' } ) ;
@@ -999,27 +1047,20 @@ <h3>Payment and provisioning are coming next.</h3>
9991047 status . textContent = 'No ENS names found for this wallet. You can still type manually.' ;
10001048 return ;
10011049 }
1002- status . textContent = 'Detected ENS names' ;
1003- list . innerHTML = names . map ( ( entry ) => `<div class="ens-owned-card"><h4>${ entry . name } </h4><div style="font-size:12px;color:var(--muted)">Owned ENS name</div><div style="font-size:12px;color:var(--muted)">Control not checked </div><div class="ens-owned-actions"><button class="btn btn-secondary" onclick="useEnsAsTenant('${ entry . name } ')">Use as tenant label</button><button class="btn btn-secondary" onclick="useEnsAsNamespace('${ entry . name } ')">Use as ENS namespace</button></div></div>` ) . join ( '' ) ;
1050+ status . textContent = 'ENS names detected from this wallet ' ;
1051+ list . innerHTML = names . map ( ( entry ) => `<div class="ens-owned-card"><h4>${ entry . name } </h4><div style="font-size:12px;color:var(--muted)">Owned ENS name</div><div style="font-size:12px;color:var(--muted)">controlStatus: not_checked </div><div class="ens-owned-actions"><button class="btn btn-secondary" onclick="useEnsAsTenant('${ entry . name } ')">Use as tenant label</button><button class="btn btn-secondary" onclick="useEnsAsNamespace('${ entry . name } ')">Use as ENS namespace</button></div></div>` ) . join ( '' ) ;
10041052 } catch ( _err ) {
10051053 status . textContent = 'ENS lookup unavailable. You can still type manually.' ;
10061054 }
10071055}
10081056
1009- function useEnsAsTenant ( name ) {
1010- const label = String ( name || '' ) . toLowerCase ( ) . replace ( / \. e t h $ / , '' ) ;
1011- if ( ! label ) return ;
1012- selectMode ( 'cl' ) ;
1013- document . getElementById ( 'ensInput' ) . value = `${ label } .eth` ;
1014- }
1057+ function useEnsAsTenant ( name ) { const label = String ( name || '' ) . toLowerCase ( ) . replace ( / \. e t h $ / , '' ) ; if ( ! label ) return ; selectMode ( 'cl' ) ; document . getElementById ( 'tenantInput' ) . value = label ; state . tenantLabel = label ; renderCanonicalSearch ( ) ; }
10151058
1016- function useEnsAsNamespace ( name ) {
1017- selectMode ( 'own' ) ;
1018- document . getElementById ( 'ensInput' ) . value = String ( name || '' ) . toLowerCase ( ) ;
1019- }
1059+ function useEnsAsNamespace ( name ) { selectMode ( 'own' ) ; document . getElementById ( 'ensInput' ) . value = String ( name || '' ) . toLowerCase ( ) ; }
10201060
10211061function goStep5Auth ( ) {
10221062 updateSiweModeHint ( ) ;
1063+ updateStep3ModeUI ( ) ;
10231064 if ( state . activationMode === 'cl' && ! state . authenticatedAddress ) {
10241065 alert ( 'Sign-In with Ethereum is required before submitting CommandLayer activation request.' ) ;
10251066 return ;
@@ -1396,7 +1437,7 @@ <h3>Payment and provisioning are coming next.</h3>
13961437
13971438function startOver ( ) {
13981439 state = {
1399- step :1 , ens :'' , activationMode :'cl' , capMode :'packs' , selectedPack :null , cherryVerbs :[ ] ,
1440+ step :1 , ens :'' , tenantLabel : '' , activationMode :'cl' , capMode :'packs' , selectedPack :null , cherryVerbs :[ ] ,
14001441 pubKeyB64 :'' , privKeyB64 :'' , kid :'' , keyGenerated :false , keyDownloaded :false , keyAcked :false ,
14011442 _cardJson :'' , _packageJson :'' , submitResult :null , authenticatedAddress :'' , authStatus :'NOT_AUTHENTICATED' , authChainId :null , siweAuthenticated :false
14021443 } ;
@@ -1414,6 +1455,9 @@ <h3>Payment and provisioning are coming next.</h3>
14141455
14151456// ── INIT ──────────────────────────────────────────────────────────────────────
14161457updateProgress ( ) ;
1458+ document . getElementById ( 'canonicalSearchInput' ) . addEventListener ( 'input' , renderCanonicalSearch ) ;
1459+ renderCanonicalSearch ( ) ;
1460+ updateStep3ModeUI ( ) ;
14171461</ script >
14181462</ body >
14191463</ html >
0 commit comments