@niiiiiiile/iw-backlog-cli 0.1.0
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/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +45 -0
- package/dist/commands/issue.d.ts +2 -0
- package/dist/commands/issue.js +311 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.js +108 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +62 -0
- package/dist/commands/setup.d.ts +13 -0
- package/dist/commands/setup.js +32 -0
- package/dist/commands/whoami.d.ts +13 -0
- package/dist/commands/whoami.js +25 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +39 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +32 -0
- package/dist/shared.d.ts +8 -0
- package/dist/shared.js +8 -0
- package/package.json +51 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Teruaki Iwane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# backlog-cli
|
|
2
|
+
|
|
3
|
+
`backlog-cli` は [Nulab Backlog](https://backlog.com/) の課題・プロジェクトをターミナルから操作する CLI ツールです。`setup` / `profile` による接続情報管理と、`issue` / `project` / `whoami` による日常操作を一貫したフローで提供します。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
* [Overview](#overview)
|
|
12
|
+
* [Features](#features)
|
|
13
|
+
* [Installation](#installation)
|
|
14
|
+
+ [Install globally](#install-globally)
|
|
15
|
+
+ [Run with npx](#run-with-npx)
|
|
16
|
+
+ [Run from source](#run-from-source)
|
|
17
|
+
* [Quick start](#quick-start)
|
|
18
|
+
* [Profiles](#profiles)
|
|
19
|
+
* [Environment variables](#environment-variables)
|
|
20
|
+
* [Authentication check](#authentication-check)
|
|
21
|
+
* [Credential precedence](#credential-precedence)
|
|
22
|
+
* [Usage](#usage)
|
|
23
|
+
+ [Issue commands](#issue-commands)
|
|
24
|
+
+ [Project commands](#project-commands)
|
|
25
|
+
+ [Profile commands](#profile-commands)
|
|
26
|
+
* [Development](#development)
|
|
27
|
+
* [License](#license)
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
`backlog-cli` は Backlog の API をシンプルに扱うための CLI です。初回は `backlog setup` で接続情報を保存し、その後は `backlog issue ...` や `backlog project ...` をそのまま実行できます。複数スペースを扱う場合も `profile` で切り替えできます。
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- 課題の一覧取得・詳細確認・作成・更新・削除・コメント操作
|
|
36
|
+
- プロジェクト一覧取得・詳細確認
|
|
37
|
+
- `setup` / `profile` による複数スペース管理
|
|
38
|
+
- `whoami` による接続確認
|
|
39
|
+
- フラグ / プロファイル / 環境変数の優先順位による資格情報解決
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
### Install globally
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install -g @niiiiiiile/iw-backlog-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Run with npx
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @niiiiiiile/iw-backlog-cli --help
|
|
53
|
+
npx @niiiiiiile/iw-backlog-cli issue list
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Run from source
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/Niiiiile/backlog-cli.git
|
|
60
|
+
cd backlog-cli
|
|
61
|
+
npm install
|
|
62
|
+
npm run dev -- --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
backlog setup \
|
|
69
|
+
--url https://yourspace.backlog.com \
|
|
70
|
+
--api-key YOUR_API_KEY \
|
|
71
|
+
--project-key MYPROJECT
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
初回登録時は `default` プロファイルに保存され、自動でデフォルトになります。
|
|
75
|
+
|
|
76
|
+
## Profiles
|
|
77
|
+
|
|
78
|
+
複数スペースを使い分ける場合は `profile` を使います。
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# work プロファイルを追加
|
|
82
|
+
backlog profile add work \
|
|
83
|
+
--url https://yourspace.backlog.com \
|
|
84
|
+
--api-key YOUR_API_KEY \
|
|
85
|
+
--project-key MYPROJECT
|
|
86
|
+
|
|
87
|
+
# プロファイル一覧
|
|
88
|
+
backlog profile list
|
|
89
|
+
|
|
90
|
+
# デフォルト切り替え
|
|
91
|
+
backlog profile use work
|
|
92
|
+
|
|
93
|
+
# 実行時だけ別プロファイルを使う
|
|
94
|
+
backlog issue list --profile work
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Environment variables
|
|
98
|
+
|
|
99
|
+
`.env.example` を参考に環境変数を設定することもできます。
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
export BACKLOG_BASE_URL=https://yourspace.backlog.com
|
|
103
|
+
export BACKLOG_API_KEY=your_api_key_here
|
|
104
|
+
export BACKLOG_PROJECT_KEY=MYPROJECT
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Authentication check
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
backlog whoami
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Credential precedence
|
|
114
|
+
|
|
115
|
+
1. コマンドフラグ(`--url`, `--api-key`, `--project-key`)
|
|
116
|
+
2. 設定ファイルのプロファイル(`--profile` または default)
|
|
117
|
+
3. 環境変数(`BACKLOG_BASE_URL`, `BACKLOG_API_KEY`, `BACKLOG_PROJECT_KEY`)
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
backlog --help
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Issue commands
|
|
126
|
+
|
|
127
|
+
| サブコマンド | 説明 |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `list` | 課題の一覧を取得(キーワード・ステータス・担当者でフィルタ可) |
|
|
130
|
+
| `get <issueIdOrKey>` | 課題の詳細を取得 |
|
|
131
|
+
| `add` | 課題を作成 |
|
|
132
|
+
| `update <issueIdOrKey>` | 課題を更新(ステータス変更・コメント追加など) |
|
|
133
|
+
| `delete <issueIdOrKey>` | 課題を削除 |
|
|
134
|
+
| `comments <issueIdOrKey>` | コメント一覧を取得 |
|
|
135
|
+
| `comment-add <issueIdOrKey> <content>` | コメントを追加 |
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 最新20件を取得
|
|
139
|
+
backlog issue list
|
|
140
|
+
|
|
141
|
+
# キーワード検索
|
|
142
|
+
backlog issue list --keyword バグ
|
|
143
|
+
|
|
144
|
+
# 未対応・処理中のみ
|
|
145
|
+
backlog issue list --status-id 1,2
|
|
146
|
+
|
|
147
|
+
# 課題の詳細
|
|
148
|
+
backlog issue get PROJ-123
|
|
149
|
+
|
|
150
|
+
# 課題を作成
|
|
151
|
+
backlog issue add --summary "画面表示のバグ" --issue-type-id 2 --priority-id 2
|
|
152
|
+
|
|
153
|
+
# ステータスを完了に変更しコメントを追加
|
|
154
|
+
backlog issue update PROJ-123 --status-id 4 --comment "対応完了"
|
|
155
|
+
|
|
156
|
+
# コメント一覧
|
|
157
|
+
backlog issue comments PROJ-123
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Project commands
|
|
161
|
+
|
|
162
|
+
| サブコマンド | 説明 |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `list` | 参加しているプロジェクトの一覧を取得 |
|
|
165
|
+
| `get <projectIdOrKey>` | プロジェクトの詳細を取得 |
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
backlog project list
|
|
169
|
+
backlog project get MYPROJECT
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Profile commands
|
|
173
|
+
|
|
174
|
+
| サブコマンド | 説明 |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `list` | 登録済みプロファイルの一覧を表示 |
|
|
177
|
+
| `add <name>` | プロファイルを追加・更新 |
|
|
178
|
+
| `remove <name>` | プロファイルを削除 |
|
|
179
|
+
| `use <name>` | デフォルトプロファイルを変更 |
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
backlog profile add personal --url https://myspace.backlog.jp --api-key KEY2 --project-key MYPROJ
|
|
183
|
+
|
|
184
|
+
# プロファイル一覧
|
|
185
|
+
backlog profile list
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Development
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# 依存関係のインストール
|
|
192
|
+
npm install
|
|
193
|
+
|
|
194
|
+
# TypeScript のビルド
|
|
195
|
+
npm run build
|
|
196
|
+
|
|
197
|
+
# ビルドなしで直接実行
|
|
198
|
+
npm run dev -- --help
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## ライセンス
|
|
202
|
+
|
|
203
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface BacklogClient {
|
|
2
|
+
get: <T>(path: string, params?: Record<string, unknown>) => Promise<T>;
|
|
3
|
+
post: <T>(path: string, body?: Record<string, unknown>) => Promise<T>;
|
|
4
|
+
patch: <T>(path: string, body?: Record<string, unknown>) => Promise<T>;
|
|
5
|
+
delete: <T>(path: string) => Promise<T>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createClient(baseUrl: string, apiKey: string): BacklogClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function toFormPairs(obj) {
|
|
2
|
+
const pairs = [];
|
|
3
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
4
|
+
if (value === undefined || value === null)
|
|
5
|
+
continue;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
for (const item of value) {
|
|
8
|
+
pairs.push([`${key}[]`, String(item)]);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
pairs.push([key, String(value)]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return pairs;
|
|
16
|
+
}
|
|
17
|
+
export function createClient(baseUrl, apiKey) {
|
|
18
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
19
|
+
async function request(method, path, params, body) {
|
|
20
|
+
const url = new URL(`${base}/api/v2${path}`);
|
|
21
|
+
url.searchParams.set('apiKey', apiKey);
|
|
22
|
+
if (params) {
|
|
23
|
+
for (const [k, v] of toFormPairs(params)) {
|
|
24
|
+
url.searchParams.append(k, v);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const init = { method };
|
|
28
|
+
if (body) {
|
|
29
|
+
init.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
30
|
+
init.body = new URLSearchParams(toFormPairs(body));
|
|
31
|
+
}
|
|
32
|
+
const res = await fetch(url.toString(), init);
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const text = await res.text().catch(() => '');
|
|
35
|
+
throw new Error(`Backlog API エラー [${res.status}]: ${text}`);
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
get: (path, params) => request('GET', path, params),
|
|
41
|
+
post: (path, body) => request('POST', path, undefined, body),
|
|
42
|
+
patch: (path, body) => request('PATCH', path, undefined, body),
|
|
43
|
+
delete: (path) => request('DELETE', path),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { Cli, z } from 'incur';
|
|
2
|
+
import { createClient } from '../client.js';
|
|
3
|
+
import { resolveCredentials } from '../config.js';
|
|
4
|
+
import { authOptions } from '../shared.js';
|
|
5
|
+
async function resolveProjectId(client, projectKey) {
|
|
6
|
+
const project = await client.get(`/projects/${projectKey}`);
|
|
7
|
+
return project.id;
|
|
8
|
+
}
|
|
9
|
+
function parseIds(value) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return undefined;
|
|
12
|
+
return value
|
|
13
|
+
.split(',')
|
|
14
|
+
.map((s) => Number(s.trim()))
|
|
15
|
+
.filter((n) => !Number.isNaN(n));
|
|
16
|
+
}
|
|
17
|
+
function formatIssue(issue) {
|
|
18
|
+
return {
|
|
19
|
+
key: issue.issueKey,
|
|
20
|
+
summary: issue.summary,
|
|
21
|
+
status: issue.status.name,
|
|
22
|
+
priority: issue.priority.name,
|
|
23
|
+
issueType: issue.issueType.name,
|
|
24
|
+
assignee: issue.assignee?.name ?? null,
|
|
25
|
+
dueDate: issue.dueDate ?? null,
|
|
26
|
+
created: issue.created,
|
|
27
|
+
updated: issue.updated,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export const issueCli = Cli.create('issue', {
|
|
31
|
+
description: '課題操作',
|
|
32
|
+
});
|
|
33
|
+
issueCli.command('list', {
|
|
34
|
+
description: '課題の一覧を取得',
|
|
35
|
+
options: z.object({
|
|
36
|
+
keyword: z.string().optional().describe('キーワード検索'),
|
|
37
|
+
statusId: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('ステータス ID(カンマ区切り)例: 1,2 (1=未対応,2=処理中,3=処理済み,4=完了)'),
|
|
41
|
+
assigneeId: z.string().optional().describe('担当者ユーザー ID(カンマ区切り)'),
|
|
42
|
+
count: z.coerce.number().default(20).describe('取得件数(1〜100)'),
|
|
43
|
+
offset: z.coerce.number().default(0).describe('オフセット'),
|
|
44
|
+
sort: z
|
|
45
|
+
.enum(['created', 'updated', 'status', 'priority', 'dueDate', 'summary'])
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('ソートフィールド'),
|
|
48
|
+
order: z.enum(['asc', 'desc']).default('desc').describe('ソート順'),
|
|
49
|
+
...authOptions.shape,
|
|
50
|
+
}),
|
|
51
|
+
examples: [
|
|
52
|
+
{ description: 'デフォルトプロジェクトの課題一覧(最新20件)' },
|
|
53
|
+
{ options: { keyword: 'バグ' }, description: 'キーワード "バグ" で検索' },
|
|
54
|
+
{ options: { statusId: '1,2' }, description: '未対応・処理中の課題のみ' },
|
|
55
|
+
{ options: { count: 50, sort: 'dueDate', order: 'asc' }, description: '期限日順で50件取得' },
|
|
56
|
+
{ options: { profile: 'work' }, description: '別プロファイルで取得' },
|
|
57
|
+
],
|
|
58
|
+
async run(c) {
|
|
59
|
+
const creds = resolveCredentials(c.options);
|
|
60
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
61
|
+
const projectId = await resolveProjectId(client, creds.projectKey);
|
|
62
|
+
const params = {
|
|
63
|
+
projectId: [projectId],
|
|
64
|
+
count: c.options.count,
|
|
65
|
+
offset: c.options.offset,
|
|
66
|
+
order: c.options.order,
|
|
67
|
+
};
|
|
68
|
+
if (c.options.keyword)
|
|
69
|
+
params.keyword = c.options.keyword;
|
|
70
|
+
if (c.options.statusId)
|
|
71
|
+
params.statusId = parseIds(c.options.statusId);
|
|
72
|
+
if (c.options.assigneeId)
|
|
73
|
+
params.assigneeId = parseIds(c.options.assigneeId);
|
|
74
|
+
if (c.options.sort)
|
|
75
|
+
params.sort = c.options.sort;
|
|
76
|
+
const issues = await client.get('/issues', params);
|
|
77
|
+
return {
|
|
78
|
+
count: issues.length,
|
|
79
|
+
issues: issues.map(formatIssue),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
issueCli.command('get', {
|
|
84
|
+
description: '課題の詳細を取得',
|
|
85
|
+
args: z.object({
|
|
86
|
+
issueIdOrKey: z.string().describe('課題 ID または課題キー(例: PROJ-123)'),
|
|
87
|
+
}),
|
|
88
|
+
options: authOptions,
|
|
89
|
+
examples: [
|
|
90
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, description: '課題キーで詳細取得' },
|
|
91
|
+
{ args: { issueIdOrKey: '123456' }, description: '課題 ID で詳細取得' },
|
|
92
|
+
],
|
|
93
|
+
async run(c) {
|
|
94
|
+
const creds = resolveCredentials(c.options);
|
|
95
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
96
|
+
const issue = await client.get(`/issues/${c.args.issueIdOrKey}`);
|
|
97
|
+
return {
|
|
98
|
+
key: issue.issueKey,
|
|
99
|
+
summary: issue.summary,
|
|
100
|
+
description: issue.description ?? '',
|
|
101
|
+
status: issue.status.name,
|
|
102
|
+
priority: issue.priority.name,
|
|
103
|
+
issueType: issue.issueType.name,
|
|
104
|
+
assignee: issue.assignee?.name ?? null,
|
|
105
|
+
startDate: issue.startDate ?? null,
|
|
106
|
+
dueDate: issue.dueDate ?? null,
|
|
107
|
+
estimatedHours: issue.estimatedHours ?? null,
|
|
108
|
+
actualHours: issue.actualHours ?? null,
|
|
109
|
+
created: issue.created,
|
|
110
|
+
updated: issue.updated,
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
issueCli.command('add', {
|
|
115
|
+
description: '課題を作成',
|
|
116
|
+
options: z.object({
|
|
117
|
+
summary: z.string().describe('課題の件名'),
|
|
118
|
+
issueTypeId: z.coerce.number().describe('課題種別 ID'),
|
|
119
|
+
priorityId: z.coerce.number().describe('優先度 ID(2=高,3=中,4=低)'),
|
|
120
|
+
description: z.string().optional().describe('課題の詳細'),
|
|
121
|
+
assigneeId: z.coerce.number().optional().describe('担当者のユーザー ID'),
|
|
122
|
+
startDate: z.string().optional().describe('開始日(yyyy-MM-dd)'),
|
|
123
|
+
dueDate: z.string().optional().describe('期限日(yyyy-MM-dd)'),
|
|
124
|
+
estimatedHours: z.coerce.number().optional().describe('予定時間'),
|
|
125
|
+
parentIssueId: z.coerce.number().optional().describe('親課題 ID'),
|
|
126
|
+
...authOptions.shape,
|
|
127
|
+
}),
|
|
128
|
+
examples: [
|
|
129
|
+
{
|
|
130
|
+
options: { summary: '画面表示のバグ', issueTypeId: 2, priorityId: 2 },
|
|
131
|
+
description: '最小構成で課題を作成(件名・種別・優先度)',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
options: {
|
|
135
|
+
summary: '新機能の実装',
|
|
136
|
+
issueTypeId: 1,
|
|
137
|
+
priorityId: 3,
|
|
138
|
+
description: '詳細な説明',
|
|
139
|
+
dueDate: '2026-04-30',
|
|
140
|
+
estimatedHours: 8,
|
|
141
|
+
},
|
|
142
|
+
description: '詳細情報を含めて課題を作成',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
async run(c) {
|
|
146
|
+
const creds = resolveCredentials(c.options);
|
|
147
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
148
|
+
const projectId = await resolveProjectId(client, creds.projectKey);
|
|
149
|
+
const body = {
|
|
150
|
+
projectId,
|
|
151
|
+
summary: c.options.summary,
|
|
152
|
+
issueTypeId: c.options.issueTypeId,
|
|
153
|
+
priorityId: c.options.priorityId,
|
|
154
|
+
};
|
|
155
|
+
if (c.options.description)
|
|
156
|
+
body.description = c.options.description;
|
|
157
|
+
if (c.options.assigneeId)
|
|
158
|
+
body.assigneeId = c.options.assigneeId;
|
|
159
|
+
if (c.options.startDate)
|
|
160
|
+
body.startDate = c.options.startDate;
|
|
161
|
+
if (c.options.dueDate)
|
|
162
|
+
body.dueDate = c.options.dueDate;
|
|
163
|
+
if (c.options.estimatedHours)
|
|
164
|
+
body.estimatedHours = c.options.estimatedHours;
|
|
165
|
+
if (c.options.parentIssueId)
|
|
166
|
+
body.parentIssueId = c.options.parentIssueId;
|
|
167
|
+
const issue = await client.post('/issues', body);
|
|
168
|
+
return c.ok({
|
|
169
|
+
key: issue.issueKey,
|
|
170
|
+
summary: issue.summary,
|
|
171
|
+
status: issue.status.name,
|
|
172
|
+
priority: issue.priority.name,
|
|
173
|
+
}, { cta: { commands: [`backlog issue get ${issue.issueKey}`] } });
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
issueCli.command('update', {
|
|
177
|
+
description: '課題を更新',
|
|
178
|
+
args: z.object({
|
|
179
|
+
issueIdOrKey: z.string().describe('課題 ID または課題キー(例: PROJ-123)'),
|
|
180
|
+
}),
|
|
181
|
+
options: z.object({
|
|
182
|
+
summary: z.string().optional().describe('課題の件名'),
|
|
183
|
+
description: z.string().optional().describe('課題の詳細'),
|
|
184
|
+
statusId: z.coerce
|
|
185
|
+
.number()
|
|
186
|
+
.optional()
|
|
187
|
+
.describe('ステータス ID(1=未対応,2=処理中,3=処理済み,4=完了)'),
|
|
188
|
+
priorityId: z.coerce.number().optional().describe('優先度 ID(2=高,3=中,4=低)'),
|
|
189
|
+
assigneeId: z.coerce.number().optional().describe('担当者のユーザー ID'),
|
|
190
|
+
startDate: z.string().optional().describe('開始日(yyyy-MM-dd)'),
|
|
191
|
+
dueDate: z.string().optional().describe('期限日(yyyy-MM-dd)'),
|
|
192
|
+
estimatedHours: z.coerce.number().optional().describe('予定時間'),
|
|
193
|
+
actualHours: z.coerce.number().optional().describe('実績時間'),
|
|
194
|
+
comment: z.string().optional().describe('更新時のコメント'),
|
|
195
|
+
...authOptions.shape,
|
|
196
|
+
}),
|
|
197
|
+
examples: [
|
|
198
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, options: { statusId: 2 }, description: 'ステータスを処理中に変更' },
|
|
199
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, options: { statusId: 4, comment: '対応完了' }, description: 'ステータスを完了にしてコメント追加' },
|
|
200
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, options: { summary: '新しいタイトル', dueDate: '2026-05-01' }, description: '件名と期限日を更新' },
|
|
201
|
+
],
|
|
202
|
+
async run(c) {
|
|
203
|
+
const creds = resolveCredentials(c.options);
|
|
204
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
205
|
+
const body = {};
|
|
206
|
+
if (c.options.summary)
|
|
207
|
+
body.summary = c.options.summary;
|
|
208
|
+
if (c.options.description)
|
|
209
|
+
body.description = c.options.description;
|
|
210
|
+
if (c.options.statusId)
|
|
211
|
+
body.statusId = c.options.statusId;
|
|
212
|
+
if (c.options.priorityId)
|
|
213
|
+
body.priorityId = c.options.priorityId;
|
|
214
|
+
if (c.options.assigneeId)
|
|
215
|
+
body.assigneeId = c.options.assigneeId;
|
|
216
|
+
if (c.options.startDate)
|
|
217
|
+
body.startDate = c.options.startDate;
|
|
218
|
+
if (c.options.dueDate)
|
|
219
|
+
body.dueDate = c.options.dueDate;
|
|
220
|
+
if (c.options.estimatedHours !== undefined)
|
|
221
|
+
body.estimatedHours = c.options.estimatedHours;
|
|
222
|
+
if (c.options.actualHours !== undefined)
|
|
223
|
+
body.actualHours = c.options.actualHours;
|
|
224
|
+
if (c.options.comment)
|
|
225
|
+
body.comment = c.options.comment;
|
|
226
|
+
const issue = await client.patch(`/issues/${c.args.issueIdOrKey}`, body);
|
|
227
|
+
return {
|
|
228
|
+
key: issue.issueKey,
|
|
229
|
+
summary: issue.summary,
|
|
230
|
+
status: issue.status.name,
|
|
231
|
+
updated: issue.updated,
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
issueCli.command('delete', {
|
|
236
|
+
description: '課題を削除',
|
|
237
|
+
args: z.object({
|
|
238
|
+
issueIdOrKey: z.string().describe('課題 ID または課題キー(例: PROJ-123)'),
|
|
239
|
+
}),
|
|
240
|
+
options: authOptions,
|
|
241
|
+
examples: [
|
|
242
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, description: '課題を完全削除(取り消し不可)' },
|
|
243
|
+
],
|
|
244
|
+
async run(c) {
|
|
245
|
+
const creds = resolveCredentials(c.options);
|
|
246
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
247
|
+
const issue = await client.delete(`/issues/${c.args.issueIdOrKey}`);
|
|
248
|
+
return { deleted: issue.issueKey };
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
issueCli.command('comments', {
|
|
252
|
+
description: '課題のコメント一覧を取得',
|
|
253
|
+
args: z.object({
|
|
254
|
+
issueIdOrKey: z.string().describe('課題 ID または課題キー(例: PROJ-123)'),
|
|
255
|
+
}),
|
|
256
|
+
options: z.object({
|
|
257
|
+
count: z.coerce.number().default(20).describe('取得件数(1〜200)'),
|
|
258
|
+
minId: z.coerce.number().optional().describe('最小コメント ID(この ID より大きいコメントを取得)'),
|
|
259
|
+
maxId: z.coerce.number().optional().describe('最大コメント ID(この ID より小さいコメントを取得)'),
|
|
260
|
+
order: z.enum(['asc', 'desc']).default('asc').describe('ソート順'),
|
|
261
|
+
...authOptions.shape,
|
|
262
|
+
}),
|
|
263
|
+
examples: [
|
|
264
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, description: '課題のコメントを時系列順で取得' },
|
|
265
|
+
{ args: { issueIdOrKey: 'PROJ-123' }, options: { order: 'desc' }, description: '最新コメントから取得' },
|
|
266
|
+
],
|
|
267
|
+
async run(c) {
|
|
268
|
+
const creds = resolveCredentials(c.options);
|
|
269
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
270
|
+
const params = {
|
|
271
|
+
count: c.options.count,
|
|
272
|
+
order: c.options.order,
|
|
273
|
+
};
|
|
274
|
+
if (c.options.minId !== undefined)
|
|
275
|
+
params.minId = c.options.minId;
|
|
276
|
+
if (c.options.maxId !== undefined)
|
|
277
|
+
params.maxId = c.options.maxId;
|
|
278
|
+
const comments = await client.get(`/issues/${c.args.issueIdOrKey}/comments`, params);
|
|
279
|
+
return {
|
|
280
|
+
count: comments.length,
|
|
281
|
+
comments: comments.map((cm) => ({
|
|
282
|
+
id: cm.id,
|
|
283
|
+
content: cm.content,
|
|
284
|
+
createdUser: cm.createdUser.name,
|
|
285
|
+
created: cm.created,
|
|
286
|
+
updated: cm.updated,
|
|
287
|
+
})),
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
issueCli.command('comment-add', {
|
|
292
|
+
description: '課題にコメントを追加',
|
|
293
|
+
args: z.object({
|
|
294
|
+
issueIdOrKey: z.string().describe('課題 ID または課題キー(例: PROJ-123)'),
|
|
295
|
+
content: z.string().describe('コメント本文'),
|
|
296
|
+
}),
|
|
297
|
+
options: authOptions,
|
|
298
|
+
examples: [
|
|
299
|
+
{ args: { issueIdOrKey: 'PROJ-123', content: '対応完了しました。確認をお願いします。' }, description: '課題にコメントを追加' },
|
|
300
|
+
],
|
|
301
|
+
async run(c) {
|
|
302
|
+
const creds = resolveCredentials(c.options);
|
|
303
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
304
|
+
const comment = await client.post(`/issues/${c.args.issueIdOrKey}/comments`, { content: c.args.content });
|
|
305
|
+
return {
|
|
306
|
+
id: comment.id,
|
|
307
|
+
content: comment.content,
|
|
308
|
+
created: comment.created,
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Cli, z } from 'incur';
|
|
2
|
+
import { readConfig, writeConfig } from '../config.js';
|
|
3
|
+
export const profileCli = Cli.create('profile', {
|
|
4
|
+
description: 'プロファイル管理(Backlog スペース接続情報)',
|
|
5
|
+
});
|
|
6
|
+
profileCli.command('list', {
|
|
7
|
+
description: '設定済みプロファイルの一覧を表示',
|
|
8
|
+
examples: [
|
|
9
|
+
{ description: '登録済みプロファイルとデフォルトを確認' },
|
|
10
|
+
],
|
|
11
|
+
run() {
|
|
12
|
+
const config = readConfig();
|
|
13
|
+
const profiles = Object.entries(config.profiles).map(([name, p]) => ({
|
|
14
|
+
name,
|
|
15
|
+
baseUrl: p.baseUrl,
|
|
16
|
+
projectKey: p.projectKey,
|
|
17
|
+
isDefault: name === config.default,
|
|
18
|
+
}));
|
|
19
|
+
return {
|
|
20
|
+
default: config.default ?? null,
|
|
21
|
+
profiles,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
profileCli.command('add', {
|
|
26
|
+
description: 'プロファイルを追加・更新',
|
|
27
|
+
args: z.object({
|
|
28
|
+
name: z.string().describe('プロファイル名(例: work, personal)'),
|
|
29
|
+
}),
|
|
30
|
+
options: z.object({
|
|
31
|
+
url: z.string().describe('Backlog スペース URL(例: https://yourspace.backlog.com)'),
|
|
32
|
+
apiKey: z.string().describe('Backlog API キー'),
|
|
33
|
+
projectKey: z.string().describe('デフォルトプロジェクトキー(例: MYPROJECT)'),
|
|
34
|
+
default: z.boolean().optional().describe('このプロファイルをデフォルトに設定'),
|
|
35
|
+
}),
|
|
36
|
+
examples: [
|
|
37
|
+
{
|
|
38
|
+
args: { name: 'work' },
|
|
39
|
+
options: { url: 'https://yourspace.backlog.com', apiKey: 'YOUR_API_KEY', projectKey: 'MYPROJECT' },
|
|
40
|
+
description: 'work プロファイルを追加(初回登録時は自動でデフォルトになる)',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
args: { name: 'personal' },
|
|
44
|
+
options: { url: 'https://myspace.backlog.jp', apiKey: 'ANOTHER_KEY', projectKey: 'MYPROJ', default: true },
|
|
45
|
+
description: 'personal プロファイルをデフォルトに設定しながら追加',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
run(c) {
|
|
49
|
+
const config = readConfig();
|
|
50
|
+
config.profiles[c.args.name] = {
|
|
51
|
+
baseUrl: c.options.url.replace(/\/$/, ''),
|
|
52
|
+
apiKey: c.options.apiKey,
|
|
53
|
+
projectKey: c.options.projectKey,
|
|
54
|
+
};
|
|
55
|
+
const isFirst = Object.keys(config.profiles).length === 1;
|
|
56
|
+
if (c.options.default || isFirst) {
|
|
57
|
+
config.default = c.args.name;
|
|
58
|
+
}
|
|
59
|
+
writeConfig(config);
|
|
60
|
+
return {
|
|
61
|
+
added: c.args.name,
|
|
62
|
+
isDefault: config.default === c.args.name,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
profileCli.command('remove', {
|
|
67
|
+
description: 'プロファイルを削除',
|
|
68
|
+
args: z.object({
|
|
69
|
+
name: z.string().describe('削除するプロファイル名'),
|
|
70
|
+
}),
|
|
71
|
+
run(c) {
|
|
72
|
+
const config = readConfig();
|
|
73
|
+
if (!config.profiles[c.args.name]) {
|
|
74
|
+
return c.error({
|
|
75
|
+
code: 'PROFILE_NOT_FOUND',
|
|
76
|
+
message: `プロファイル "${c.args.name}" が見つかりません`,
|
|
77
|
+
retryable: false,
|
|
78
|
+
cta: { commands: ['backlog profile list'] },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
delete config.profiles[c.args.name];
|
|
82
|
+
if (config.default === c.args.name) {
|
|
83
|
+
config.default = Object.keys(config.profiles)[0];
|
|
84
|
+
}
|
|
85
|
+
writeConfig(config);
|
|
86
|
+
return { removed: c.args.name, newDefault: config.default ?? null };
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
profileCli.command('use', {
|
|
90
|
+
description: 'デフォルトプロファイルを変更',
|
|
91
|
+
args: z.object({
|
|
92
|
+
name: z.string().describe('デフォルトに設定するプロファイル名'),
|
|
93
|
+
}),
|
|
94
|
+
run(c) {
|
|
95
|
+
const config = readConfig();
|
|
96
|
+
if (!config.profiles[c.args.name]) {
|
|
97
|
+
return c.error({
|
|
98
|
+
code: 'PROFILE_NOT_FOUND',
|
|
99
|
+
message: `プロファイル "${c.args.name}" が見つかりません`,
|
|
100
|
+
retryable: false,
|
|
101
|
+
cta: { commands: ['backlog profile list'] },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
config.default = c.args.name;
|
|
105
|
+
writeConfig(config);
|
|
106
|
+
return { default: c.args.name };
|
|
107
|
+
},
|
|
108
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Cli, z } from 'incur';
|
|
2
|
+
import { createClient } from '../client.js';
|
|
3
|
+
import { resolveCredentials } from '../config.js';
|
|
4
|
+
import { authOptions } from '../shared.js';
|
|
5
|
+
export const projectCli = Cli.create('project', {
|
|
6
|
+
description: 'プロジェクト操作',
|
|
7
|
+
});
|
|
8
|
+
projectCli.command('list', {
|
|
9
|
+
description: '参加しているプロジェクトの一覧を取得',
|
|
10
|
+
options: z.object({
|
|
11
|
+
archived: z.boolean().optional().describe('アーカイブ済みプロジェクトのみ取得'),
|
|
12
|
+
all: z.boolean().optional().describe('全プロジェクトを取得(管理者のみ)'),
|
|
13
|
+
...authOptions.shape,
|
|
14
|
+
}),
|
|
15
|
+
examples: [
|
|
16
|
+
{ description: 'アクティブなプロジェクト一覧' },
|
|
17
|
+
{ options: { archived: true }, description: 'アーカイブ済みプロジェクト一覧' },
|
|
18
|
+
],
|
|
19
|
+
async run(c) {
|
|
20
|
+
const creds = resolveCredentials(c.options);
|
|
21
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
22
|
+
const params = {};
|
|
23
|
+
if (c.options.archived !== undefined)
|
|
24
|
+
params.archived = c.options.archived;
|
|
25
|
+
if (c.options.all !== undefined)
|
|
26
|
+
params.all = c.options.all;
|
|
27
|
+
const projects = await client.get('/projects', params);
|
|
28
|
+
return {
|
|
29
|
+
count: projects.length,
|
|
30
|
+
projects: projects.map((p) => ({
|
|
31
|
+
id: p.id,
|
|
32
|
+
key: p.projectKey,
|
|
33
|
+
name: p.name,
|
|
34
|
+
archived: p.archived,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
projectCli.command('get', {
|
|
40
|
+
description: 'プロジェクトの詳細を取得',
|
|
41
|
+
args: z.object({
|
|
42
|
+
projectIdOrKey: z.string().describe('プロジェクト ID またはプロジェクトキー'),
|
|
43
|
+
}),
|
|
44
|
+
options: authOptions,
|
|
45
|
+
examples: [
|
|
46
|
+
{ args: { projectIdOrKey: 'MYPROJECT' }, description: 'プロジェクトキーで詳細取得' },
|
|
47
|
+
],
|
|
48
|
+
async run(c) {
|
|
49
|
+
const creds = resolveCredentials(c.options);
|
|
50
|
+
const client = createClient(creds.baseUrl, creds.apiKey);
|
|
51
|
+
const project = await client.get(`/projects/${c.args.projectIdOrKey}`);
|
|
52
|
+
return {
|
|
53
|
+
id: project.id,
|
|
54
|
+
key: project.projectKey,
|
|
55
|
+
name: project.name,
|
|
56
|
+
archived: project.archived,
|
|
57
|
+
useWiki: project.useWiki,
|
|
58
|
+
useGit: project.useGit,
|
|
59
|
+
textFormattingRule: project.textFormattingRule,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'incur';
|
|
2
|
+
export declare const setupOptions: z.ZodObject<{
|
|
3
|
+
profile: z.ZodOptional<z.ZodString>;
|
|
4
|
+
url: z.ZodString;
|
|
5
|
+
apiKey: z.ZodString;
|
|
6
|
+
projectKey: z.ZodString;
|
|
7
|
+
default: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare function runSetup(options: z.infer<typeof setupOptions>): {
|
|
10
|
+
saved: string;
|
|
11
|
+
isDefault: boolean;
|
|
12
|
+
hint: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'incur';
|
|
2
|
+
import { readConfig, writeConfig } from '../config.js';
|
|
3
|
+
const DEFAULT_PROFILE_NAME = 'default';
|
|
4
|
+
export const setupOptions = z.object({
|
|
5
|
+
profile: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe(`保存先プロファイル名(省略時: ${DEFAULT_PROFILE_NAME})`),
|
|
9
|
+
url: z.string().describe('Backlog スペース URL(例: https://yourspace.backlog.com)'),
|
|
10
|
+
apiKey: z.string().describe('Backlog API キー'),
|
|
11
|
+
projectKey: z.string().describe('デフォルトプロジェクトキー(例: MYPROJECT)'),
|
|
12
|
+
default: z.boolean().optional().describe('このプロファイルをデフォルトに設定'),
|
|
13
|
+
});
|
|
14
|
+
export function runSetup(options) {
|
|
15
|
+
const config = readConfig();
|
|
16
|
+
const name = options.profile ?? DEFAULT_PROFILE_NAME;
|
|
17
|
+
config.profiles[name] = {
|
|
18
|
+
baseUrl: options.url.replace(/\/$/, ''),
|
|
19
|
+
apiKey: options.apiKey,
|
|
20
|
+
projectKey: options.projectKey,
|
|
21
|
+
};
|
|
22
|
+
const isFirst = Object.keys(config.profiles).length === 1;
|
|
23
|
+
if (options.default || isFirst || !config.default) {
|
|
24
|
+
config.default = name;
|
|
25
|
+
}
|
|
26
|
+
writeConfig(config);
|
|
27
|
+
return {
|
|
28
|
+
saved: name,
|
|
29
|
+
isDefault: config.default === name,
|
|
30
|
+
hint: '接続確認は `backlog whoami` を実行してください',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function loadWhoami(opts: {
|
|
2
|
+
profile?: string;
|
|
3
|
+
url?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
projectKey?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
id: number;
|
|
8
|
+
userId: string;
|
|
9
|
+
name: string;
|
|
10
|
+
mailAddress: string | null;
|
|
11
|
+
lang: string | null;
|
|
12
|
+
projectKey: string | null;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createClient } from '../client.js';
|
|
2
|
+
import { readConfig } from '../config.js';
|
|
3
|
+
export async function loadWhoami(opts) {
|
|
4
|
+
const config = readConfig();
|
|
5
|
+
const profileName = opts.profile ?? config.default;
|
|
6
|
+
const profileData = profileName ? config.profiles[profileName] : undefined;
|
|
7
|
+
const baseUrl = opts.url ?? profileData?.baseUrl ?? process.env.BACKLOG_BASE_URL;
|
|
8
|
+
const apiKey = opts.apiKey ?? profileData?.apiKey ?? process.env.BACKLOG_API_KEY;
|
|
9
|
+
if (!baseUrl) {
|
|
10
|
+
throw new Error('Backlog URL が未設定です。--url フラグ、プロファイル、または BACKLOG_BASE_URL 環境変数を設定してください。');
|
|
11
|
+
}
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error('API キーが未設定です。--api-key フラグ、プロファイル、または BACKLOG_API_KEY 環境変数を設定してください。');
|
|
14
|
+
}
|
|
15
|
+
const client = createClient(baseUrl.replace(/\/$/, ''), apiKey);
|
|
16
|
+
const me = await client.get('/users/myself');
|
|
17
|
+
return {
|
|
18
|
+
id: me.id,
|
|
19
|
+
userId: me.userId,
|
|
20
|
+
name: me.name,
|
|
21
|
+
mailAddress: me.mailAddress ?? null,
|
|
22
|
+
lang: me.lang ?? null,
|
|
23
|
+
projectKey: opts.projectKey ?? profileData?.projectKey ?? process.env.BACKLOG_PROJECT_KEY ?? null,
|
|
24
|
+
};
|
|
25
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Profile {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
projectKey: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Config {
|
|
7
|
+
default?: string;
|
|
8
|
+
profiles: Record<string, Profile>;
|
|
9
|
+
}
|
|
10
|
+
export interface Credentials {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
projectKey: string;
|
|
13
|
+
apiKey: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolveOptions {
|
|
16
|
+
profile?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
projectKey?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function readConfig(): Config;
|
|
22
|
+
export declare function writeConfig(config: Config): void;
|
|
23
|
+
/**
|
|
24
|
+
* 認証情報を以下の優先順位で解決する:
|
|
25
|
+
* 1. コマンドフラグ (--url / --api-key / --project-key)
|
|
26
|
+
* 2. 設定ファイルのプロファイル (--profile または default)
|
|
27
|
+
* 3. 環境変数 (BACKLOG_BASE_URL / BACKLOG_API_KEY / BACKLOG_PROJECT_KEY)
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveCredentials(opts: ResolveOptions): Credentials;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const CONFIG_PATH = join(homedir(), '.config', 'backlog-cli', 'config.json');
|
|
5
|
+
export function readConfig() {
|
|
6
|
+
if (!existsSync(CONFIG_PATH))
|
|
7
|
+
return { profiles: {} };
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return { profiles: {} };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function writeConfig(config) {
|
|
16
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
17
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 認証情報を以下の優先順位で解決する:
|
|
21
|
+
* 1. コマンドフラグ (--url / --api-key / --project-key)
|
|
22
|
+
* 2. 設定ファイルのプロファイル (--profile または default)
|
|
23
|
+
* 3. 環境変数 (BACKLOG_BASE_URL / BACKLOG_API_KEY / BACKLOG_PROJECT_KEY)
|
|
24
|
+
*/
|
|
25
|
+
export function resolveCredentials(opts) {
|
|
26
|
+
const config = readConfig();
|
|
27
|
+
const profileName = opts.profile ?? config.default;
|
|
28
|
+
const profileData = profileName ? config.profiles[profileName] : undefined;
|
|
29
|
+
const baseUrl = opts.url ?? profileData?.baseUrl ?? process.env.BACKLOG_BASE_URL;
|
|
30
|
+
const apiKey = opts.apiKey ?? profileData?.apiKey ?? process.env.BACKLOG_API_KEY;
|
|
31
|
+
const projectKey = opts.projectKey ?? profileData?.projectKey ?? process.env.BACKLOG_PROJECT_KEY;
|
|
32
|
+
if (!baseUrl)
|
|
33
|
+
throw new Error('Backlog URL が未設定です。--url フラグ、プロファイル、または BACKLOG_BASE_URL 環境変数を設定してください。');
|
|
34
|
+
if (!apiKey)
|
|
35
|
+
throw new Error('API キーが未設定です。--api-key フラグ、プロファイル、または BACKLOG_API_KEY 環境変数を設定してください。');
|
|
36
|
+
if (!projectKey)
|
|
37
|
+
throw new Error('プロジェクトキーが未設定です。--project-key フラグ、プロファイル、または BACKLOG_PROJECT_KEY 環境変数を設定してください。');
|
|
38
|
+
return { baseUrl: baseUrl.replace(/\/$/, ''), apiKey, projectKey };
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Cli } from 'incur';
|
|
3
|
+
import { issueCli } from './commands/issue.js';
|
|
4
|
+
import { profileCli } from './commands/profile.js';
|
|
5
|
+
import { projectCli } from './commands/project.js';
|
|
6
|
+
import { runSetup, setupOptions } from './commands/setup.js';
|
|
7
|
+
import { loadWhoami } from './commands/whoami.js';
|
|
8
|
+
import { authOptions } from './shared.js';
|
|
9
|
+
const cli = Cli.create('backlog', {
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
description: 'Backlog API CLI — setup/profile、課題・プロジェクト管理',
|
|
12
|
+
});
|
|
13
|
+
cli
|
|
14
|
+
.command('setup', {
|
|
15
|
+
description: '初回セットアップ向けにプロファイルを保存',
|
|
16
|
+
options: setupOptions,
|
|
17
|
+
run(c) {
|
|
18
|
+
return runSetup(c.options);
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
.command('whoami', {
|
|
22
|
+
description: '認証ユーザー情報を取得',
|
|
23
|
+
options: authOptions,
|
|
24
|
+
async run(c) {
|
|
25
|
+
return loadWhoami(c.options);
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
.command(issueCli)
|
|
29
|
+
.command(projectCli)
|
|
30
|
+
.command(profileCli)
|
|
31
|
+
.serve();
|
|
32
|
+
export default cli;
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'incur';
|
|
2
|
+
/** 全コマンド共通の認証オプション */
|
|
3
|
+
export declare const authOptions: z.ZodObject<{
|
|
4
|
+
profile: z.ZodOptional<z.ZodString>;
|
|
5
|
+
url: z.ZodOptional<z.ZodString>;
|
|
6
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
7
|
+
projectKey: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strip>;
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'incur';
|
|
2
|
+
/** 全コマンド共通の認証オプション */
|
|
3
|
+
export const authOptions = z.object({
|
|
4
|
+
profile: z.string().optional().describe('使用するプロファイル名'),
|
|
5
|
+
url: z.string().optional().describe('Backlog スペース URL(上書き)'),
|
|
6
|
+
apiKey: z.string().optional().describe('API キー(上書き)'),
|
|
7
|
+
projectKey: z.string().optional().describe('プロジェクトキー(上書き)'),
|
|
8
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@niiiiiiile/iw-backlog-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Nulab Backlog issue and project management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"backlog": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
".env.example"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"backlog",
|
|
22
|
+
"nulab",
|
|
23
|
+
"cli",
|
|
24
|
+
"issue",
|
|
25
|
+
"project-management"
|
|
26
|
+
],
|
|
27
|
+
"author": "Teruaki Iwane",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Niiiiile/backlog-cli.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/Niiiiile/backlog-cli#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/Niiiiile/backlog-cli/issues"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.0.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"incur": "^0.3.25"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"tsx": "^4.19.0",
|
|
49
|
+
"typescript": "^5.8.0"
|
|
50
|
+
}
|
|
51
|
+
}
|