코딩 에이전트를 위한 dev.to 발행 도구를 만들었습니다. 핵심 문제는 코드가 아니라 셸(shell)이었습니다.
요약
코딩 에이전트가 dev.to에 글을 자동으로 발행할 수 있도록 돕는 확장 도구 개발 사례를 소개합니다. 셸 환경의 비대화형(non-interactive) 특성으로 인해 발생하는 환경 변수 인식 문제를 해결하는 과정을 다룹니다.
핵심 포인트
- 코딩 에이전트를 위한 dev.to 발행용 TypeScript 확장 도구 개발
- 마크다운 파일을 단일 원천(Source of Truth)으로 활용하는 설계
- 비대화형 셸 환경에서 환경 변수가 로드되지 않는 문제와 해결책 제시
이제 제 코딩 에이전트(coding agent)가 제 글쓰기 워크플로우의 대부분을 실행합니다. 초안은 마크다운 (markdown) 파일로 존재하며, 이를 dev.to에 발행하는 것이 남은 유일한 수동 단계였습니다. 그래서 에이전트가 스스로 호출할 수 있는 도구를 만들었습니다. 8.9KB 크기의 확장 파일 하나, 세 가지 액션(actions), 그리고 코드와는 전혀 상관없는 두 가지 환경적 함정(environment traps)이 전부입니다.
이것은 2026년 8월에 시작된 빌드 인 퍼블릭 (build-in-public) 실험의 일부입니다. 저는 43개의 사이트를 운영하며 수익은 거의 0달러, 월 방문자 수는 약 320명 정도를 기록한 후에야 교훈을 깨달았습니다. dev.to는 25개의 정적 사이트(static sites)가 한 달 동안 얻는 것보다 48시간 안에 더 많은 트래픽을 제공한다는 사실입니다. 빠르고 자주 발행하는 것이 핵심입니다. 발행 단계는 비용이 저렴해야 합니다.
필요한 것
- pi 코딩 에이전트 (확장 시스템이 있는 에이전트라면 무엇이든 가능하며, 패턴은 동일하게 적용됩니다)
- 확장 디렉토리로서의
~/.pi/agent/extensions/ - dev.to 설정에서 생성한 dev.to API 키
도구
pi는 ~/.pi/agent/extensions/에 있는 TypeScript 파일을 자동으로 검색하고 jiti를 사용하여 빌드 단계 없이 로드합니다. 확장은 단순한 팩토리 함수(factory function)입니다:
import { Type } from "typebox";
import { StringEnum } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
...
이 도구는 세 가지 액션을 노출합니다:
- list —
drafts/와published/에 무엇이 있는지 표시 - create — 초안을 읽고, 기본적으로 dev.to에 _초안(draft)_으로 POST 요청을 보낸 뒤, 프론트매터 (frontmatter)에
devto_id/devto_url을 다시 기록하고, 파일을published/로 이동 - update — 저장된
devto_id를 사용하여 기존 기사에 변경 사항을 PUT 요청
중요한 설계 선택: 마크다운 파일이 신뢰할 수 있는 단일 원천 (source of truth)입니다. 프론트매터 (frontmatter)는 제목과 dev.to ID를 보유합니다. 파일에서 보는 내용이 관리용 키를 제외하고 dev.to로 전송되는 내용입니다.
API 키 함정
첫 번째 버전은 셸 환경 (shell environment)에서 키를 읽어왔습니다. 제 터미널에서는 잘 작동했습니다. 하지만 에이전트 세션 내부에서는 실패했습니다. 에이전트는 터미널 멀티플렉서 (terminal multiplexer)에 의해 실행되는데, 이 터미널의 bash 창들은 비대화형 (non-interactive) 셸입니다. 제 .bashrc 파일의 여섯 번째 줄에는 다음과 같은 내용이 있습니다:
[[ $- != *i* ]] && return
이것은 흔히 쓰이는 조기 종료 가드 (early-exit guard)입니다: "만약 이 셸이 대화형이 아니라면, 읽기를 중단하라." 이 줄 이후의 모든 내용 — 제 export DEVTO_API_KEY=...를 포함하여 — 에이전트의 셸에서는 실행되지 않았습니다. 키가 그저 존재하지 않았던 것입니다.
해결책은 환경 변수를 완전히 신뢰하지 않는 것이었습니다:
function devtoApiKey(): string | null {
const fromEnv = process.env.DEVTO_API_KEY;
if (fromEnv) return fromEnv;
...
환경 변수를 먼저 확인하고, 그 다음 chmod 600 권한이 설정된 ~/.pi/agent/devto.key로 폴백 (fallback)합니다. 키는 코드에 절대 저장되지 않으며, 셸이 제 환경 변수 (exports)를 로드하든 말든 도구는 정상적으로 작동합니다.
두 가지 추가 함정
키 문제가 아닌 401 오류. ?state= 파라미터 없이 GET /api/articles/me를 호출하면 유효한 키를 사용하더라도 401을 반환합니다. 이는 자격 증명 (credential) 문제가 아니라 해당 엔드포인트 (endpoint)의 동작 방식입니다. ?state=published를 사용하거나 단순히 POST로 테스트하십시오.
쓰기 되돌리기 (write-back) 버그. create는 devto_id와 devto_url을 아카이브된 파일의 프론트매터 (frontmatter)에 다시 기록합니다. 제 첫 번째 시도에서는 이 장부 정리 과정이 잘못되었습니다. 모의 API (mock-API) 엔드투엔드 (end-to-end) 테스트를 통해 실제 엔드포인트에 닿기 전에 이를 잡아낼 수 있었습니다. 설정하는 데 10분이 걸렸지만 그만한 가치가 있었습니다.
이 도구가 가능하게 하는 워크플로 (workflow)
이제 에이전트가 초안을 작성하면, 제가 파일을 검토하고, publish_devto create가 이를 dev.to 초안으로 저장하며, 저는 브라우저에서 발행 버튼을 누릅니다. 최종 클릭 단계에 단 한 번의 인간 개입(human gate)이 있을 뿐, 나머지는 모두 스크립트로 처리됩니다.
이 프로젝트는 1인 개발자가 작은 에이전트 예산으로 하루 1달러를 벌 수 있는지에 대한 공개 실험입니다. 콘텐츠를 생성하는 기계 자체가 곧 콘텐츠입니다. 이 도구는 그 과정의 벽돌 하나이며, 전체 프로젝트는 GitHub에서 확인할 수 있습니다.
한 가지 더 말씀드리자면, 이것은 에이전트가 완전히 스스로 작성한 첫 번째 확장 기능(extension)입니다. 저는 기계에게 진화 루프 (evolution loop)를 부여했습니다. 즉, 기계가 GitHub에서 기술을 수집하고, 스스로 확장 기능을 작성하면, 제가 이를 검토하고 다시 로드(reload)하는 방식입니다. 이 도구는 그 루프의 첫 번째 결과물입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기