harvask-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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/api-base.js +8 -0
  4. package/dist/api-base.js.map +1 -0
  5. package/dist/api-client.js +221 -0
  6. package/dist/api-client.js.map +1 -0
  7. package/dist/cli.js +154 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/_exec.js +63 -0
  10. package/dist/commands/_exec.js.map +1 -0
  11. package/dist/commands/api/index.js +88 -0
  12. package/dist/commands/api/index.js.map +1 -0
  13. package/dist/commands/attachment/index.js +54 -0
  14. package/dist/commands/attachment/index.js.map +1 -0
  15. package/dist/commands/auth/index.js +238 -0
  16. package/dist/commands/auth/index.js.map +1 -0
  17. package/dist/commands/calendar/index.js +167 -0
  18. package/dist/commands/calendar/index.js.map +1 -0
  19. package/dist/commands/chat/index.js +97 -0
  20. package/dist/commands/chat/index.js.map +1 -0
  21. package/dist/commands/file/index.js +42 -0
  22. package/dist/commands/file/index.js.map +1 -0
  23. package/dist/commands/global-wiki/index.js +196 -0
  24. package/dist/commands/global-wiki/index.js.map +1 -0
  25. package/dist/commands/issue/builds.js +160 -0
  26. package/dist/commands/issue/builds.js.map +1 -0
  27. package/dist/commands/issue/index.js +169 -0
  28. package/dist/commands/issue/index.js.map +1 -0
  29. package/dist/commands/issue/sub.js +149 -0
  30. package/dist/commands/issue/sub.js.map +1 -0
  31. package/dist/commands/issue/subbuilds.js +174 -0
  32. package/dist/commands/issue/subbuilds.js.map +1 -0
  33. package/dist/commands/milestone/index.js +104 -0
  34. package/dist/commands/milestone/index.js.map +1 -0
  35. package/dist/commands/note/builds.js +68 -0
  36. package/dist/commands/note/builds.js.map +1 -0
  37. package/dist/commands/note/index.js +122 -0
  38. package/dist/commands/note/index.js.map +1 -0
  39. package/dist/commands/preview-config/index.js +87 -0
  40. package/dist/commands/preview-config/index.js.map +1 -0
  41. package/dist/commands/project/builds.js +78 -0
  42. package/dist/commands/project/builds.js.map +1 -0
  43. package/dist/commands/project/index.js +103 -0
  44. package/dist/commands/project/index.js.map +1 -0
  45. package/dist/commands/scheduled-task/index.js +196 -0
  46. package/dist/commands/scheduled-task/index.js.map +1 -0
  47. package/dist/commands/site-credential/index.js +23 -0
  48. package/dist/commands/site-credential/index.js.map +1 -0
  49. package/dist/commands/skill/index.js +121 -0
  50. package/dist/commands/skill/index.js.map +1 -0
  51. package/dist/commands/storage/index.js +103 -0
  52. package/dist/commands/storage/index.js.map +1 -0
  53. package/dist/commands/user/index.js +55 -0
  54. package/dist/commands/user/index.js.map +1 -0
  55. package/dist/commands/wiki/builds.js +135 -0
  56. package/dist/commands/wiki/builds.js.map +1 -0
  57. package/dist/commands/wiki/index.js +177 -0
  58. package/dist/commands/wiki/index.js.map +1 -0
  59. package/dist/commands/workflow/index.js +111 -0
  60. package/dist/commands/workflow/index.js.map +1 -0
  61. package/dist/config.js +123 -0
  62. package/dist/config.js.map +1 -0
  63. package/dist/context.js +61 -0
  64. package/dist/context.js.map +1 -0
  65. package/dist/errors/hint-map.js +81 -0
  66. package/dist/errors/hint-map.js.map +1 -0
  67. package/dist/errors/index.js +132 -0
  68. package/dist/errors/index.js.map +1 -0
  69. package/dist/index.js +17 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/output/format.js +108 -0
  72. package/dist/output/format.js.map +1 -0
  73. package/dist/output/index.js +3 -0
  74. package/dist/output/index.js.map +1 -0
  75. package/dist/output/tty.js +22 -0
  76. package/dist/output/tty.js.map +1 -0
  77. package/dist/registry.js +47 -0
  78. package/dist/registry.js.map +1 -0
  79. package/dist/token-store.js +92 -0
  80. package/dist/token-store.js.map +1 -0
  81. package/dist/types/api.js +8 -0
  82. package/dist/types/api.js.map +1 -0
  83. package/dist/utils/bulk.js +29 -0
  84. package/dist/utils/bulk.js.map +1 -0
  85. package/dist/utils/download.js +36 -0
  86. package/dist/utils/download.js.map +1 -0
  87. package/dist/utils/file-utils.js +58 -0
  88. package/dist/utils/file-utils.js.map +1 -0
  89. package/dist/utils/input.js +66 -0
  90. package/dist/utils/input.js.map +1 -0
  91. package/dist/utils/issue-resolver.js +20 -0
  92. package/dist/utils/issue-resolver.js.map +1 -0
  93. package/dist/utils/loopback.js +86 -0
  94. package/dist/utils/loopback.js.map +1 -0
  95. package/dist/utils/pkce.js +20 -0
  96. package/dist/utils/pkce.js.map +1 -0
  97. package/dist/utils/project-resolver.js +63 -0
  98. package/dist/utils/project-resolver.js.map +1 -0
  99. package/dist/utils/prompt.js +17 -0
  100. package/dist/utils/prompt.js.map +1 -0
  101. package/dist/utils/upload.js +32 -0
  102. package/dist/utils/upload.js.map +1 -0
  103. package/package.json +44 -0
  104. package/skills/harvask/AGENTS.md +131 -0
  105. package/skills/harvask/GEMINI.md +131 -0
  106. package/skills/harvask/SKILL.md +134 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * api escape hatch
3
+ *
4
+ * 未ラップの /ai-worker/* (および /cli/*)エンドポイントを直接叩く。
5
+ * SSRF / 誤接続防止のため、path は /ai-worker または /cli 始まりに限定し、
6
+ * 絶対 URL は受け付けない。
7
+ */
8
+ import { execCall } from '../_exec.js';
9
+ import { printOutput } from '../../output/index.js';
10
+ import { ValidationError } from '../../errors/index.js';
11
+ import { parseJsonArg } from '../../utils/input.js';
12
+ import { API_NS } from '../../api-base.js';
13
+ const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
14
+ // 明示的に許可する名前空間(既定の /v1、後方互換の /ai-worker、CLI 認証の /cli)
15
+ const KNOWN_NAMESPACES = [API_NS, '/ai-worker', '/cli'];
16
+ /**
17
+ * path を正規化する純粋関数。接続を既定名前空間(/v1。後方互換で /ai-worker、CLI 認証の /cli)
18
+ * 配下に限定する。
19
+ * - 絶対 URL は拒否(SSRF 防止)
20
+ * - '..' を含むパスは拒否(baseURL の /api を超えて遡るのを防ぐ)
21
+ * - 先頭の /api は除去(api-client の baseURL が既に /api を含むため)
22
+ * - 既知の名前空間始まりはそのまま。それ以外は /v1 配下の相対パスとして扱う
23
+ * (例: "projects/5/issues" -> "/v1/projects/5/issues")
24
+ */
25
+ export function normalizeApiPath(input) {
26
+ let path = input.trim();
27
+ if (/^https?:\/\//i.test(path)) {
28
+ throw new ValidationError('absolute URLs are not allowed; use a path like /v1/projects');
29
+ }
30
+ // パーセントエンコード(%2e%2e 等)でのトラバーサル回避を防ぐため、デコード後も '..' を拒否。
31
+ let decoded = path;
32
+ try {
33
+ decoded = decodeURIComponent(path);
34
+ }
35
+ catch {
36
+ /* 不正なエンコードはそのまま下の '..' 判定/サーバ側に委ねる */
37
+ }
38
+ if (path.includes('..') || decoded.includes('..')) {
39
+ throw new ValidationError("path must not contain '..'");
40
+ }
41
+ if (path === '/api') {
42
+ path = '/';
43
+ }
44
+ else if (path.startsWith('/api/')) {
45
+ path = path.slice(4);
46
+ }
47
+ if (!path.startsWith('/')) {
48
+ path = '/' + path;
49
+ }
50
+ for (const ns of KNOWN_NAMESPACES) {
51
+ if (path === ns || path.startsWith(ns + '/')) {
52
+ return path;
53
+ }
54
+ }
55
+ // それ以外は既定名前空間(/v1)配下の相対パスとみなす
56
+ return API_NS + path;
57
+ }
58
+ export const apiModule = {
59
+ name: 'api',
60
+ describe: 'Call any /v1/* endpoint directly (escape hatch)',
61
+ register(program, ctx) {
62
+ program
63
+ .command('api')
64
+ .description('Call any /v1/* endpoint directly (escape hatch)')
65
+ .argument('<method>', 'HTTP method (GET|POST|PUT|PATCH|DELETE)')
66
+ .argument('<path>', 'path under /v1 (or /cli), e.g. projects/5/issues')
67
+ .argument('[json]', 'request body: inline JSON, @file.json, or - for stdin')
68
+ .addHelpText('after', `
69
+ Examples:
70
+ harvask api GET projects
71
+ harvask api GET projects/5/issues?status=open
72
+ harvask api POST notes '{"content":"hi","is_pinned":true}'
73
+ harvask api POST projects/5/issues @issue.json
74
+ `)
75
+ .action(async (method, path, json) => {
76
+ const upper = method.toUpperCase();
77
+ if (!METHODS.includes(upper)) {
78
+ throw new ValidationError(`method must be one of: ${METHODS.join(', ')}`);
79
+ }
80
+ const normalized = normalizeApiPath(path);
81
+ const body = json !== undefined ? parseJsonArg(json) : undefined;
82
+ const call = { method: upper, path: normalized, body };
83
+ const result = await execCall(ctx, call);
84
+ printOutput(result, ctx.out);
85
+ });
86
+ },
87
+ };
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/api/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * attachment カテゴリ(汎用添付, /v1/attachments)。upload は multipart、download は binary。
3
+ */
4
+ import { parseProjectArg } from '../_exec.js';
5
+ import { resolveProjectId } from '../../utils/project-resolver.js';
6
+ import { printOutput } from '../../output/index.js';
7
+ import { ValidationError } from '../../errors/index.js';
8
+ import { parseIntArg } from '../../utils/input.js';
9
+ import { buildFileFormData } from '../../utils/upload.js';
10
+ import { saveBlob } from '../../utils/download.js';
11
+ import { API_NS } from '../../api-base.js';
12
+ export const ATTACHABLE_TYPES = ['issue', 'comment', 'chat_message'];
13
+ export const attachmentModule = {
14
+ name: 'attachment',
15
+ describe: 'Upload/download attachments',
16
+ register(program, ctx) {
17
+ const at = program.command('attachment').description('Attachments (upload/download)');
18
+ at.command('upload')
19
+ .description('Upload an attachment (multipart)')
20
+ .requiredOption('--file <path>', 'local file path')
21
+ .requiredOption('--type <type>', `attachable type (${ATTACHABLE_TYPES.join('|')})`)
22
+ .option('--id <attachableId>', 'attachable id (required for issue/comment)', parseIntArg)
23
+ .option('-p, --project <key|id>', 'project (required for issue/comment)')
24
+ .action(async (opts) => {
25
+ if (!ATTACHABLE_TYPES.includes(opts.type)) {
26
+ throw new ValidationError(`--type must be one of: ${ATTACHABLE_TYPES.join(', ')}`);
27
+ }
28
+ const client = ctx.getApiClient();
29
+ const fields = { attachable_type: opts.type };
30
+ if (opts.type === 'issue' || opts.type === 'comment') {
31
+ if (opts.id === undefined)
32
+ throw new ValidationError(`--id is required for type=${opts.type}`);
33
+ if (opts.project === undefined)
34
+ throw new ValidationError(`-p/--project is required for type=${opts.type}`);
35
+ const pid = await resolveProjectId(client, parseProjectArg(opts.project) ?? {});
36
+ fields.attachable_id = opts.id;
37
+ fields.project_id = pid;
38
+ }
39
+ const form = buildFileFormData(opts.file, fields);
40
+ const result = await client.postMultipart(`${API_NS}/attachments/upload`, form);
41
+ printOutput(result, ctx.out);
42
+ });
43
+ at.command('download')
44
+ .description('Download an attachment to a file')
45
+ .argument('<id>', 'attachment id')
46
+ .option('--out <path>', 'output path (default: server filename)')
47
+ .action(async (id, opts) => {
48
+ const client = ctx.getApiClient();
49
+ const blob = await client.getBlob(`${API_NS}/attachments/${parseIntArg(id)}/download`);
50
+ printOutput(saveBlob(blob, opts.out), ctx.out);
51
+ });
52
+ },
53
+ };
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/attachment/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * auth カテゴリ登録(login / logout / whoami)
3
+ *
4
+ * login はブラウザ認可フロー(OAuth authorization-code + PKCE 相当):
5
+ * 1. PKCE verifier/challenge を生成
6
+ * 2. POST {api}/cli/auth/start で認可リクエスト開始
7
+ * 3. ループバック(既定): 127.0.0.1 受け口を立て、ブラウザ承認 → code を自動受領
8
+ * コード貼り付け: 表示URLをブラウザで承認 → 表示された code を手入力
9
+ * (--no-browser 指定時、またはブラウザを開けなかった場合は自動で貼り付けに切替)
10
+ * 4. POST {api}/cli/auth/exchange で code+verifier をトークンに交換
11
+ * 5. tokens.json に保存(token は stdout に出さない)
12
+ */
13
+ import { printOutput } from '../../output/index.js';
14
+ import { ValidationError } from '../../errors/index.js';
15
+ import { ApiClientError } from '../../api-client.js';
16
+ import { readStore, saveTenant, removeTenant, clearAll, getStorePath } from '../../token-store.js';
17
+ import { prompt } from '../../utils/prompt.js';
18
+ import { generateVerifier, challengeFor } from '../../utils/pkce.js';
19
+ import { startLoopback } from '../../utils/loopback.js';
20
+ /**
21
+ * 認証不要の JSON POST(start / exchange 用)。非2xx は ApiClientError。
22
+ */
23
+ async function postJson(url, body) {
24
+ let res;
25
+ try {
26
+ res = await fetch(url, {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
29
+ body: JSON.stringify(body),
30
+ });
31
+ }
32
+ catch (e) {
33
+ throw new ApiClientError(`Network error: ${e.message}. Check --api-url / connectivity.`, 0);
34
+ }
35
+ const text = await res.text();
36
+ let json;
37
+ try {
38
+ json = (text ? JSON.parse(text) : {});
39
+ }
40
+ catch {
41
+ // プロキシ/LB が非JSON(HTML エラーページ等)を返した場合に SyntaxError を素通しせず、
42
+ // 構造化エラー(Error / Why / Hint)に載せる。
43
+ throw new ApiClientError(res.ok
44
+ ? `Invalid (non-JSON) response from ${url}`
45
+ : `HTTP ${res.status}: ${res.statusText || 'non-JSON response'}`, res.status || 0);
46
+ }
47
+ if (!res.ok) {
48
+ throw new ApiClientError(json.message || `HTTP ${res.status}`, res.status, json);
49
+ }
50
+ return json;
51
+ }
52
+ async function openBrowser(url) {
53
+ try {
54
+ const { default: open } = await import('open');
55
+ await open(url);
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ /**
63
+ * コード貼り付けフロー: URL を表示し、ユーザーがブラウザ承認後に表示される code を入力。
64
+ */
65
+ async function pasteFlow(apiUrl, codeChallenge) {
66
+ const start = await postJson(`${apiUrl}/cli/auth/start`, {
67
+ code_challenge: codeChallenge,
68
+ });
69
+ process.stderr.write(`次のURLをブラウザで開いて承認してください:\n${start.verification_uri}\n\n`);
70
+ const code = (await prompt('承認後に表示されたコードを貼り付け: ')).trim();
71
+ if (!code) {
72
+ throw new ValidationError('code is required');
73
+ }
74
+ return { requestId: start.request_id, code };
75
+ }
76
+ /**
77
+ * ループバックフロー: 受け口を立ててブラウザ承認 → code を自動受領。
78
+ * 受け口を作れない / ブラウザを開けない場合は自動でコード貼り付けにフォールバック。
79
+ */
80
+ async function loopbackFlow(apiUrl, codeChallenge) {
81
+ let loopback;
82
+ try {
83
+ loopback = await startLoopback();
84
+ }
85
+ catch {
86
+ // ループバック受け口を作れない → 貼り付けにフォールバック
87
+ return pasteFlow(apiUrl, codeChallenge);
88
+ }
89
+ const start = await postJson(`${apiUrl}/cli/auth/start`, {
90
+ code_challenge: codeChallenge,
91
+ redirect_uri: loopback.redirectUri,
92
+ });
93
+ // このログイン試行の request_id を登録し、偽コールバックを弾けるようにする。
94
+ loopback.expectRequestId(start.request_id);
95
+ process.stderr.write(`ブラウザで承認してください:\n${start.verification_uri}\n`);
96
+ // HARVASK_NO_OPEN=1: 自動オープンせずループバックは維持(手動でURLを開く)
97
+ if (process.env.HARVASK_NO_OPEN) {
98
+ process.stderr.write('(上記URLをブラウザで開いて承認してください)\n');
99
+ }
100
+ else {
101
+ const opened = await openBrowser(start.verification_uri);
102
+ if (!opened) {
103
+ // ブラウザを開けない(ヘッドレス/リモート等)→ 貼り付けに自動フォールバック
104
+ loopback.close();
105
+ process.stderr.write('ブラウザを開けませんでした。コード貼り付け方式に切り替えます。\n\n');
106
+ return pasteFlow(apiUrl, codeChallenge);
107
+ }
108
+ }
109
+ try {
110
+ const cb = await loopback.wait();
111
+ return { requestId: start.request_id, code: cb.code };
112
+ }
113
+ finally {
114
+ loopback.close();
115
+ }
116
+ }
117
+ export const authModule = {
118
+ name: 'auth',
119
+ describe: 'Authenticate to a tenant (login/logout/whoami)',
120
+ register(program, ctx) {
121
+ // login
122
+ program
123
+ .command('login')
124
+ .description('Log in to a tenant via the browser (no password typed into the CLI)')
125
+ .option('--tenant <domain>', 'tenant domain (e.g. acme.harvask.app)')
126
+ .option('--api-url <url>', 'override API base URL (host must equal the tenant domain; dev: http://acme.harvask.test:8080/api)')
127
+ .option('--no-browser', 'do not open a browser; show the URL and paste the code instead')
128
+ .addHelpText('after', `
129
+ How it works:
130
+ Default: opens your browser to approve, then receives the token automatically (loopback).
131
+ If the browser cannot be opened (or with --no-browser): open the printed URL, approve,
132
+ and paste the shown code back into the terminal.
133
+
134
+ Examples:
135
+ harvask login --tenant acme.harvask.app
136
+ harvask login --tenant acme.harvask.test --api-url http://acme.harvask.test:8080/api
137
+ harvask login --tenant acme.harvask.app --no-browser # remote/headless
138
+
139
+ The tenant is resolved from the API URL host. The password is never entered into the CLI.
140
+ Tokens are saved per-tenant in ${getStorePath()} (mode 0600).
141
+ `)
142
+ .action(async (opts) => {
143
+ const { apiUrl, tenantDomain } = ctx.getTarget({
144
+ tenant: opts.tenant,
145
+ apiUrl: opts.apiUrl,
146
+ });
147
+ const verifier = generateVerifier();
148
+ const codeChallenge = challengeFor(verifier);
149
+ const auth = opts.browser === false
150
+ ? await pasteFlow(apiUrl, codeChallenge)
151
+ : await loopbackFlow(apiUrl, codeChallenge);
152
+ const result = await postJson(`${apiUrl}/cli/auth/exchange`, {
153
+ request_id: auth.requestId,
154
+ code: auth.code,
155
+ code_verifier: verifier,
156
+ });
157
+ const storeKey = tenantDomain || result.tenant_domain || new URL(apiUrl).hostname;
158
+ saveTenant({
159
+ token: result.token,
160
+ tenant_domain: storeKey,
161
+ api_url: apiUrl,
162
+ user_id: result.user_id,
163
+ expires_at: result.expires_at,
164
+ });
165
+ // token は出力しない
166
+ printOutput({
167
+ logged_in: true,
168
+ tenant: storeKey,
169
+ api_url: apiUrl,
170
+ user_id: result.user_id,
171
+ expires_at: result.expires_at,
172
+ stored_at: getStorePath(),
173
+ }, ctx.out);
174
+ });
175
+ // logout
176
+ program
177
+ .command('logout')
178
+ .description('Revoke and remove a stored CLI token')
179
+ .option('--all', 'log out of all tenants')
180
+ .action(async (opts) => {
181
+ const store = readStore();
182
+ if (opts.all) {
183
+ const tenants = Object.keys(store.tenants);
184
+ for (const t of tenants) {
185
+ await tryRevoke(ctx, t);
186
+ }
187
+ clearAll();
188
+ printOutput({ logged_out: true, tenants }, ctx.out);
189
+ return;
190
+ }
191
+ const tenant = ctx.flags.profile ?? store.current;
192
+ if (!tenant || !store.tenants[tenant]) {
193
+ throw new ValidationError('no active session to log out (use --profile <domain> or --all)');
194
+ }
195
+ await tryRevoke(ctx, tenant);
196
+ removeTenant(tenant);
197
+ printOutput({ logged_out: true, tenant }, ctx.out);
198
+ });
199
+ // whoami
200
+ program
201
+ .command('whoami')
202
+ .description('Show the current authenticated user / tenant')
203
+ .option('--local', 'show stored token info without contacting the server')
204
+ .action(async (opts) => {
205
+ if (opts.local) {
206
+ const store = readStore();
207
+ const tenant = ctx.flags.profile ?? store.current;
208
+ const entry = tenant ? store.tenants[tenant] : undefined;
209
+ if (!entry) {
210
+ throw new ValidationError('no stored session (run `harvask login`)');
211
+ }
212
+ printOutput({
213
+ tenant: entry.tenant_domain,
214
+ api_url: entry.api_url,
215
+ user_id: entry.user_id,
216
+ expires_at: entry.expires_at,
217
+ }, ctx.out);
218
+ return;
219
+ }
220
+ const client = ctx.getApiClient();
221
+ const result = await client.get('/cli/whoami');
222
+ printOutput(result, ctx.out);
223
+ });
224
+ },
225
+ };
226
+ /**
227
+ * 指定テナントのトークンをサーバ側で失効させる(到達不可でも握りつぶす)。
228
+ */
229
+ async function tryRevoke(ctx, tenant) {
230
+ try {
231
+ const client = ctx.freshApiClient({ profile: tenant });
232
+ await client.delete('/cli/login');
233
+ }
234
+ catch {
235
+ // サーバ到達不可・失効済み等はローカル削除を優先するため無視
236
+ }
237
+ }
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * calendar カテゴリ(個人カレンダー予定 /v1/calendar-events)。
3
+ * 純粋関数 build*Call を export(テスト用)し、commander へ登録する。
4
+ */
5
+ import { execCall } from '../_exec.js';
6
+ import { printOutput } from '../../output/index.js';
7
+ import { ValidationError } from '../../errors/index.js';
8
+ import { parseIntArg, parseNumberList } from '../../utils/input.js';
9
+ import { API_NS } from '../../api-base.js';
10
+ const BASE = `${API_NS}/calendar-events`;
11
+ export const CAL_ACCENTS = ['sky', 'violet', 'emerald', 'rose', 'amber', 'slate'];
12
+ export const CAL_VISIBILITIES = ['public', 'busy', 'private'];
13
+ function assertEnum(value, allowed, field) {
14
+ if (!allowed.includes(value)) {
15
+ throw new ValidationError(`${field} must be one of: ${allowed.join(', ')}`);
16
+ }
17
+ }
18
+ export function buildCalendarListCall(a) {
19
+ if (!a.from || !a.to) {
20
+ throw new ValidationError('--from and --to are required (date/datetime)');
21
+ }
22
+ return { method: 'GET', path: BASE, query: { from: a.from, to: a.to } };
23
+ }
24
+ export function buildCalendarShowCall(id) {
25
+ return { method: 'GET', path: `${BASE}/${id}` };
26
+ }
27
+ function calendarBody(a) {
28
+ if (a.title !== undefined && a.title.length > 255) {
29
+ throw new ValidationError('title must be <= 255 characters');
30
+ }
31
+ if (a.location !== undefined && a.location.length > 255) {
32
+ throw new ValidationError('location must be <= 255 characters');
33
+ }
34
+ if (a.description !== undefined && a.description.length > 5000) {
35
+ throw new ValidationError('description must be <= 5000 characters');
36
+ }
37
+ if (a.color && !/^#[0-9a-fA-F]{6}$/.test(a.color)) {
38
+ throw new ValidationError('color must be a hex like #rrggbb');
39
+ }
40
+ if (a.accent)
41
+ assertEnum(a.accent, CAL_ACCENTS, 'accent');
42
+ if (a.visibility)
43
+ assertEnum(a.visibility, CAL_VISIBILITIES, 'visibility');
44
+ if (a.participantIds && a.participantIds.length > 50) {
45
+ throw new ValidationError('participant ids must be <= 50');
46
+ }
47
+ const b = {};
48
+ if (a.title !== undefined)
49
+ b.title = a.title;
50
+ if (a.startAt !== undefined)
51
+ b.start_at = a.startAt;
52
+ if (a.endAt !== undefined)
53
+ b.end_at = a.endAt;
54
+ if (a.allDay !== undefined)
55
+ b.all_day = a.allDay;
56
+ if (a.location !== undefined)
57
+ b.location = a.location;
58
+ if (a.description !== undefined)
59
+ b.description = a.description;
60
+ if (a.color !== undefined)
61
+ b.color = a.color;
62
+ if (a.accent !== undefined)
63
+ b.accent = a.accent;
64
+ if (a.visibility !== undefined)
65
+ b.visibility = a.visibility;
66
+ if (a.participantIds && a.participantIds.length)
67
+ b.participant_user_ids = a.participantIds;
68
+ return b;
69
+ }
70
+ export function buildCalendarCreateCall(a) {
71
+ if (!a.title || a.title.trim() === '')
72
+ throw new ValidationError('title is required (--title)');
73
+ if (!a.startAt)
74
+ throw new ValidationError('start-at is required (--start-at)');
75
+ if (!a.endAt)
76
+ throw new ValidationError('end-at is required (--end-at)');
77
+ return { method: 'POST', path: BASE, body: calendarBody(a) };
78
+ }
79
+ export function buildCalendarUpdateCall(id, a) {
80
+ const body = calendarBody(a);
81
+ if (Object.keys(body).length === 0) {
82
+ throw new ValidationError('nothing to update');
83
+ }
84
+ return { method: 'PUT', path: `${BASE}/${id}`, body };
85
+ }
86
+ export function buildCalendarDeleteCall(id) {
87
+ return { method: 'DELETE', path: `${BASE}/${id}` };
88
+ }
89
+ export const calendarModule = {
90
+ name: 'calendar',
91
+ describe: 'Manage your personal calendar events',
92
+ register(program, ctx) {
93
+ const cal = program.command('calendar').description('Personal calendar events (private to you)');
94
+ cal
95
+ .command('list')
96
+ .description('List events in a time range')
97
+ .requiredOption('--from <datetime>', 'range start (ISO8601 / YYYY-MM-DD)')
98
+ .requiredOption('--to <datetime>', 'range end (must be after --from)')
99
+ .action(async (opts) => {
100
+ printOutput(await execCall(ctx, buildCalendarListCall({ from: opts.from, to: opts.to })), ctx.out);
101
+ });
102
+ cal
103
+ .command('show')
104
+ .description('Show an event')
105
+ .argument('<id>', 'event id')
106
+ .action(async (id) => {
107
+ printOutput(await execCall(ctx, buildCalendarShowCall(parseIntArg(id))), ctx.out);
108
+ });
109
+ const createOpts = (cmd) => cmd
110
+ .option('--all-day', 'all-day event')
111
+ .option('--location <text>', 'location (<=255)')
112
+ .option('--description <text>', 'description (<=5000)')
113
+ .option('--event-color <hex>', 'event color #rrggbb')
114
+ .option('--accent <accent>', `accent (${CAL_ACCENTS.join('|')})`)
115
+ .option('--visibility <v>', `visibility (${CAL_VISIBILITIES.join('|')})`)
116
+ .option('--participant-ids <ids>', 'comma-separated user ids (<=50)', parseNumberList);
117
+ createOpts(cal.command('create'))
118
+ .description('Create an event')
119
+ .requiredOption('--title <title>', 'title (<=255)')
120
+ .requiredOption('--start-at <datetime>', 'start (ISO8601)')
121
+ .requiredOption('--end-at <datetime>', 'end (ISO8601, after start)')
122
+ .action(async (opts) => {
123
+ const call = buildCalendarCreateCall({
124
+ title: opts.title,
125
+ startAt: opts.startAt,
126
+ endAt: opts.endAt,
127
+ allDay: opts.allDay,
128
+ location: opts.location,
129
+ description: opts.description,
130
+ color: opts.eventColor,
131
+ accent: opts.accent,
132
+ visibility: opts.visibility,
133
+ participantIds: opts.participantIds,
134
+ });
135
+ printOutput(await execCall(ctx, call), ctx.out);
136
+ });
137
+ createOpts(cal.command('update'))
138
+ .description('Update an event')
139
+ .argument('<id>', 'event id')
140
+ .option('--title <title>', 'title (<=255)')
141
+ .option('--start-at <datetime>', 'start (ISO8601)')
142
+ .option('--end-at <datetime>', 'end (ISO8601, after start)')
143
+ .action(async (id, opts) => {
144
+ const call = buildCalendarUpdateCall(parseIntArg(id), {
145
+ title: opts.title,
146
+ startAt: opts.startAt,
147
+ endAt: opts.endAt,
148
+ allDay: opts.allDay,
149
+ location: opts.location,
150
+ description: opts.description,
151
+ color: opts.eventColor,
152
+ accent: opts.accent,
153
+ visibility: opts.visibility,
154
+ participantIds: opts.participantIds,
155
+ });
156
+ printOutput(await execCall(ctx, call), ctx.out);
157
+ });
158
+ cal
159
+ .command('delete')
160
+ .description('Delete an event')
161
+ .argument('<id>', 'event id')
162
+ .action(async (id) => {
163
+ printOutput(await execCall(ctx, buildCalendarDeleteCall(parseIntArg(id))), ctx.out);
164
+ });
165
+ },
166
+ };
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/calendar/index.ts"],"names":[],"mappings":"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"}