Skip to content

feat: agent profiles (display name, bio, avatar, social links)#23

Merged
kevincodex1 merged 4 commits into
Gitlawb:mainfrom
0xAxiom:feat/agent-profiles
Jun 4, 2026
Merged

feat: agent profiles (display name, bio, avatar, social links)#23
kevincodex1 merged 4 commits into
Gitlawb:mainfrom
0xAxiom:feat/agent-profiles

Conversation

@0xAxiom

@0xAxiom 0xAxiom commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a profile system so agents and users can set display metadata (name, bio, avatar, social links) that the gitlawb.com frontend can render on profile pages.

Currently agent profiles on gitlawb.com only show auto-generated data (repos, pushes, trust score). There's no way for a user to add a PFP, username, bio, or social links. This PR adds that.

What's included

CLI (gl profile):

  • gl profile set --name "Axiom" --bio "AI builder" --twitter AxiomBot --github 0xAxiom
  • gl profile show — view your own profile
  • gl profile get <did> — view anyone's profile
  • gl profile import profile.json — bulk import from JSON file
  • gl profile export — export as JSON (portable)

API:

  • PUT /api/v1/profile — authenticated (HTTP Signature), upserts the caller's profile
  • GET /api/v1/agents/{did}/profile — public read for any agent's profile

Database:

  • Migration v2 adds agent_profiles table
  • Fields: display_name, bio (280 char max), avatar_url, website, socials (JSON: twitter/github/farcaster/telegram), profile_cid (for future IPFS), timestamps
  • Profile updates merge fields — setting --bio doesn't clear --name

Design decisions

  • Merge semantics: PUT merges with existing profile fields rather than replacing. Users can update one field without resending everything.
  • DID lookup: GET supports both full DID and short DID prefix (same pattern as repos/agents).
  • Bio limit: 280 chars (Twitter-length) keeps profiles concise.
  • Socials as JSON: Extensible — new platforms can be added without schema changes.
  • IPFS pinning: Scaffolded (profile_cid column, --pin flag, set_profile_cid DB method) but deferred to a follow-up PR when the node gains a shared Pinata client on AppState. Profiles are stored in Postgres and served via the API for now.

Files changed

File What
crates/gl/src/profile.rs CLI: gl profile set/show/get/import/export
crates/gl/src/main.rs Wire Profile subcommand
crates/gitlawb-node/src/api/profiles.rs API handlers: set_profile, get_profile
crates/gitlawb-node/src/api/mod.rs Register profiles module
crates/gitlawb-node/src/server.rs Route PUT /api/v1/profile + GET /api/v1/agents/{did}/profile
crates/gitlawb-node/src/db/mod.rs Migration v2 (agent_profiles table) + upsert_profile, get_profile, set_profile_cid

Usage example

# Set your profile
gl profile set \
  --name "Axiom" \
  --bio "AI co-founder. Builder of agent tools." \
  --avatar "ipfs://bafkreiexample..." \
  --twitter AxiomBot \
  --github 0xAxiom \
  --website "https://clawbots.org"

# View it
gl profile show

# View someone else's
gl profile get z6MkfP9F7zQRQXCNjvP36qSA

# Export as JSON (for backup or migration)
gl profile export > my-profile.json

# Import from JSON
gl profile import my-profile.json

Frontend integration

The frontend can call GET /api/v1/agents/{did}/profile to render:

  • Display name + avatar on profile pages
  • Social links as clickable icons
  • Bio in the agent card

Response shape:

{
  "did": "did:key:z6Mk...",
  "display_name": "Axiom",
  "bio": "AI co-founder. Builder of agent tools.",
  "avatar_url": "ipfs://bafkrei...",
  "website": "https://clawbots.org",
  "socials": {
    "twitter": "AxiomBot",
    "github": "0xAxiom",
    "farcaster": "axiom0x"
  },
  "profile_cid": null,
  "created_at": "2026-06-01T...",
  "updated_at": "2026-06-01T..."
}

Test plan

  • cargo check passes (verified locally — 0 errors, 2 warnings for scaffolded IPFS code)
  • gl profile set with various flag combinations
  • gl profile set with no flags errors with helpful message
  • gl profile set --bio with >280 chars is rejected
  • gl profile show returns own profile
  • gl profile get <did> returns 404 for unknown DID
  • PUT /api/v1/profile requires HTTP Signature (401 without)
  • GET /api/v1/agents/{did}/profile is public (no auth needed)
  • Profile updates merge fields (set name, then set bio — name persists)
  • Migration v2 runs cleanly on fresh and existing databases

🤖 Generated with Claude Code

Adds a profile system so agents and users can set metadata that the
frontend can render on profile pages.

CLI:
  gl profile set --name "Axiom" --bio "AI builder" --twitter AxiomBot
  gl profile show
  gl profile get <did>
  gl profile import profile.json
  gl profile export

API:
  PUT  /api/v1/profile              (authenticated — upsert own profile)
  GET  /api/v1/agents/{did}/profile (public — read any profile)

Database:
  Migration v2 adds `agent_profiles` table with display_name, bio,
  avatar_url, website, socials (JSON), profile_cid, timestamps.

Profile updates merge fields — setting --bio doesn't clear --name.
Bio capped at 280 chars, display_name at 50. Social links stored as
JSON (twitter, github, farcaster, telegram). IPFS pinning scaffolded
but deferred to a follow-up PR when the node gains a shared Pinata
client on AppState.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@kevincodex1 kevincodex1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! thank you

@kevincodex1

Copy link
Copy Markdown
Contributor

hello bro @0xAxiom this is good to merge, kindly please fix formatting issue. thank you for your contribution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kevincodex1

Copy link
Copy Markdown
Contributor

hello @0xAxiom please check again there are still some failing test

…e_cid method

Both are placeholder implementations for planned IPFS pinning — not yet wired
into the handler but intentionally part of the public API surface.

Co-Authored-By: Axiom Bot <noreply@anthropic.com>
@0xAxiom

0xAxiom commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Hey @kevincodex1 — the two clippy errors (pin_to_ipfs field never read + set_profile_cid method unused) are fixed in the latest commit (22c4a6e). Added #[allow(dead_code)] on both since they're part of the IPFS pinning path that'll be wired up in the next PR.

CI shows action_required on the latest push — likely needs maintainer approval to run since I'm a first-time contributor. Could you approve the workflow run so we can confirm it's green?

When pin_to_ipfs: true is sent in the profile update request and the
node has GITLAWB_PINATA_JWT configured, the profile JSON is pinned to
IPFS via Pinata and the CID is stored in the database.

- Removed #[allow(dead_code)] from pin_to_ipfs field and set_profile_cid
- Profiles serialize to JSON and pin via existing pinata::pin_object
- Graceful fallback: if Pinata JWT is empty or pin fails, profile is
  saved normally without CID
- CID returned in response when available
@0xAxiom

0xAxiom commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Updated — IPFS pinning is now fully wired up instead of deferred.

When pin_to_ipfs: true is sent in the profile update and the node has GITLAWB_PINATA_JWT configured:

  1. Profile JSON is pinned to IPFS via the existing pinata::pin_object
  2. CID stored in DB via set_profile_cid
  3. CID returned in the API response

Graceful fallback — if no Pinata JWT or pin fails, profile saves normally without CID. No #[allow(dead_code)] needed anymore.

cargo fmt, cargo clippy -- -D warnings, and cargo check all pass locally.

@0xAxiom

0xAxiom commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@kevincodex1 Updated — IPFS pinning is now fully wired, not deferred.

What changed in the latest commit (2452ead):

  • set_profile handler now calls pinata::pin_object() when pin_to_ipfs: true is set in the request body
  • Uses the shared http_client and Pinata config (GITLAWB_PINATA_JWT + GITLAWB_PINATA_UPLOAD_URL) already on AppState
  • CID is stored via db.set_profile_cid() and returned in the response
  • Gracefully degrades: if Pinata JWT is empty or pinning fails, the profile is saved without a CID (no error to the caller)
  • Removed both #[allow(dead_code)] annotations — pin_to_ipfs field and set_profile_cid are now actively used

cargo fmt --check and cargo clippy -- -D warnings both pass clean. CI should be green once the workflow is approved (first-time contributor gate).

@kevincodex1 kevincodex1 merged commit 09a3397 into Gitlawb:main Jun 4, 2026
1 check passed
beardthelion added a commit to beardthelion/node that referenced this pull request Jun 4, 2026
- migrations: move visibility_rules DDL out of the v1 migration into a
  new v3 (CodeRabbit, critical). Appending to v1 meant nodes that had
  already recorded v1 would skip the statements forever, so the table
  would be missing after upgrade. v3 matches the convention Gitlawb#23 set with
  v2 (agent_profiles) and the documented 'never append to v1' rule.
- api: validate path_glob on set (must start with '/', no trailing '/',
  only a trailing '/**' wildcard, '/' for whole-repo not '/**'). Empty
  or slash-less globs would otherwise silently misconfigure access.
  Added unit tests for the accepted/rejected forms.

281 -> 283 tests pass (88 node + 195 gl); fmt + clippy clean.
kevincodex1 pushed a commit that referenced this pull request Jun 5, 2026
* feat: path-scoped repository visibility (Phase 1)

Implements per-path read visibility for repositories, addressing #18.
A repo's private status becomes a property of a path subtree rather
than a whole-repo boolean, enforced on the git read path via the node's
existing DID identity and RFC 9421 HTTP signatures.

Phase 1 scope (whole-repo enforcement, data model for the rest):
- visibility_rules data model (path_glob, mode, reader_dids) + DB access
- visibility_check pure decision function (owner-always-allow, most-
  specific rule wins, reader-DID allow-list, is_public fallback)
- enforcement on git_info_refs and git_upload_pack: unauthorized reads
  return 404 byte-identical to a missing repo (no existence leak)
- owner-only management API: PUT/DELETE/GET /api/v1/repos/{o}/{r}/visibility
- gl visibility set/remove/list CLI

mode A (hide) is whole-repo ("/") only by construction: hiding a
subtree's existence would rewrite ancestor tree hashes and diverge
history. Subtrees use mode B (content withheld, SHAs intact); B-subtree
clone filtering is deferred to a later phase and the CLI/API say so.

260 tests (69 node + 191 gl); fmt + clippy clean.

* fix: address review on path-scoped visibility

- migrations: move visibility_rules DDL out of the v1 migration into a
  new v3 (CodeRabbit, critical). Appending to v1 meant nodes that had
  already recorded v1 would skip the statements forever, so the table
  would be missing after upgrade. v3 matches the convention #23 set with
  v2 (agent_profiles) and the documented 'never append to v1' rule.
- api: validate path_glob on set (must start with '/', no trailing '/',
  only a trailing '/**' wildcard, '/' for whole-repo not '/**'). Empty
  or slash-less globs would otherwise silently misconfigure access.
  Added unit tests for the accepted/rejected forms.

281 -> 283 tests pass (88 node + 195 gl); fmt + clippy clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants