viruagent-cli 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viruagent-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "AI-agent-optimized CLI for blog publishing (Tistory, Naver) and Instagram automation",
5
5
  "private": false,
6
6
  "type": "commonjs",
@@ -18,6 +18,7 @@ Instagram 자동화를 위한 viruagent-cli 가이드. 항상 `--provider insta`
18
18
  | 명령 | 스킬 | 설명 |
19
19
  |------|------|------|
20
20
  | login | va-insta-login | 로그인 + 챌린지 해결 |
21
+ | publish | va-insta-publish | 게시물 발행 (어그로 전략 포함) |
21
22
  | like, unlike | va-insta-like | 좋아요/좋아요 취소 |
22
23
  | comment | va-insta-comment | 댓글 작성 |
23
24
  | follow, unfollow | va-insta-follow | 팔로우/언팔로우 |
@@ -61,4 +62,4 @@ INSTA_PASSWORD=
61
62
 
62
63
  ## See Also
63
64
 
64
- va-shared, va-insta-login, va-insta-like, va-insta-comment, va-insta-follow, va-insta-dm, va-insta-feed
65
+ va-shared, va-insta-login, va-insta-publish, va-insta-like, va-insta-comment, va-insta-follow, va-insta-dm, va-insta-feed
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: va-insta-publish
3
+ version: 1.0.0
4
+ description: "Instagram 게시물 발행: 어그로 캡션 전략, 이미지 선택, 해시태그"
5
+ metadata:
6
+ category: "publish"
7
+ provider: "insta"
8
+ requires:
9
+ bins: ["viruagent-cli"]
10
+ ---
11
+
12
+ # va-insta-publish — Instagram 게시물 발행
13
+
14
+ Instagram에 이미지+캡션을 발행하는 가이드. 어그로(고engagement) 콘텐츠 전략 포함.
15
+
16
+ ## 명령어
17
+
18
+ ```bash
19
+ npx viruagent-cli publish --provider insta \
20
+ --image-urls "<이미지URL>" \
21
+ --content "<캡션텍스트>"
22
+ ```
23
+
24
+ 또는 키워드로 이미지 자동 검색:
25
+
26
+ ```bash
27
+ npx viruagent-cli publish --provider insta \
28
+ --related-image-keywords "<키워드>" \
29
+ --content "<캡션텍스트>"
30
+ ```
31
+
32
+ ## 어그로 주제 유형 (인게이지먼트 높은 순)
33
+
34
+ ### 1. 논쟁형 (Controversial) — 저장+댓글 최고
35
+ 공감대를 자극하는 주장. 한쪽 편을 들되 반론 여지를 남긴다.
36
+
37
+ | 예시 주제 | 핵심 앵글 |
38
+ |-----------|-----------|
39
+ | "개발자 월급 현실 폭로" | 기대 vs 실제 연봉 갭 |
40
+ | "운동 유튜버들이 절대 안 말하는 것" | 업계 비밀 폭로 프레임 |
41
+ | "20대에 집 사는 게 정말 답인가" | 세대 갈등 자극 |
42
+ | "직장인이 부업하면 안 되는 이유" | 역발상 |
43
+ | "한국 스타트업이 망하는 진짜 이유" | 내부자 폭로 |
44
+
45
+ ### 2. 공감형 (Relatable) — 저장+공유 최고
46
+ "내 얘기다" 싶게 만드는 콘텐츠.
47
+
48
+ | 예시 주제 | 핵심 앵글 |
49
+ |-----------|-----------|
50
+ | "개발자라면 공감 100%" | 직군 공감 밈 |
51
+ | "월요일 아침 출근 전 뇌 상태" | 직장인 공감 |
52
+ | "카페에서 노트북 피는 사람 유형" | 유형 분류 |
53
+ | "운동 3개월 전후 마인드셋 변화" | 성장 공감 |
54
+ | "AI 쓰기 전 vs 후 업무량" | 도구 도입 전후 |
55
+
56
+ ### 3. 정보형 (Educational) — 저장 최고, 팔로우 증가
57
+ "몰랐다, 유용하다" 반응 유도.
58
+
59
+ | 예시 주제 | 핵심 앵글 |
60
+ |-----------|-----------|
61
+ | "Claude AI 프롬프트 꿀팁 5가지" | 즉시 써먹는 팁 |
62
+ | "개발자 연봉 협상 스크립트" | 실전 대화 예시 |
63
+ | "인스타 알고리즘 2026 변화 요약" | 최신 정보 |
64
+ | "노션 템플릿 무료 공유" | 실용 리소스 |
65
+ | "사이드 프로젝트 6개월 수익 공개" | 숫자 공개 |
66
+
67
+ ### 4. 스토리형 (Story) — 댓글+팔로우 최고
68
+ 여정과 감정이 있는 서사.
69
+
70
+ | 예시 주제 | 핵심 앵글 |
71
+ |-----------|-----------|
72
+ | "비전공자가 개발자 되기까지 1년" | 성장 서사 |
73
+ | "월 300만원 부업 만든 방법" | 숫자+여정 |
74
+ | "회사 그만두고 6개월 후기" | 결단+결과 |
75
+ | "AI 에이전트로 블로그 자동화한 결과" | 실험 후기 |
76
+ | "팔로워 0에서 1만까지 걸린 시간" | 성장 수치 |
77
+
78
+ ## 캡션 작성 규칙
79
+
80
+ ### 첫 줄이 전부다 (2줄 이상 "더 보기" 전에 후킹)
81
+ - **숫자**: "3가지만 알면 됩니다"
82
+ - **반전**: "잘못 알고 있었습니다"
83
+ - **질문**: "이거 해보셨나요?"
84
+ - **금지**: "이것만은 하지 마세요"
85
+ - **공감**: "저만 이런 거 아니죠?"
86
+
87
+ ### 구조 (300~500자 최적)
88
+ ```
89
+ [첫 줄 후킹 — 1문장]
90
+
91
+ [본론 — 3~5줄. 줄바꿈 많이. 읽기 쉽게.]
92
+
93
+ [행동 유도 — 저장/댓글/팔로우 중 하나만]
94
+
95
+ [해시태그 — 줄바꿈 후 별도로]
96
+ ```
97
+
98
+ ### 행동 유도 (CTA) 예시
99
+ - 저장: "나중에 써먹으려면 저장해 두세요 🔖"
100
+ - 댓글: "여러분은 어떻게 생각하세요? 댓글로 알려주세요 👇"
101
+ - 팔로우: "이런 내용 더 보려면 팔로우 ✅"
102
+ - 공유: "이거 꼭 필요한 친구한테 태그해 주세요 🏷️"
103
+
104
+ ### 해시태그 전략 (20~25개)
105
+ - 대형 (100만+): 3~5개 (노출용)
106
+ - 중형 (10만~100만): 10~12개 (타겟용)
107
+ - 소형 (1만~10만): 5~7개 (경쟁 적음)
108
+ - 브랜드/니치: 2~3개 (커뮤니티용)
109
+
110
+ ```
111
+ #개발자 #프로그래밍 #코딩 (대형)
112
+ #개발자일상 #개발자성장 #주니어개발자 (중형)
113
+ #사이드프로젝트 #AI자동화 #viruagent (소형/니치)
114
+ ```
115
+
116
+ ## 이미지 선택 기준
117
+
118
+ | 유형 | 효과 | 권장 상황 |
119
+ |------|------|-----------|
120
+ | 텍스트 카드 | 저장률↑ | 정보형, 팁 |
121
+ | 비포/애프터 | 댓글↑ | 변화, 성장 |
122
+ | 밈/유머 | 공유↑ | 공감형 |
123
+ | 실제 화면캡처 | 신뢰↑ | 후기, 결과 공개 |
124
+ | 얼굴 포함 | 팔로우↑ | 스토리형 |
125
+
126
+ `--related-image-keywords`에 주제 관련 영어 키워드 사용 권장 (Unsplash 검색):
127
+ - 개발: `developer laptop coding`
128
+ - AI: `artificial intelligence technology`
129
+ - 성장: `growth success motivation`
130
+ - 일상: `coffee work lifestyle`
131
+
132
+ ## 발행 예시
133
+
134
+ ```bash
135
+ # 정보형: Claude 팁 카드
136
+ npx viruagent-cli publish --provider insta \
137
+ --related-image-keywords "AI technology laptop" \
138
+ --content "Claude AI 쓰면서 바뀐 것들 🤖
139
+
140
+ 프롬프트 하나로 블로그 글을 자동 발행합니다.
141
+ 코드 리뷰도 10초면 끝납니다.
142
+ 기획안도 5분 만에 초안이 나옵니다.
143
+
144
+ 아직도 혼자 다 하고 있다면
145
+ 도구를 바꿀 때입니다.
146
+
147
+ 나중에 써먹으려면 저장해 두세요 🔖
148
+
149
+ #AI #Claude #개발자 #업무자동화 #생산성 #개발자일상 #프로그래밍 #코딩 #사이드프로젝트 #AI자동화 #viruagent #개발자성장 #주니어개발자 #테크 #스타트업"
150
+ ```
151
+
152
+ ## 주의사항
153
+
154
+ - caption 최대 2,200자 (초과 시 잘림)
155
+ - 해시태그 최대 30개 (초과 시 인게이지먼트 감소)
156
+ - 발행 간격: 최소 1~2분 (rate limit)
157
+ - 로그인 안 된 경우: `va-insta-login` 먼저 실행
@@ -64,6 +64,7 @@ SKILLS_DIR: <viruagent-cli 설치 경로>/skills/
64
64
  | va-naver-posts | `va-naver-posts/SKILL.md` | 글 목록/읽기 |
65
65
  | va-insta | `va-insta/SKILL.md` | Instagram 개요 + 레이트리밋 |
66
66
  | va-insta-login | `va-insta-login/SKILL.md` | 로그인 + 챌린지 |
67
+ | va-insta-publish | `va-insta-publish/SKILL.md` | 게시물 발행 + 어그로 전략 |
67
68
  | va-insta-like | `va-insta-like/SKILL.md` | 좋아요 |
68
69
  | va-insta-comment | `va-insta-comment/SKILL.md` | 댓글 |
69
70
  | va-insta-follow | `va-insta-follow/SKILL.md` | 팔로우/언팔 |
@@ -52,7 +52,7 @@ npx viruagent-cli publish \
52
52
  - **소제목 간격**: 600~1,000자(약 300~500단어)마다 h3 1개
53
53
  - **목차**: 3개 이상 섹션이면 앵커 링크 목차 삽입 (Featured Snippet 진입 확률 상승)
54
54
  - **이미지 alt 태그**: 반드시 `핵심키워드 + 설명` 형식으로 작성
55
- - **내부 링크**: 관련 2~4개 연결 (체류시간 35% 연장 효과)
55
+ - **내부 링크**: 실제 발행된 URL이 제공된 경우에만 삽입. URL 없으면 섹션 전체 생략 (가짜 링크 절대 금지)
56
56
  - **E-E-A-T**: 직접 경험 기반 문장, 데이터/출처 인용, 작성자 관점 명시
57
57
 
58
58
  ### Tistory HTML 규칙
@@ -163,13 +163,7 @@ npx viruagent-cli publish \
163
163
  <p>[예상 못한 결과나 부가적 인사이트 — 이 섹션이 인간미를 만듦]</p>
164
164
  <p>&nbsp;</p>
165
165
 
166
- <!-- 내부 링크 (2~4개 필수) -->
167
- <h2>관련 글</h2>
168
- <ul>
169
- <li><a href="[URL1]">[관련 글 1 제목]</a></li>
170
- <li><a href="[URL2]">[관련 글 2 제목]</a></li>
171
- </ul>
172
- <p>&nbsp;</p>
166
+ <!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
173
167
 
174
168
  <p>[핵심 요약 1~2문장] [독자에게 구체적 행동 제안] [공유/댓글 유도]</p>
175
169
  ```
@@ -233,13 +227,7 @@ npx viruagent-cli publish \
233
227
  <p>[구체적 답변]</p>
234
228
  <p>&nbsp;</p>
235
229
 
236
- <!-- 내부 링크 -->
237
- <h2>관련 글</h2>
238
- <ul>
239
- <li><a href="[URL1]">[관련 글 1]</a></li>
240
- <li><a href="[URL2]">[관련 글 2]</a></li>
241
- </ul>
242
- <p>&nbsp;</p>
230
+ <!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
243
231
 
244
232
  <p>[완료 축하 + 다음 단계 제안]</p>
245
233
  ```
@@ -294,13 +282,7 @@ npx viruagent-cli publish \
294
282
  <p>[이유]</p>
295
283
  <p>&nbsp;</p>
296
284
 
297
- <!-- 내부 링크 -->
298
- <h2>관련 글</h2>
299
- <ul>
300
- <li><a href="[URL1]">[관련 글 1]</a></li>
301
- <li><a href="[URL2]">[관련 글 2]</a></li>
302
- </ul>
303
- <p>&nbsp;</p>
285
+ <!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
304
286
 
305
287
  <p>[내 개인 픽 + 이유] [댓글 유도 — "여러분이 써본 것 중 좋았던 건?"]</p>
306
288
  ```
@@ -360,13 +342,7 @@ npx viruagent-cli publish \
360
342
  <p>[맞지 않는 상황도 언급 — 신뢰도 상승]</p>
361
343
  <p>&nbsp;</p>
362
344
 
363
- <!-- 내부 링크 -->
364
- <h2>관련 글</h2>
365
- <ul>
366
- <li><a href="[URL1]">[관련 글 1]</a></li>
367
- <li><a href="[URL2]">[관련 글 2]</a></li>
368
- </ul>
369
- <p>&nbsp;</p>
345
+ <!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
370
346
 
371
347
  <p>[최종 점수나 별점 + 총평 1~2문장]</p>
372
348
  ```
@@ -423,13 +399,7 @@ npx viruagent-cli publish \
423
399
  </ul>
424
400
  <p>&nbsp;</p>
425
401
 
426
- <!-- 내부 링크 -->
427
- <h2>관련 글</h2>
428
- <ul>
429
- <li><a href="[URL1]">[관련 글 1]</a></li>
430
- <li><a href="[URL2]">[관련 글 2]</a></li>
431
- </ul>
432
- <p>&nbsp;</p>
402
+ <!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
433
403
 
434
404
  <p>[추가 궁금한 점은 댓글로 — 독자 참여 유도]</p>
435
405
  ```
@@ -12,7 +12,7 @@ const {
12
12
  } = require('./utils');
13
13
  const { normalizeThumbnailForPublish } = require('./imageNormalization');
14
14
  const { enrichContentWithUploadedImages, resolveMandatoryThumbnail } = require('./imageEnrichment');
15
- const { createWithProviderSession } = require('./session');
15
+ const { createWithProviderSession, checkAndIncrementRateLimit, getRateLimitStatus } = require('./session');
16
16
  const { createAskForAuthentication } = require('./auth');
17
17
 
18
18
  const createTistoryProvider = ({ sessionPath, account }) => {
@@ -95,6 +95,7 @@ const createTistoryProvider = ({ sessionPath, account }) => {
95
95
 
96
96
  async publish(payload) {
97
97
  return withProviderSession(async () => {
98
+ checkAndIncrementRateLimit(sessionPath, 'publish');
98
99
  const title = payload.title || 'Untitled';
99
100
  const rawContent = payload.content || '';
100
101
  const visibility = mapVisibility(payload.visibility);
@@ -528,6 +529,14 @@ const createTistoryProvider = ({ sessionPath, account }) => {
528
529
  });
529
530
  },
530
531
 
532
+ rateLimitStatus() {
533
+ return {
534
+ provider: 'tistory',
535
+ mode: 'rateLimitStatus',
536
+ ...getRateLimitStatus(sessionPath),
537
+ };
538
+ },
539
+
531
540
  async logout() {
532
541
  clearProviderMeta('tistory', account);
533
542
  return {
@@ -5,6 +5,46 @@ const { sleep, readCredentialsFromEnv, parseSessionError, buildLoginErrorMessage
5
5
  const { clickKakaoAccountContinue } = require('./browserHelpers');
6
6
  const { extractAllCookies, filterCookies, cookiesToSessionFormat } = require('../chromeManager');
7
7
 
8
+ // ── Rate Limit (일일 발행 제한) ──
9
+ const DAILY_LIMIT = { publish: 15 };
10
+
11
+ const readSessionFile = (sessionPath) => {
12
+ if (!fs.existsSync(sessionPath)) return null;
13
+ try { return JSON.parse(fs.readFileSync(sessionPath, 'utf-8')); } catch { return null; }
14
+ };
15
+ const writeSessionFile = (sessionPath, data) => {
16
+ fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
17
+ fs.writeFileSync(sessionPath, JSON.stringify(data, null, 2), 'utf-8');
18
+ };
19
+
20
+ const checkAndIncrementRateLimit = (sessionPath, type) => {
21
+ const raw = readSessionFile(sessionPath) || {};
22
+ if (!raw.rateLimits) raw.rateLimits = {};
23
+ const c = raw.rateLimits[type] || { daily: 0, dayStart: Date.now() };
24
+
25
+ const now = Date.now();
26
+ if (now - c.dayStart > 86400000) { c.daily = 0; c.dayStart = now; }
27
+
28
+ const dailyMax = DAILY_LIMIT[type];
29
+ if (dailyMax && c.daily >= dailyMax) {
30
+ throw new Error(`daily_limit: ${type} exceeded daily limit of ${dailyMax}. Try again tomorrow.`);
31
+ }
32
+
33
+ c.daily++;
34
+ raw.rateLimits[type] = { ...c, savedAt: new Date().toISOString() };
35
+ writeSessionFile(sessionPath, raw);
36
+ };
37
+
38
+ const getRateLimitStatus = (sessionPath) => {
39
+ const raw = readSessionFile(sessionPath) || {};
40
+ const result = {};
41
+ for (const [type, max] of Object.entries(DAILY_LIMIT)) {
42
+ const c = raw.rateLimits?.[type] || { daily: 0 };
43
+ result[type] = { daily: `${c.daily}/${max}` };
44
+ }
45
+ return result;
46
+ };
47
+
8
48
  const isLoggedInByCookies = async (context, page) => {
9
49
  try {
10
50
  // Use CDP to get all cookies including httpOnly (TSSESSION)
@@ -114,4 +154,6 @@ module.exports = {
114
154
  waitForLoginFinish,
115
155
  persistTistorySession,
116
156
  createWithProviderSession,
157
+ checkAndIncrementRateLimit,
158
+ getRateLimitStatus,
117
159
  };