994 posts
GitHub App 제대로 이해하기: OAuth App, PAT와 무엇이 다를까

GitHub App 제대로 이해하기: OAuth App, PAT와 무엇이 다를까

자동화를 만들다 보면 GitHub에 인증할 일이 계속 생깁니다. CI에서 릴리스 커밋을 올리고, 봇이 이슈에 댓글을 달고, 외부 서비스가 PR을 검사하는 식이죠. 가장 먼저 손이 가는 건 개인 액세스 토큰(Personal Access Token, 이하 PAT)인데요. 잘 돌아가다가도 꼭 한 번씩 벽에 부딪힙니다. "이 봇이 왜 내 계정으로 커밋하지?", "내가 퇴사하면 이 자동화도 멈추나?", "왜 이 토큰에 우리 조직 전체 권한이 다 붙어 있지?" 같은 의문이죠. 🤔 이 문제들의 답이 바로 GitHub App입니다. 이름만 보면

OpenRouter: 키 하나로 모든 LLM을 부르는 통합 API

OpenRouter: 키 하나로 모든 LLM을 부르는 통합 API

LLM 호출 한 줄을 코드에 붙이는 건 쉽습니다. 그런데 제공사가 둘, 셋으로 늘어나는 순간 이야기가 달라지죠. OpenAI SDK 깔고, Anthropic SDK 또 깔고, 키를 각각 발급받아 환경변수로 빼고, 비용은 대시보드 세 군데를 들여다봐야 하고요. 게다가 새 모델이 나올 때마다 "이거 한번 써볼까?" 하면 또 가입하고 결제 정보를 등록하는 일이 반복됩니다. 정작 하고 싶은 건 "프롬프트 보내고 답 받기" 한 줄인데 말이죠. 🤔 OpenRouter는 이 번거로움을 키 하나로 정리합니다. OpenAI, Anthropic,

Discord 게이트웨이로 실시간 이벤트 받기

Discord 게이트웨이로 실시간 이벤트 받기

봇 개발을 하다 보면 결국 마주치는 통로가 **게이트웨이(Gateway)**입니다. 슬래시 커맨드를 만들 때 client.login() 한 줄로 봇을 온라인으로 띄웠는데, 그 순간 봇은 바로 이 게이트웨이로 디스코드와 연결을 맺은 거였어요. 이번 글에서는 그 통로의 안쪽을 들여다보며, 봇이 어떻게 서버의 모든 일을 실시간으로 지켜보는지, 그리고 그렇게 만든 봇을 어떻게 24시간 운영하는지까지 살펴봅니다. 게이트웨이는 항상 열려 있는 WebSocket REST API가 "내가 물어보면 답해주는" 요청-응답 방식이었다면, 게이트웨이는

Void AI: 라우트 핸들러에서 LLM을 타입 안전하게 호출하기

Void AI: 라우트 핸들러에서 LLM을 타입 안전하게 호출하기

앱에 AI 기능을 하나 붙이려고 마음먹으면, 정작 모델을 부르는 코드보다 그 주변을 차리는 일이 더 번거롭습니다. 어느 제공사를 쓸지 정하고, SDK를 설치하고, API 키를 환경변수로 빼고, 스트리밍 응답을 직접 파싱하고, 사용량이 얼마나 나왔는지 따로 추적하고… 정작 "프롬프트를 보내고 답을 받는" 본질은 한 줄인데 말이죠. Void는 이 주변 작업을 void/ai라는 클라이언트로 미리 차려둡니다. Cloudflare의 AI 게이트웨이 위에서 동작하는 타입 안전한 클라이언트인데, Void 라우팅에서 본 라우트 핸들러 안에서 모델

Rust tokio::sync: 채널과 동기화 도구 고르기

Rust tokio::sync: 채널과 동기화 도구 고르기

Tokio로 비동기 코드를 짜다 보면 생각보다 빨리 tokio::sync를 만나게 됩니다. 태스크 하나가 다른 태스크에게 일을 넘겨야 하고, 결과를 돌려받아야 하고, 여러 태스크에 종료 신호를 보내야 하고, 공유 상태를 잠가야 하니까요. 문제는 선택지가 꽤 많다는 점입니다. mpsc, oneshot, broadcast, watch, Mutex, RwLock, Semaphore, Notify가 한 모듈 안에 같이 있는데, 이름만 봐서는 언제 무엇을 써야 할지 헷갈립니다. mpsc로도 종료 신호를 보낼 수 있고, watch로도 보낼 수

SQLite FTS5로 전문 검색 구현하기

SQLite FTS5로 전문 검색 구현하기

블로그나 문서 앱을 만들다 보면 처음에는 검색 기능을 아주 단순하게 시작하게 됩니다. 제목이나 본문에 검색어가 들어 있는지 LIKE '%검색어%'로 확인하면 되니까요. 데이터가 몇십 개일 때는 이것만으로도 충분합니다. 그런데 글이 수백 개, 수천 개로 늘어나면 느낌이 달라집니다. 검색 속도가 느려지고, 관련도 순 정렬도 어렵고, 검색 결과에서 어느 부분이 매칭됐는지 보여주기도 번거로워요. "검색"이라는 단어가 한 번 들어간 글과 본문 전체가 검색에 관한 글을 같은 수준으로 다루게 되는 것도 아쉽습니다. 이럴 때 SQLite만 쓰고

State of AI 2026 설문 결과로 본 개발자들의 AI 활용 트렌드

State of AI 2026 설문 결과로 본 개발자들의 AI 활용 트렌드

매년 자바스크립트 생태계를 정리해 주던 State of JS 설문, 한 번쯤 보신 적 있으실 텐데요. 그 설문을 만들던 팀이 이번에는 흐름을 완전히 갈아탔습니다. 2026년에는 자바스크립트가 아니라 AI를 주제로 State of AI 2026 설문을 진행한 건데요. 그만큼 지난 한 해 개발자들의 관심사가 어디로 쏠렸는지를 단적으로 보여주는 변화가 아닐까 싶습니다. 이번 설문에는 2026년 4월 8일부터 5월 8일까지 전 세계 개발자 7,258명이 참여했습니다. 단순히 "어떤 AI 도구를 쓰나요?" 정도를 묻는 가벼운 조사가 아니라,

Portless로 localhost:3000 대신 이름 있는 URL 쓰기

Portless로 localhost:3000 대신 이름 있는 URL 쓰기

로컬에서 개발하다 보면 http://localhost:3000, http://localhost:3001, http://localhost:8080 같은 주소를 하루에도 수십 번씩 입력하는데요. 프로젝트가 두세 개만 돼도 어느 포트가 어느 앱이었는지 헷갈리기 시작합니다. 프런트엔드는 3000번, API는 4000번, 스토리북은 6006번... 이걸 외우고 있다가 동료에게 "그거 몇 번 포트였죠?"라고 물어본 경험, 다들 한 번쯤 있으시죠. 😅 게다가 포트가 겹치면 EADDRINUSE 에러가 나면서 서버가 안 뜨고, HTTPS가 필요한

Rust pub, crate, self, super: 모듈 경계와 공개 범위 이해하기

Rust pub, crate, self, super: 모듈 경계와 공개 범위 이해하기

Rust에서 모듈 시스템의 큰 그림을 잡고 파일을 나누는 mod, 경로를 가져오는 use까지 익히고 나면 다음으로 부딪히는 벽은 공개 범위입니다. 분명 함수에 pub을 붙였는데 외부에서 안 보이거나, 반대로 내부 구현까지 밖으로 드러나서 API가 지저분해지는 일이 생기죠. 여기에 crate::, self::, super::, pub(crate), pub use까지 섞이면 머릿속 모듈 지도가 금방 흐려집니다. 이번 글에서는 Rust 모듈 시스템에서 공개 범위가 어떻게 결정되는지 정리해보겠습니다. 핵심은 "항목이 공개인가?"만 보는 게

Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

앱에 LLM 호출 한 줄을 붙이는 건 쉽습니다. 그런데 그게 프로덕션에 올라가는 순간 이야기가 달라지죠. 같은 질문이 하루에도 수백 번 들어오는데 매번 돈을 내고 모델을 부르고 있진 않은지, OpenAI가 잠깐 흔들릴 때 서비스가 같이 멈추진 않을지, 이번 달 토큰 비용이 얼마나 나왔는지, 누가 우리 키로 폭주하고 있진 않은지… 정작 "프롬프트 보내고 답 받기"는 한 줄인데 그 주변을 지키는 일이 훨씬 많습니다. 게다가 Cloudflare Workers AI의 자체 모델로 시작했다가도, 복잡한 추론은 GPT나 Claude로 넘기고

Rust use 키워드: 긴 경로를 짧게 가져오는 법

Rust use 키워드: 긴 경로를 짧게 가져오는 법

Rust 예제를 보면 파일 맨 위에 use std::collections::HashMap; 같은 줄이 자주 나옵니다. 익숙해지면 아무렇지 않게 쓰지만, 처음에는 mod와 use가 비슷해 보여 헷갈리기 쉽습니다. "파일을 가져오는 건가?", "모듈을 만드는 건가?", "이걸 쓰면 공개되는 건가?" 같은 질문이 자연스럽게 따라오죠. 결론부터 말하면 use는 이미 존재하는 경로를 현재 스코프에서 짧은 이름으로 쓰게 해주는 키워드입니다. 모듈을 새로 만들지도 않고, 항목의 공개 범위를 바꾸지도 않습니다. 이번 글에서는 use가 하는 일과

Cloudflare Sandbox에서 안전하게 코드 실행하기

Cloudflare Sandbox에서 안전하게 코드 실행하기

AI 코딩 도구를 만들다 보면 결국 "생성한 코드를 어디서 실행할 것인가?"라는 문제를 만나게 됩니다. 단순히 코드를 문자열로 돌려주는 것만으로는 부족하거든요. 테스트도 돌려봐야 하고, 파일도 만들어야 하고, 때로는 개발 서버를 띄운 뒤 브라우저에서 확인할 수 있는 미리보기 URL도 필요합니다. 그렇다고 사용자의 코드를 내 서버에서 그대로 실행하기에는 부담이 큽니다. 코드는 파일 시스템을 건드릴 수도 있고, 오래 실행될 수도 있고, 의도치 않게 서버 자원을 많이 쓸 수도 있으니까요. 그래서 AI 에이전트나 코드 실행 서비스를 만들

1Password Service Account로 Touch ID 없이 시크릿 관리하기

1Password Service Account로 Touch ID 없이 시크릿 관리하기

얼마 전에 Discord MCP 서버를 클로드 코드에 붙여서 쓰고 있었는데요. 봇 토큰을 설정 파일에 평문으로 박아두기는 찜찜해서, 1Password CLI로 토큰을 주입하도록 바꿔뒀습니다. 그런데 그날부터 클로드 코드를 켤 때마다 화면 한가운데 "1Password Access Requested" 창이 뜨면서 Touch ID를 요구하기 시작했어요. 😅 하루에도 몇 번씩 터미널을 새로 여는데, 그때마다 손가락을 갖다 대는 건 생각보다 거슬립니다. 게다가 cron으로 돌리는 스크립트라면 아예 사람이 없으니 인증 자체가 불가능하죠. 이

mktemp로 안전하게 임시 파일 만들기

mktemp로 안전하게 임시 파일 만들기

쉘 스크립트를 쓰다 보면 잠깐 저장할 파일이 필요할 때가 많습니다. API 응답을 받아서 가공하거나, 여러 명령의 중간 결과를 합치거나, 압축하기 전에 작업 디렉토리를 하나 만들어두는 경우가 그렇죠. 처음에는 대충 /tmp/result.txt 같은 이름을 쓰기 쉽습니다. 조금 더 신경 쓰면 프로세스 ID를 붙여서 /tmp/myapp.$$처럼 만들기도 하고요. 혼자 쓰는 노트북에서는 별문제가 없어 보이지만, 이런 방식은 스크립트가 서버나 CI에서 돌기 시작하면 꽤 위험해집니다. 파일 이름이 예측 가능하고, 이미 같은 이름의 파일이 있

Rust에서 구조체 없이 JSON 다루기: serde_json

Rust에서 구조체 없이 JSON 다루기: serde_json

Serde로 데이터를 직렬화할 때는 보통 구조체를 먼저 선언하고 #[derive(Serialize, Deserialize)]를 붙이는데요. 데이터의 모양을 미리 알고 있다면 이 방식이 가장 안전하고 깔끔합니다. 그런데 현업에서는 구조체로 미리 못 박기 애매한 경우도 자주 만나게 됩니다. 외부 API가 내려주는 응답 중 일부 필드만 필요하거나, 응답 구조가 자주 바뀌거나, 요청 본문을 그때그때 즉석에서 조립해야 할 때가 그렇죠. 이럴 때 구조체를 일일이 정의하는 건 오히려 번거롭습니다. 그래서 이 글에서는 serde_json이 제공하

실시간 단방향 통신을 위한 Server-Sent Events(SSE)

실시간 단방향 통신을 위한 Server-Sent Events(SSE)

ChatGPT에게 질문을 던지면 답변이 한 글자씩 타이핑되듯 흘러나오는 모습, 다들 익숙하시죠? 증권 앱의 시세가 새로고침 없이 실시간으로 바뀌거나, 웹 대시보드에 알림이 띵 하고 도착하는 것도 마찬가지인데요. 이런 화면들의 공통점은 서버에서 새로운 데이터가 생길 때마다 클라이언트가 곧바로 받아본다는 것입니다. 실시간 양방향 통신을 위한 웹소켓(WebSocket) 글에서 우리는 HTTP의 한계와 이를 극복하는 웹소켓을 살펴봤는데요. 그런데 위 예시를 가만히 보면 한 가지 공통점이 더 있습니다. 데이터가 서버에서 클라이언트로 한 방향

Rust mod 키워드: 모듈을 선언하고 파일을 나누는 법

Rust mod 키워드: 모듈을 선언하고 파일을 나누는 법

Rust를 처음 배우면서 파일을 나누기 시작하면 mod에서 한 번쯤 멈칫하게 됩니다. 다른 언어의 import나 require에 익숙하다면 mod user;를 보고 "아, user 파일을 가져오는 건가?"라고 생각하기 쉬운데요. 반은 맞고 반은 틀립니다. mod는 파일을 읽어 오는 명령이라기보다 이 위치에 이런 이름의 모듈이 있다고 선언하는 키워드입니다. 그 모듈의 내용이 같은 파일 안에 있을 수도 있고, 별도 파일에 있을 수도 있죠. 이번 글에서는 Rust의 mod 키워드가 어떤 일을 하는지, main.rs와 lib.rs에서 모듈

하이브리드 검색과 RRF: 키워드 검색과 의미 검색을 한 줄로 합치기

하이브리드 검색과 RRF: 키워드 검색과 의미 검색을 한 줄로 합치기

지난 두 편에서 검색의 두 갈래를 따로 살펴봤는데요. BM25 키워드 검색은 단어가 정확히 일치할 때 강하고, 의미 검색은 표현이 달라도 뜻으로 문서를 찾아줍니다. 그리고 두 글 모두 마지막에 "둘을 합치면 어떨까?"라는 떡밥을 남겨뒀죠. 키워드 검색의 정확함과 의미 검색의 유연함을 동시에 누리자는 게 하이브리드 검색(hybrid search)의 아이디어입니다. 말은 간단한데, 막상 합치려고 하면 곧바로 벽에 부딪힙니다. 두 검색기가 내놓는 점수가 완전히 다른 단위거든요. BM25는 0에서 수십까지 제한 없이 커지는 점수를 주고,

Discord MCP 서버로 AI에게 커뮤니티 운영 맡기기

Discord MCP 서버로 AI에게 커뮤니티 운영 맡기기

개발 커뮤니티나 팀의 소통 채널로 Discord를 운영하고 있으면 은근히 손이 가는 일이 많은데요. 새 버전을 릴리스하면 공지 채널에 변경 사항을 정리해서 올리고, 질문 채널에 쌓인 메시지를 읽고 답하고, 행사가 잡히면 이벤트를 등록합니다. 하나하나는 금방 끝나는 일이지만 매번 Discord 창을 열고 손으로 처리하다 보면 흐름이 자꾸 끊기죠. 😅 만약 코딩 중인 AI 도구에 "방금 머지된 PR 내용을 정리해서 #release 채널에 공지해줘"라고 말하면 그대로 처리해주고, "어제 질문 채널에 올라온 메시지 중에 아직 답이 없는

agentgateway로 MCP 서버와 LLM 트래픽 한곳에서 관리하기

agentgateway로 MCP 서버와 LLM 트래픽 한곳에서 관리하기

AI 에이전트를 하나 만들어서 노트북에서 돌릴 때는 세상이 참 단순합니다. MCP(Model Context Protocol) 서버 두어 개를 설정 파일에 적어두고, OpenAI 키 하나를 환경 변수에 넣으면 끝이거든요. 그런데 이걸 팀 전체가 쓰게 만들려는 순간 질문이 쏟아지기 시작합니다. 사내 MCP 서버가 다섯 개로 늘어났는데 에이전트마다 다섯 개를 전부 적어줘야 하나요? 누가 실수로 배포 도구를 호출하는 건 어떻게 막죠? 지난달 LLM 요금이 왜 세 배가 됐는지, 누가 어떤 도구를 불렀는지 물어보면 답할 자료가 있나요? 😅

Discord