Skip to content

Commit 7ded7c5

Browse files
committed
Add operator token minting UI
1 parent 6163330 commit 7ded7c5

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

src/App.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ agent-comms signup \\
104104
After it returns status "pending", stop and tell Shay that you re-submitted the onboarding request. Do not use Agent Comms further until Shay approves you and gives you a minted per-agent token.`;
105105
}
106106

107+
function agentTokenPrompt(agent: AgentIdentity, token: string) {
108+
return `Your Agent Comms onboarding request for ${agent.handle} has been approved. This is your per-agent token. Keep it local and do not paste it into Agent Comms, issues, PRs, docs, or chat transcripts.
109+
110+
Configure your session:
111+
112+
export AGENT_COMMS_API_BASE="https://adanim-agent-comms.pages.dev"
113+
export AGENT_COMMS_TOKEN="${token}"
114+
115+
Then start with:
116+
117+
agent-comms doctor ${agent.id}
118+
agent-comms context ${agent.id}
119+
agent-comms inbox ${agent.id}
120+
agent-comms schemas
121+
122+
Use the CLI or REST API only. Do not use the browser dashboard.`;
123+
}
124+
107125
function readJsonRecord(key: string): Record<string, string | undefined> {
108126
try {
109127
const value = localStorage.getItem(key);
@@ -676,18 +694,24 @@ function Onboarding({
676694
state,
677695
expandedIds,
678696
copiedPromptAgentId,
697+
mintedTokens,
679698
onToggle,
680699
onStatus,
681700
onOpenProfile,
682701
onCopyPrompt,
702+
onCopyTokenPrompt,
703+
onMintToken,
683704
}: {
684705
state: AgentCommsState;
685706
expandedIds: Set<string>;
686707
copiedPromptAgentId?: string;
708+
mintedTokens: Record<string, { token: string; copied?: boolean } | undefined>;
687709
onToggle: (agentId: string) => void;
688710
onStatus: (agentId: string, status: AgentStatus) => void;
689711
onOpenProfile: (agentId: string) => void;
690712
onCopyPrompt: (agent: AgentIdentity) => void;
713+
onCopyTokenPrompt: (agent: AgentIdentity) => void;
714+
onMintToken: (agent: AgentIdentity) => void;
691715
}) {
692716
return (
693717
<div className="view-stack">
@@ -752,6 +776,16 @@ function Onboarding({
752776
</details>
753777
</div>
754778
) : null}
779+
{mintedTokens[agent.id]?.token ? (
780+
<div className="token-result">
781+
<strong>Minted token for {agent.handle}</strong>
782+
<textarea readOnly rows={9} value={agentTokenPrompt(agent, mintedTokens[agent.id]?.token ?? "")} />
783+
<button type="button" onClick={() => onCopyTokenPrompt(agent)}>
784+
<Copy aria-hidden="true" />
785+
{mintedTokens[agent.id]?.copied ? "Copied" : "Copy token prompt"}
786+
</button>
787+
</div>
788+
) : null}
755789
<footer>
756790
<button type="button" onClick={() => onOpenProfile(agent.id)}>
757791
Open profile
@@ -767,6 +801,11 @@ function Onboarding({
767801
Approve access
768802
</button>
769803
) : null}
804+
{agent.status === "approved" ? (
805+
<button type="button" onClick={() => onMintToken(agent)}>
806+
Mint token
807+
</button>
808+
) : null}
770809
{agent.status === "approved" ? (
771810
<button type="button" onClick={() => onStatus(agent.id, "suspended")}>
772811
Suspend access
@@ -939,6 +978,7 @@ export function App() {
939978
const [expandedSuggestionIds, setExpandedSuggestionIds] = useState<Set<string>>(() => new Set());
940979
const [expandedAgentIds, setExpandedAgentIds] = useState<Set<string>>(() => new Set());
941980
const [copiedPromptAgentId, setCopiedPromptAgentId] = useState<string | undefined>();
981+
const [mintedTokens, setMintedTokens] = useState<Record<string, { token: string; copied?: boolean } | undefined>>({});
942982
const [liveSessions, setLiveSessions] = useState<LiveConversationSession[]>([]);
943983
const [operatorToken] = useState(() => localStorage.getItem("agent-comms-operator-token") ?? "");
944984
const [apiStatus, setApiStatus] = useState("demo data");
@@ -1170,6 +1210,36 @@ export function App() {
11701210
}
11711211
};
11721212

1213+
const copyMintedTokenPrompt = async (agent: AgentIdentity) => {
1214+
const token = mintedTokens[agent.id]?.token;
1215+
if (!token) return;
1216+
try {
1217+
await navigator.clipboard.writeText(agentTokenPrompt(agent, token));
1218+
setMintedTokens((current) => ({ ...current, [agent.id]: { token, copied: true } }));
1219+
setActionStatus("Token prompt copied.");
1220+
window.setTimeout(() => {
1221+
setMintedTokens((current) => (
1222+
current[agent.id]?.token === token ? { ...current, [agent.id]: { token } } : current
1223+
));
1224+
}, 1800);
1225+
} catch {
1226+
setActionStatus("Copy failed. Select and copy the token prompt manually.");
1227+
}
1228+
};
1229+
1230+
const mintAgentToken = async (agent: AgentIdentity) => {
1231+
try {
1232+
const payload = await operatorRequest(`agents/${agent.id}/tokens`, {
1233+
method: "POST",
1234+
body: JSON.stringify({ label: `${agent.handle} dashboard token` }),
1235+
});
1236+
setMintedTokens((current) => ({ ...current, [agent.id]: { token: payload.token } }));
1237+
setActionStatus("Token minted. Copy it now; it will not be shown after refresh.");
1238+
} catch (error) {
1239+
setActionStatus(error instanceof Error ? error.message : "Token minting failed.");
1240+
}
1241+
};
1242+
11731243
const approveAgent = async (agentId: string) => {
11741244
try {
11751245
await operatorRequest("agent-approvals", {
@@ -1491,7 +1561,10 @@ export function App() {
14911561
<Onboarding
14921562
copiedPromptAgentId={copiedPromptAgentId}
14931563
expandedIds={expandedAgentIds}
1564+
mintedTokens={mintedTokens}
14941565
onCopyPrompt={copyOnboardingCorrectionPrompt}
1566+
onCopyTokenPrompt={copyMintedTokenPrompt}
1567+
onMintToken={mintAgentToken}
14951568
onOpenProfile={openProfile}
14961569
onStatus={updateAgentStatus}
14971570
onToggle={(agentId) => toggleSetValue(setExpandedAgentIds, agentId)}

src/styles.css

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,8 @@ meter {
848848
font-weight: 800;
849849
}
850850

851-
.onboarding-correction textarea {
851+
.onboarding-correction textarea,
852+
.token-result textarea {
852853
width: 100%;
853854
min-height: 260px;
854855
margin-top: 12px;
@@ -861,7 +862,21 @@ meter {
861862
font: 0.88rem/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
862863
}
863864

864-
.onboarding-correction details button {
865+
.token-result {
866+
display: grid;
867+
gap: 10px;
868+
border: 1px solid var(--color-accent);
869+
border-radius: 8px;
870+
padding: 12px;
871+
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
872+
}
873+
874+
.token-result textarea {
875+
min-height: 220px;
876+
}
877+
878+
.onboarding-correction details button,
879+
.token-result button {
865880
margin-top: 10px;
866881
}
867882

0 commit comments

Comments
 (0)