jun-claude-code 0.0.3 → 0.0.4

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.
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: project-task-manager
3
+ description: GitHub Project 태스크 관리 Agent. 태스크 조회, 생성, 상태 변경, 이슈 연동.
4
+ keywords: [GitHub, Project, 태스크, 이슈, 할일, 백로그, 진행중, task, issue, project board]
5
+ model: haiku
6
+ color: green
7
+ ---
8
+
9
+ # Project Task Manager Agent
10
+
11
+ GitHub Project 태스크를 관리하는 Agent입니다.
12
+
13
+ ## 역할
14
+
15
+ 1. **태스크 조회**: 프로젝트 보드의 태스크 목록 조회
16
+ 2. **태스크 생성**: 새 이슈 생성 및 프로젝트에 추가
17
+ 3. **상태 변경**: 태스크 상태 업데이트 (Backlog/In progress/Done)
18
+ 4. **태스크 검색**: 키워드/상태별 필터링
19
+
20
+ ## 프로젝트 설정
21
+
22
+ 이 Agent는 `.claude/project.env` 파일에서 프로젝트 정보를 읽습니다.
23
+
24
+ ```bash
25
+ # .claude/project.env
26
+ GITHUB_PROJECT_OWNER=your-org
27
+ GITHUB_PROJECT_NUMBER=1
28
+ GITHUB_PROJECT_REPO=your-org/your-repo
29
+ ```
30
+
31
+ 설정이 없으면 `jun-claude-code init-project` 명령어로 생성할 수 있습니다.
32
+
33
+ ### 환경변수 로드
34
+
35
+ 모든 명령어 실행 전 환경변수를 로드하세요:
36
+
37
+ ```bash
38
+ source .claude/project.env
39
+ ```
40
+
41
+ ---
42
+
43
+ ## 사용 시점
44
+
45
+ ### 적합한 경우
46
+
47
+ ```
48
+ - "현재 태스크 목록 보여줘"
49
+ - "새 태스크 추가해줘"
50
+ - "이 태스크를 In progress로 변경해줘"
51
+ - "Backlog 태스크만 보여줘"
52
+ - "이슈 #123을 프로젝트에 추가해줘"
53
+ ```
54
+
55
+ ### 부적합한 경우
56
+
57
+ ```
58
+ - 코드 작업 (code-writer 사용)
59
+ - PR 생성/관리 (git-manager 사용)
60
+ - 코드 리뷰 (code-reviewer 사용)
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 명령어 패턴
66
+
67
+ ### 태스크 목록 조회
68
+
69
+ ```bash
70
+ # 전체 목록
71
+ gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json
72
+
73
+ # jq로 파싱
74
+ gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '.items[] | {title: .title, status: .status}'
75
+ ```
76
+
77
+ ### 상태별 필터링
78
+
79
+ ```bash
80
+ # 특정 상태만
81
+ gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '[.items[] | select(.status == "In progress")]'
82
+
83
+ # 상태별 개수
84
+ gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json | jq '.items | group_by(.status) | map({status: .[0].status, count: length})'
85
+ ```
86
+
87
+ ### 이슈 생성 및 프로젝트 추가
88
+
89
+ ```bash
90
+ # 1. 이슈 생성
91
+ gh issue create --repo "$GITHUB_PROJECT_REPO" --title "제목" --body "내용"
92
+
93
+ # 2. 프로젝트에 추가
94
+ gh project item-add "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --url <issue-url>
95
+ ```
96
+
97
+ ### 태스크 상태 변경
98
+
99
+ ```bash
100
+ # 1. 프로젝트 필드 ID 조회
101
+ gh project field-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json
102
+
103
+ # 2. 아이템 상태 변경
104
+ gh project item-edit --project-id <PROJECT_ID> --id <ITEM_ID> --field-id <FIELD_ID> --single-select-option-id <OPTION_ID>
105
+ ```
106
+
107
+ ### 이슈 닫기
108
+
109
+ ```bash
110
+ gh issue close <issue-number> --repo "$GITHUB_PROJECT_REPO"
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 출력 형식
116
+
117
+ ```markdown
118
+ # GitHub Project 태스크 현황
119
+
120
+ ## 상태 요약
121
+ | 상태 | 개수 |
122
+ |------|------|
123
+ | In progress | N개 |
124
+ | Backlog | N개 |
125
+ | Done | N개 |
126
+
127
+ ## 태스크 목록
128
+
129
+ ### In progress
130
+ - #191 FEAT: 포트원 결제 취소 API 연동 (@0r0loo)
131
+
132
+ ### Backlog
133
+ - #130 RFC: SKU 시스템 설계
134
+ - #49 S3 URL 대신 CDN URL로 응답하도록 수정
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 권한 요구사항
140
+
141
+ GitHub Project 접근에 `read:project` 및 `project` 스코프가 필요합니다.
142
+
143
+ ```bash
144
+ # 권한 부족 시
145
+ gh auth refresh -s read:project,project
146
+ ```
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+ # 세션 시작 시 GitHub Project 태스크 조회
3
+
4
+ # project.env 로드
5
+ if [ -f ".claude/project.env" ]; then
6
+ source .claude/project.env
7
+ fi
8
+
9
+ # 필수 설정 확인
10
+ if [ -z "$GITHUB_PROJECT_OWNER" ] || [ -z "$GITHUB_PROJECT_NUMBER" ]; then
11
+ exit 0
12
+ fi
13
+
14
+ # gh CLI 확인
15
+ if ! command -v gh &> /dev/null; then
16
+ exit 0
17
+ fi
18
+
19
+ # 프로젝트 태스크 조회
20
+ TASKS=$(gh project item-list "$GITHUB_PROJECT_NUMBER" --owner "$GITHUB_PROJECT_OWNER" --format json 2>/dev/null)
21
+
22
+ if [ $? -ne 0 ]; then
23
+ echo "⚠️ GitHub Project 접근 실패. 권한 확인: gh auth refresh -s read:project,project"
24
+ exit 0
25
+ fi
26
+
27
+ # 태스크 개수 확인
28
+ TOTAL=$(echo "$TASKS" | jq '.totalCount // 0')
29
+
30
+ if [ "$TOTAL" -eq 0 ]; then
31
+ exit 0
32
+ fi
33
+
34
+ {
35
+ echo ""
36
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
37
+ echo "📋 ${GITHUB_PROJECT_OWNER}/projects/${GITHUB_PROJECT_NUMBER} 태스크 현황"
38
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
39
+ printf "%-8s │ %-14s │ %-45s\n" "Priority" "Status" "Title"
40
+ echo "─────────┼────────────────┼──────────────────────────────────────────────"
41
+
42
+ # 태스크 출력 (Priority 순: P0 → P1 → P2 → 없음)
43
+ echo "$TASKS" | jq -r '
44
+ .items
45
+ | sort_by(
46
+ if .priority == "P0" then 0
47
+ elif .priority == "P1" then 1
48
+ elif .priority == "P2" then 2
49
+ else 3 end
50
+ )
51
+ | .[]
52
+ | "\(.priority // "-")\t\(.status // "-")\t\(.title)"
53
+ ' | while IFS=$'\t' read -r priority status title; do
54
+ # Title 길이 제한 (45자)
55
+ if [ ${#title} -gt 42 ]; then
56
+ title="${title:0:42}..."
57
+ fi
58
+ printf "%-8s │ %-14s │ %-45s\n" "$priority" "$status" "$title"
59
+ done
60
+
61
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
62
+ echo "💡 /tasks 로 상세 목록 확인 | 전체: ${TOTAL}개"
63
+ echo ""
64
+ } > /dev/tty
65
+
66
+ exit 0
@@ -0,0 +1,7 @@
1
+ # GitHub Project 설정
2
+ # 이 파일을 프로젝트의 .claude/project.env 로 복사하여 사용하세요.
3
+ # jun-claude-code init-project 명령어로 자동 생성할 수 있습니다.
4
+
5
+ GITHUB_PROJECT_OWNER=
6
+ GITHUB_PROJECT_NUMBER=
7
+ GITHUB_PROJECT_REPO=
package/README.md CHANGED
@@ -81,6 +81,53 @@ your-project/
81
81
  └── CLAUDE.md # 프로젝트 설명
82
82
  ```
83
83
 
84
+ ### GitHub Project 연동
85
+
86
+ `init-project` 커맨드로 GitHub Project 태스크 관리를 프로젝트에 연동할 수 있습니다.
87
+
88
+ ```bash
89
+ # 프로젝트 루트에서 실행
90
+ jun-claude-code init-project
91
+ ```
92
+
93
+ 실행하면 다음을 인터랙티브로 설정합니다:
94
+
95
+ 1. **GitHub Owner** (org 또는 user)
96
+ 2. **Project Number**
97
+ 3. **Repository** (owner/repo 형식)
98
+
99
+ 설정 완료 후 프로젝트에 생성되는 파일:
100
+
101
+ ```
102
+ your-project/
103
+ ├── .claude/
104
+ │ ├── project.env # GitHub Project 설정 (GITHUB_PROJECT_OWNER 등)
105
+ │ ├── settings.json # StartSession hook (세션 시작 시 태스크 표시)
106
+ │ ├── hooks/
107
+ │ │ └── task-loader.sh # 태스크 조회 스크립트
108
+ │ └── agents/
109
+ │ └── project-task-manager.md # 태스크 관리 Agent
110
+ ```
111
+
112
+ #### 수동 설정
113
+
114
+ `init-project` 대신 직접 `.claude/project.env`를 생성할 수도 있습니다:
115
+
116
+ ```bash
117
+ # .claude/project.env
118
+ GITHUB_PROJECT_OWNER=your-org
119
+ GITHUB_PROJECT_NUMBER=1
120
+ GITHUB_PROJECT_REPO=your-org/your-repo
121
+ ```
122
+
123
+ #### 필요 권한
124
+
125
+ GitHub Project 접근에 `read:project` 스코프가 필요합니다:
126
+
127
+ ```bash
128
+ gh auth refresh -s read:project,project
129
+ ```
130
+
84
131
  ## 핵심 원칙
85
132
 
86
133
  이 템플릿의 핵심 원칙:
package/dist/cli.js CHANGED
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  const commander_1 = require("commander");
5
38
  const copy_1 = require("./copy");
@@ -27,4 +60,22 @@ program
27
60
  process.exit(1);
28
61
  }
29
62
  });
63
+ program
64
+ .command('init-project')
65
+ .description('Initialize GitHub Project integration in current directory')
66
+ .action(async () => {
67
+ try {
68
+ const { initProject } = await Promise.resolve().then(() => __importStar(require('./init-project')));
69
+ await initProject();
70
+ }
71
+ catch (error) {
72
+ if (error instanceof Error) {
73
+ console.error('Error:', error.message);
74
+ }
75
+ else {
76
+ console.error('An unexpected error occurred');
77
+ }
78
+ process.exit(1);
79
+ }
80
+ });
30
81
  program.parse();
package/dist/copy.js CHANGED
@@ -125,8 +125,17 @@ async function copyClaudeFiles(options = {}) {
125
125
  console.error(chalk_1.default.red('Error:'), 'Source .claude directory not found');
126
126
  process.exit(1);
127
127
  }
128
+ // Files to exclude from global copy (project-specific files)
129
+ const EXCLUDE_FROM_GLOBAL = [
130
+ 'hooks/task-loader.sh',
131
+ 'agents/project-task-manager.md',
132
+ 'project.env.example',
133
+ ];
128
134
  // Get all files to copy
129
- const files = getAllFiles(sourceDir);
135
+ const allFiles = getAllFiles(sourceDir);
136
+ const files = allFiles.filter((file) => {
137
+ return !EXCLUDE_FROM_GLOBAL.includes(file);
138
+ });
130
139
  if (files.length === 0) {
131
140
  console.log(chalk_1.default.yellow('No files found in .claude directory'));
132
141
  return;
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { copyClaudeFiles, CopyOptions } from './copy';
2
+ export { initProject } from './init-project';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.copyClaudeFiles = void 0;
3
+ exports.initProject = exports.copyClaudeFiles = void 0;
4
4
  var copy_1 = require("./copy");
5
5
  Object.defineProperty(exports, "copyClaudeFiles", { enumerable: true, get: function () { return copy_1.copyClaudeFiles; } });
6
+ var init_project_1 = require("./init-project");
7
+ Object.defineProperty(exports, "initProject", { enumerable: true, get: function () { return init_project_1.initProject; } });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Initialize GitHub Project integration in current directory
3
+ */
4
+ export declare function initProject(): Promise<void>;
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.initProject = initProject;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const readline = __importStar(require("readline"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ /**
45
+ * Prompt user for input using readline
46
+ */
47
+ function askQuestion(question) {
48
+ const rl = readline.createInterface({
49
+ input: process.stdin,
50
+ output: process.stdout,
51
+ });
52
+ return new Promise((resolve) => {
53
+ rl.question(question, (answer) => {
54
+ rl.close();
55
+ resolve(answer.trim());
56
+ });
57
+ });
58
+ }
59
+ /**
60
+ * Get the source .claude directory path (from package installation)
61
+ */
62
+ function getSourceClaudeDir() {
63
+ return path.resolve(__dirname, '..', '.claude');
64
+ }
65
+ /**
66
+ * Get the project .claude directory path (current working directory)
67
+ */
68
+ function getProjectClaudeDir() {
69
+ return path.join(process.cwd(), '.claude');
70
+ }
71
+ /**
72
+ * Prompt user for GitHub Project configuration
73
+ */
74
+ async function promptProjectConfig() {
75
+ console.log(chalk_1.default.cyan('\n📋 GitHub Project 설정\n'));
76
+ const owner = await askQuestion(chalk_1.default.white('GitHub Owner (org 또는 user): '));
77
+ if (!owner) {
78
+ console.log(chalk_1.default.red('Owner는 필수입니다.'));
79
+ process.exit(1);
80
+ }
81
+ const projectNumber = await askQuestion(chalk_1.default.white('Project Number: '));
82
+ if (!projectNumber || isNaN(Number(projectNumber))) {
83
+ console.log(chalk_1.default.red('유효한 Project Number를 입력하세요.'));
84
+ process.exit(1);
85
+ }
86
+ const repo = await askQuestion(chalk_1.default.white(`Repository (기본값: ${owner}/): `));
87
+ const finalRepo = repo || `${owner}/`;
88
+ return { owner, projectNumber, repo: finalRepo };
89
+ }
90
+ /**
91
+ * Create project.env file with GitHub Project configuration
92
+ */
93
+ function createProjectEnv(destDir, config) {
94
+ const envPath = path.join(destDir, 'project.env');
95
+ const content = `# GitHub Project 설정\nGITHUB_PROJECT_OWNER=${config.owner}\nGITHUB_PROJECT_NUMBER=${config.projectNumber}\nGITHUB_PROJECT_REPO=${config.repo}\n`;
96
+ fs.mkdirSync(destDir, { recursive: true });
97
+ fs.writeFileSync(envPath, content, 'utf-8');
98
+ console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), envPath)}`));
99
+ }
100
+ /**
101
+ * Copy a project-specific file from package source to project directory
102
+ */
103
+ function copyProjectFile(srcRelative, destDir) {
104
+ const srcPath = path.join(getSourceClaudeDir(), srcRelative);
105
+ const destPath = path.join(destDir, srcRelative);
106
+ if (!fs.existsSync(srcPath)) {
107
+ console.log(chalk_1.default.yellow(` ⚠ 소스 파일 없음: ${srcRelative}`));
108
+ return;
109
+ }
110
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
111
+ fs.copyFileSync(srcPath, destPath);
112
+ console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), destPath)}`));
113
+ }
114
+ /**
115
+ * Merge StartSession hook into settings.json
116
+ */
117
+ function mergeSettingsJson(destDir) {
118
+ const settingsPath = path.join(destDir, 'settings.json');
119
+ let settings = {};
120
+ if (fs.existsSync(settingsPath)) {
121
+ try {
122
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
123
+ }
124
+ catch {
125
+ console.log(chalk_1.default.yellow(' ⚠ 기존 settings.json 파싱 실패, 새로 생성합니다.'));
126
+ settings = {};
127
+ }
128
+ }
129
+ if (!settings.hooks) {
130
+ settings.hooks = {};
131
+ }
132
+ settings.hooks.StartSession = [
133
+ {
134
+ type: 'command',
135
+ command: 'bash .claude/hooks/task-loader.sh',
136
+ },
137
+ ];
138
+ fs.mkdirSync(destDir, { recursive: true });
139
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
140
+ console.log(chalk_1.default.green(` ✓ ${path.relative(process.cwd(), settingsPath)} (StartSession hook 추가)`));
141
+ }
142
+ /**
143
+ * Initialize GitHub Project integration in current directory
144
+ */
145
+ async function initProject() {
146
+ const config = await promptProjectConfig();
147
+ const destDir = getProjectClaudeDir();
148
+ console.log(chalk_1.default.cyan('\n🔧 프로젝트 설정 파일 생성\n'));
149
+ // 1. project.env 생성
150
+ createProjectEnv(destDir, config);
151
+ // 2. 프로젝트별 파일 복사
152
+ copyProjectFile('hooks/task-loader.sh', destDir);
153
+ copyProjectFile('agents/project-task-manager.md', destDir);
154
+ // 3. settings.json merge
155
+ mergeSettingsJson(destDir);
156
+ console.log(chalk_1.default.cyan('\n✅ GitHub Project 설정 완료!'));
157
+ console.log(chalk_1.default.gray(` Owner: ${config.owner}`));
158
+ console.log(chalk_1.default.gray(` Project: #${config.projectNumber}`));
159
+ console.log(chalk_1.default.gray(` Repo: ${config.repo}\n`));
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jun-claude-code",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Claude Code configuration template - copy .claude settings to your project",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/cli.js",