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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/api-base.js +8 -0
- package/dist/api-base.js.map +1 -0
- package/dist/api-client.js +221 -0
- package/dist/api-client.js.map +1 -0
- package/dist/cli.js +154 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/_exec.js +63 -0
- package/dist/commands/_exec.js.map +1 -0
- package/dist/commands/api/index.js +88 -0
- package/dist/commands/api/index.js.map +1 -0
- package/dist/commands/attachment/index.js +54 -0
- package/dist/commands/attachment/index.js.map +1 -0
- package/dist/commands/auth/index.js +238 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/calendar/index.js +167 -0
- package/dist/commands/calendar/index.js.map +1 -0
- package/dist/commands/chat/index.js +97 -0
- package/dist/commands/chat/index.js.map +1 -0
- package/dist/commands/file/index.js +42 -0
- package/dist/commands/file/index.js.map +1 -0
- package/dist/commands/global-wiki/index.js +196 -0
- package/dist/commands/global-wiki/index.js.map +1 -0
- package/dist/commands/issue/builds.js +160 -0
- package/dist/commands/issue/builds.js.map +1 -0
- package/dist/commands/issue/index.js +169 -0
- package/dist/commands/issue/index.js.map +1 -0
- package/dist/commands/issue/sub.js +149 -0
- package/dist/commands/issue/sub.js.map +1 -0
- package/dist/commands/issue/subbuilds.js +174 -0
- package/dist/commands/issue/subbuilds.js.map +1 -0
- package/dist/commands/milestone/index.js +104 -0
- package/dist/commands/milestone/index.js.map +1 -0
- package/dist/commands/note/builds.js +68 -0
- package/dist/commands/note/builds.js.map +1 -0
- package/dist/commands/note/index.js +122 -0
- package/dist/commands/note/index.js.map +1 -0
- package/dist/commands/preview-config/index.js +87 -0
- package/dist/commands/preview-config/index.js.map +1 -0
- package/dist/commands/project/builds.js +78 -0
- package/dist/commands/project/builds.js.map +1 -0
- package/dist/commands/project/index.js +103 -0
- package/dist/commands/project/index.js.map +1 -0
- package/dist/commands/scheduled-task/index.js +196 -0
- package/dist/commands/scheduled-task/index.js.map +1 -0
- package/dist/commands/site-credential/index.js +23 -0
- package/dist/commands/site-credential/index.js.map +1 -0
- package/dist/commands/skill/index.js +121 -0
- package/dist/commands/skill/index.js.map +1 -0
- package/dist/commands/storage/index.js +103 -0
- package/dist/commands/storage/index.js.map +1 -0
- package/dist/commands/user/index.js +55 -0
- package/dist/commands/user/index.js.map +1 -0
- package/dist/commands/wiki/builds.js +135 -0
- package/dist/commands/wiki/builds.js.map +1 -0
- package/dist/commands/wiki/index.js +177 -0
- package/dist/commands/wiki/index.js.map +1 -0
- package/dist/commands/workflow/index.js +111 -0
- package/dist/commands/workflow/index.js.map +1 -0
- package/dist/config.js +123 -0
- package/dist/config.js.map +1 -0
- package/dist/context.js +61 -0
- package/dist/context.js.map +1 -0
- package/dist/errors/hint-map.js +81 -0
- package/dist/errors/hint-map.js.map +1 -0
- package/dist/errors/index.js +132 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/output/format.js +108 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/index.js +3 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/tty.js +22 -0
- package/dist/output/tty.js.map +1 -0
- package/dist/registry.js +47 -0
- package/dist/registry.js.map +1 -0
- package/dist/token-store.js +92 -0
- package/dist/token-store.js.map +1 -0
- package/dist/types/api.js +8 -0
- package/dist/types/api.js.map +1 -0
- package/dist/utils/bulk.js +29 -0
- package/dist/utils/bulk.js.map +1 -0
- package/dist/utils/download.js +36 -0
- package/dist/utils/download.js.map +1 -0
- package/dist/utils/file-utils.js +58 -0
- package/dist/utils/file-utils.js.map +1 -0
- package/dist/utils/input.js +66 -0
- package/dist/utils/input.js.map +1 -0
- package/dist/utils/issue-resolver.js +20 -0
- package/dist/utils/issue-resolver.js.map +1 -0
- package/dist/utils/loopback.js +86 -0
- package/dist/utils/loopback.js.map +1 -0
- package/dist/utils/pkce.js +20 -0
- package/dist/utils/pkce.js.map +1 -0
- package/dist/utils/project-resolver.js +63 -0
- package/dist/utils/project-resolver.js.map +1 -0
- package/dist/utils/prompt.js +17 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/upload.js +32 -0
- package/dist/utils/upload.js.map +1 -0
- package/package.json +44 -0
- package/skills/harvask/AGENTS.md +131 -0
- package/skills/harvask/GEMINI.md +131 -0
- package/skills/harvask/SKILL.md +134 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ファイル関連ユーティリティ(kairon utils/file-utils.ts から流用)。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 拡張子から MIME タイプを判定する。
|
|
6
|
+
*/
|
|
7
|
+
export function getMimeType(filename) {
|
|
8
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
9
|
+
const mimeTypes = {
|
|
10
|
+
png: 'image/png',
|
|
11
|
+
jpg: 'image/jpeg',
|
|
12
|
+
jpeg: 'image/jpeg',
|
|
13
|
+
gif: 'image/gif',
|
|
14
|
+
webp: 'image/webp',
|
|
15
|
+
svg: 'image/svg+xml',
|
|
16
|
+
ico: 'image/x-icon',
|
|
17
|
+
bmp: 'image/bmp',
|
|
18
|
+
pdf: 'application/pdf',
|
|
19
|
+
doc: 'application/msword',
|
|
20
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
21
|
+
xls: 'application/vnd.ms-excel',
|
|
22
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
23
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
24
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
25
|
+
txt: 'text/plain',
|
|
26
|
+
csv: 'text/csv',
|
|
27
|
+
md: 'text/markdown',
|
|
28
|
+
html: 'text/html',
|
|
29
|
+
css: 'text/css',
|
|
30
|
+
js: 'application/javascript',
|
|
31
|
+
ts: 'application/typescript',
|
|
32
|
+
json: 'application/json',
|
|
33
|
+
xml: 'application/xml',
|
|
34
|
+
yaml: 'application/x-yaml',
|
|
35
|
+
yml: 'application/x-yaml',
|
|
36
|
+
zip: 'application/zip',
|
|
37
|
+
gz: 'application/gzip',
|
|
38
|
+
tar: 'application/x-tar',
|
|
39
|
+
mp3: 'audio/mpeg',
|
|
40
|
+
mp4: 'video/mp4',
|
|
41
|
+
wav: 'audio/wav',
|
|
42
|
+
};
|
|
43
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* バイト数を人間可読形式に変換する。
|
|
47
|
+
*/
|
|
48
|
+
export function formatFileSize(bytes) {
|
|
49
|
+
if (bytes === 0) {
|
|
50
|
+
return '0 B';
|
|
51
|
+
}
|
|
52
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
53
|
+
const k = 1024;
|
|
54
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
55
|
+
const size = bytes / Math.pow(k, i);
|
|
56
|
+
return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=file-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,SAAS,GAA2B;QACxC,GAAG,EAAE,WAAW;QAChB,GAAG,EAAE,YAAY;QACjB,IAAI,EAAE,YAAY;QAClB,GAAG,EAAE,WAAW;QAChB,IAAI,EAAE,YAAY;QAClB,GAAG,EAAE,eAAe;QACpB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,WAAW;QAChB,GAAG,EAAE,iBAAiB;QACtB,GAAG,EAAE,oBAAoB;QACzB,IAAI,EAAE,yEAAyE;QAC/E,GAAG,EAAE,0BAA0B;QAC/B,IAAI,EAAE,mEAAmE;QACzE,GAAG,EAAE,+BAA+B;QACpC,IAAI,EAAE,2EAA2E;QACjF,GAAG,EAAE,YAAY;QACjB,GAAG,EAAE,UAAU;QACf,EAAE,EAAE,eAAe;QACnB,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,UAAU;QACf,EAAE,EAAE,wBAAwB;QAC5B,EAAE,EAAE,wBAAwB;QAC5B,IAAI,EAAE,kBAAkB;QACxB,GAAG,EAAE,iBAAiB;QACtB,IAAI,EAAE,oBAAoB;QAC1B,GAAG,EAAE,oBAAoB;QACzB,GAAG,EAAE,iBAAiB;QACtB,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,mBAAmB;QACxB,GAAG,EAAE,YAAY;QACjB,GAAG,EAAE,WAAW;QAChB,GAAG,EAAE,WAAW;KACjB,CAAC;IACF,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,IAAI,CAAC;IACf,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 入力ユーティリティ
|
|
3
|
+
*
|
|
4
|
+
* 長文 Markdown / JSON を、リテラル・@file・-(stdin)から読み取る。
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { ValidationError } from '../errors/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* 値を解決する: '-' は stdin、先頭 '@' はファイル、それ以外はリテラル。
|
|
10
|
+
*/
|
|
11
|
+
export function readContentArg(value) {
|
|
12
|
+
if (value === '-') {
|
|
13
|
+
return readFileSync(0, 'utf8');
|
|
14
|
+
}
|
|
15
|
+
if (value.startsWith('@')) {
|
|
16
|
+
const path = value.slice(1);
|
|
17
|
+
try {
|
|
18
|
+
return readFileSync(path, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
throw new ValidationError(`File not found or unreadable: ${path} (${e.message})`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* JSON 引数を解決する(リテラル / @file / -)。
|
|
28
|
+
*/
|
|
29
|
+
export function parseJsonArg(value) {
|
|
30
|
+
const raw = readContentArg(value);
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new ValidationError(`Invalid JSON: ${e.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* "1,2,3" のようなカンマ区切りを number[] に変換する。
|
|
40
|
+
*/
|
|
41
|
+
export function parseNumberList(value) {
|
|
42
|
+
return value
|
|
43
|
+
.split(',')
|
|
44
|
+
.map((s) => s.trim())
|
|
45
|
+
.filter((s) => s !== '')
|
|
46
|
+
.map((s) => {
|
|
47
|
+
if (!INTEGER_RE.test(s)) {
|
|
48
|
+
throw new ValidationError(`Invalid number in list: "${s}"`);
|
|
49
|
+
}
|
|
50
|
+
return Number(s);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** 10進整数のみを許可する('', '0x10', '1e3', '3.0', ' 5 ' などは拒否)。 */
|
|
54
|
+
const INTEGER_RE = /^-?\d+$/;
|
|
55
|
+
/**
|
|
56
|
+
* 文字列を整数に変換する(commander の引数パーサ向け)。
|
|
57
|
+
* Number() の緩い変換(16進・指数・空文字を id/page として受理してしまう)を避け、
|
|
58
|
+
* 10進整数表記のみを受け付ける。
|
|
59
|
+
*/
|
|
60
|
+
export function parseIntArg(value) {
|
|
61
|
+
if (!INTEGER_RE.test(value)) {
|
|
62
|
+
throw new ValidationError(`Expected an integer, got "${value}"`);
|
|
63
|
+
}
|
|
64
|
+
return Number(value);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/utils/input.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QAClB,OAAO,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,eAAe,CAAC,iCAAiC,IAAI,KAAM,CAAW,CAAC,OAAO,GAAG,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,eAAe,CAAC,iBAAkB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;SACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,eAAe,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,eAAe,CAAC,6BAA6B,KAAK,GAAG,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* issue 識別子の解決(数値idはそのまま、キーは search で id に解決)。
|
|
3
|
+
* issue 本体・子リソース(comment/todo/memory/dependency)で共有。
|
|
4
|
+
*/
|
|
5
|
+
import { API_NS } from '../api-base.js';
|
|
6
|
+
import { resolveProjectId } from './project-resolver.js';
|
|
7
|
+
import { ValidationError } from '../errors/index.js';
|
|
8
|
+
export async function resolveIssueId(client, projectArg, value) {
|
|
9
|
+
if (/^\d+$/.test(value)) {
|
|
10
|
+
return parseInt(value, 10);
|
|
11
|
+
}
|
|
12
|
+
const pid = await resolveProjectId(client, projectArg ?? {});
|
|
13
|
+
const res = await client.get(`${API_NS}/projects/${pid}/issues/search?query=${encodeURIComponent(value)}&limit=50`);
|
|
14
|
+
const match = (res.results || []).find((r) => String(r.key).toUpperCase() === value.toUpperCase());
|
|
15
|
+
if (!match) {
|
|
16
|
+
throw new ValidationError(`Issue "${value}" not found. Pass a numeric issue id, or a valid key (see \`harvask issue search\`).`);
|
|
17
|
+
}
|
|
18
|
+
return match.id;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=issue-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-resolver.js","sourceRoot":"","sources":["../../src/utils/issue-resolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAmB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAwB,EACxB,UAAkC,EAClC,KAAa;IAEb,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,CAC1B,GAAG,MAAM,aAAa,GAAG,wBAAwB,kBAAkB,CAAC,KAAK,CAAC,WAAW,CACtF,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CACpC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAC3D,CAAC;IACF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,eAAe,CACvB,UAAU,KAAK,sFAAsF,CACtG,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ループバック受け口
|
|
3
|
+
*
|
|
4
|
+
* `127.0.0.1:<random>` に一時 HTTP サーバを立て、ブラウザ認可後のリダイレクト
|
|
5
|
+
* `/callback?request_id=...&code=...` を受け取る。
|
|
6
|
+
*/
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
/**
|
|
9
|
+
* コールバックパス(/callback?request_id&code)を解析する純粋関数。
|
|
10
|
+
*/
|
|
11
|
+
export function parseCallback(reqUrl, base = 'http://127.0.0.1') {
|
|
12
|
+
let u;
|
|
13
|
+
try {
|
|
14
|
+
u = new URL(reqUrl, base);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (u.pathname !== '/callback') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const code = u.searchParams.get('code') ?? '';
|
|
23
|
+
if (!code) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return { requestId: u.searchParams.get('request_id') ?? '', code };
|
|
27
|
+
}
|
|
28
|
+
const DONE_HTML = `<!doctype html><html lang="ja"><head><meta charset="utf-8">
|
|
29
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"><title>harvask</title></head>
|
|
30
|
+
<body style="margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f1f5f9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;color:#0f172a">
|
|
31
|
+
<div style="text-align:center;background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:40px 48px;box-shadow:0 10px 30px rgba(15,23,42,.06);max-width:360px">
|
|
32
|
+
<div style="font-size:40px;line-height:1">✅</div>
|
|
33
|
+
<h1 style="font-size:1.125rem;margin:12px 0 6px;font-weight:600">ログインが完了しました</h1>
|
|
34
|
+
<p style="color:#64748b;margin:0;font-size:.875rem">ターミナルに戻ってください。このタブは閉じて構いません。</p>
|
|
35
|
+
</div></body></html>`;
|
|
36
|
+
/**
|
|
37
|
+
* ループバックサーバを起動し、redirectUri と callback 待受を返す。
|
|
38
|
+
*/
|
|
39
|
+
export async function startLoopback() {
|
|
40
|
+
const server = http.createServer();
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
server.once('error', reject);
|
|
43
|
+
server.listen(0, '127.0.0.1', () => resolve());
|
|
44
|
+
});
|
|
45
|
+
const port = server.address().port;
|
|
46
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
47
|
+
let resolveResult;
|
|
48
|
+
const result = new Promise((res) => {
|
|
49
|
+
resolveResult = res;
|
|
50
|
+
});
|
|
51
|
+
// 期待する request_id(/cli/auth/start の戻り値)。CSRF 的な偽コールバック対策。
|
|
52
|
+
let expectedRequestId;
|
|
53
|
+
function expectRequestId(id) {
|
|
54
|
+
expectedRequestId = id;
|
|
55
|
+
}
|
|
56
|
+
server.on('request', (req, res) => {
|
|
57
|
+
const parsed = req.url ? parseCallback(req.url, redirectUri) : null;
|
|
58
|
+
// このログイン試行の request_id と完全一致しないコールバックは無視する(空・欠落も拒否)。
|
|
59
|
+
// 正規サーバのリダイレクトは常に request_id を含むため、ローカルの悪意あるページ等が
|
|
60
|
+
// code を投げ込んでも正しい request_id を推測できず弾かれる(ログインCSRF対策)。
|
|
61
|
+
const mismatched = parsed !== null &&
|
|
62
|
+
expectedRequestId !== undefined &&
|
|
63
|
+
parsed.requestId !== expectedRequestId;
|
|
64
|
+
if (!parsed || mismatched) {
|
|
65
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
66
|
+
res.end('not found');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
70
|
+
res.end(DONE_HTML);
|
|
71
|
+
resolveResult(parsed);
|
|
72
|
+
});
|
|
73
|
+
function wait(timeoutMs = 5 * 60 * 1000) {
|
|
74
|
+
return Promise.race([
|
|
75
|
+
result,
|
|
76
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('Timed out waiting for browser authorization')), timeoutMs)),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
function close() {
|
|
80
|
+
// keep-alive 接続が残るとプロセスが終了しないため、明示的に全接続を閉じる
|
|
81
|
+
server.closeAllConnections?.();
|
|
82
|
+
server.close();
|
|
83
|
+
}
|
|
84
|
+
return { redirectUri, expectRequestId, wait, close };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=loopback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.js","sourceRoot":"","sources":["../../src/utils/loopback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (RFC 7636) — S256
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
5
|
+
export function base64url(buf) {
|
|
6
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* code_verifier を生成する(base64url, 64文字程度)。
|
|
10
|
+
*/
|
|
11
|
+
export function generateVerifier() {
|
|
12
|
+
return base64url(randomBytes(48));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* code_challenge = base64url(sha256(verifier))(S256)。純粋関数。
|
|
16
|
+
*/
|
|
17
|
+
export function challengeFor(verifier) {
|
|
18
|
+
return base64url(createHash('sha256').update(verifier).digest());
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/utils/pkce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,OAAO,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Resolver
|
|
3
|
+
*
|
|
4
|
+
* project_key から project_id を解決するユーティリティ(セッション内キャッシュ付き)。
|
|
5
|
+
* kairon MCP (mcp-servers/kairon/src/utils/project-resolver.ts) から流用。
|
|
6
|
+
*/
|
|
7
|
+
import { ApiClientError } from '../api-client.js';
|
|
8
|
+
import { API_NS } from '../api-base.js';
|
|
9
|
+
const projectKeyCache = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* project_id または project_key からプロジェクトIDを解決する。
|
|
12
|
+
*
|
|
13
|
+
* @throws ProjectIdentifierRequiredError どちらも未指定の場合
|
|
14
|
+
* @throws ProjectKeyNotFoundError キーが見つからない場合
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveProjectId(apiClient, arg) {
|
|
17
|
+
const projectId = arg.id;
|
|
18
|
+
const projectKey = arg.key;
|
|
19
|
+
// 1. project_id 優先
|
|
20
|
+
if (projectId !== undefined && projectId !== null) {
|
|
21
|
+
return projectId;
|
|
22
|
+
}
|
|
23
|
+
// 2. project_key を API で解決(キャッシュあり)
|
|
24
|
+
if (projectKey !== undefined && projectKey !== null && projectKey.trim() !== '') {
|
|
25
|
+
const cacheKey = projectKey.toUpperCase();
|
|
26
|
+
const cached = projectKeyCache.get(cacheKey);
|
|
27
|
+
if (cached !== undefined) {
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const response = await apiClient.get(`${API_NS}/projects/resolve?key=${encodeURIComponent(projectKey)}`);
|
|
32
|
+
projectKeyCache.set(cacheKey, response.project_id);
|
|
33
|
+
return response.project_id;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof ApiClientError && error.statusCode === 404) {
|
|
37
|
+
throw new ProjectKeyNotFoundError(`プロジェクトキー "${projectKey}" が見つかりませんでした。`);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 3. どちらも未指定
|
|
43
|
+
throw new ProjectIdentifierRequiredError('プロジェクトを指定してください(-p, --project に key または id)。');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* キャッシュをクリア(テスト用)
|
|
47
|
+
*/
|
|
48
|
+
export function clearProjectKeyCache() {
|
|
49
|
+
projectKeyCache.clear();
|
|
50
|
+
}
|
|
51
|
+
export class ProjectKeyNotFoundError extends Error {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = 'ProjectKeyNotFoundError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class ProjectIdentifierRequiredError extends Error {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = 'ProjectIdentifierRequiredError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=project-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-resolver.js","sourceRoot":"","sources":["../../src/utils/project-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,MAAM,eAAe,GAAwB,IAAI,GAAG,EAAE,CAAC;AAOvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAA2B,EAC3B,GAAe;IAEf,MAAM,SAAS,GAAG,GAAG,CAAC,EAAE,CAAC;IACzB,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC;IAE3B,mBAAmB;IACnB,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QAClD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,oCAAoC;IACpC,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChF,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAClC,GAAG,MAAM,yBAAyB,kBAAkB,CAAC,UAAU,CAAC,EAAE,CACnE,CAAC;YACF,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YACnD,OAAO,QAAQ,CAAC,UAAU,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAc,IAAI,KAAK,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBAChE,MAAM,IAAI,uBAAuB,CAAC,aAAa,UAAU,gBAAgB,CAAC,CAAC;YAC7E,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,aAAa;IACb,MAAM,IAAI,8BAA8B,CACtC,8CAA8C,CAC/C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,eAAe,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACvD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 対話プロンプト(TTY)
|
|
3
|
+
*/
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
/**
|
|
6
|
+
* 通常の入力プロンプト。
|
|
7
|
+
*/
|
|
8
|
+
export function prompt(question) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
11
|
+
rl.question(question, (answer) => {
|
|
12
|
+
rl.close();
|
|
13
|
+
resolve(answer);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/utils/prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,QAAgB;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* マルチパートアップロード用 FormData 構築。
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
import { ValidationError } from '../errors/index.js';
|
|
7
|
+
import { getMimeType } from './file-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* ローカルファイルと追加フィールドから FormData を作る。
|
|
10
|
+
* フィールド名 `file` でファイルを添付する(API 仕様)。
|
|
11
|
+
*/
|
|
12
|
+
export function buildFileFormData(filePath, fields = {}) {
|
|
13
|
+
let buffer;
|
|
14
|
+
try {
|
|
15
|
+
buffer = readFileSync(filePath);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
throw new ValidationError(`File not found or unreadable: ${filePath} (${e.message})`);
|
|
19
|
+
}
|
|
20
|
+
const name = basename(filePath);
|
|
21
|
+
const form = new FormData();
|
|
22
|
+
// undici の FormData は Blob + filename を受け付ける(Buffer を Uint8Array ビューに包む)。
|
|
23
|
+
const blob = new Blob([new Uint8Array(buffer)], { type: getMimeType(name) });
|
|
24
|
+
form.append('file', blob, name);
|
|
25
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
26
|
+
if (value !== undefined && value !== null) {
|
|
27
|
+
form.append(key, String(value));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return form;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/utils/upload.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,SAAsD,EAAE;IAExD,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,eAAe,CAAC,iCAAiC,QAAQ,KAAM,CAAW,CAAC,OAAO,GAAG,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAChC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "harvask-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line client for a harvask tenant, built for coding agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"harvask": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"skills",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"gen:agents": "node scripts/gen-agent-docs.mjs",
|
|
20
|
+
"build": "npm run clean && npm run gen:agents && tsc -p tsconfig.build.json && chmod +x dist/index.js",
|
|
21
|
+
"build:test": "npm run clean && tsc && chmod +x dist/index.js",
|
|
22
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
23
|
+
"test": "npm run build:test && node --test \"dist/__tests__/**/*.test.js\"",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"cli",
|
|
28
|
+
"harvask",
|
|
29
|
+
"tenant",
|
|
30
|
+
"ai-worker",
|
|
31
|
+
"coding-agent"
|
|
32
|
+
],
|
|
33
|
+
"author": "wown",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^12.1.0",
|
|
37
|
+
"env-paths": "^3.0.0",
|
|
38
|
+
"open": "^10.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.10.0",
|
|
42
|
+
"typescript": "^5.6.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<!-- Generated from skills/harvask/SKILL.md by scripts/gen-agent-docs.mjs. Edit that file, then run `npm run gen:agents`. Do not edit skills/harvask/AGENTS.md by hand. -->
|
|
2
|
+
|
|
3
|
+
# harvask CLI
|
|
4
|
+
|
|
5
|
+
`harvask` is a command-line client for a harvask tenant, designed for coding
|
|
6
|
+
agents: output is JSON by default and errors are structured as
|
|
7
|
+
`Error / Why / Hint`.
|
|
8
|
+
|
|
9
|
+
## First: authenticate
|
|
10
|
+
|
|
11
|
+
Login is browser-based (no password typed into the CLI):
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
harvask login --tenant <tenant-domain> # opens browser, approve, done (loopback)
|
|
15
|
+
harvask login --tenant <tenant-domain> --no-browser # print URL, approve, paste the code (remote/headless)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
You must be logged into the web app to approve. Tokens are stored per tenant
|
|
19
|
+
under your OS config dir (mode 0600). Check status with `harvask whoami`.
|
|
20
|
+
Log out with `harvask logout`.
|
|
21
|
+
|
|
22
|
+
If a command fails with an auth error (exit code 3), run `harvask login` again.
|
|
23
|
+
|
|
24
|
+
## Core workflow: discover → fetch → act
|
|
25
|
+
|
|
26
|
+
1. **discover** the target
|
|
27
|
+
```
|
|
28
|
+
harvask project list
|
|
29
|
+
harvask issue search -p PRJ "login bug"
|
|
30
|
+
```
|
|
31
|
+
2. **fetch** details
|
|
32
|
+
```
|
|
33
|
+
harvask issue show -p PRJ 1234 --comments
|
|
34
|
+
harvask wiki show -p PRJ --slug api-documentation
|
|
35
|
+
```
|
|
36
|
+
3. **act**
|
|
37
|
+
```
|
|
38
|
+
harvask issue create -p PRJ --title "Fix login" --type bug --priority high
|
|
39
|
+
harvask issue status -p PRJ 1234 in_progress
|
|
40
|
+
harvask issue assign -p PRJ 1234 --assignee-id 42
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## project key vs id
|
|
44
|
+
|
|
45
|
+
- Humans know the **key** (e.g. `PRJ`); the API uses a numeric **id**.
|
|
46
|
+
- Every project-scoped command accepts either via `-p, --project <key|id>`
|
|
47
|
+
(`-p PRJ` or `-p 5`). The CLI resolves keys to ids automatically.
|
|
48
|
+
- For `issue show/update/status/assign`, the issue argument accepts a numeric id
|
|
49
|
+
or a key like `PRJ-12` (resolved via search).
|
|
50
|
+
- New to a tenant? Start with `harvask project list`.
|
|
51
|
+
|
|
52
|
+
## Reading output
|
|
53
|
+
|
|
54
|
+
- Default output is pretty JSON.
|
|
55
|
+
- `--data` prints only the payload (unwraps `{issues:[...]}` → the array).
|
|
56
|
+
- `--raw` prints the unformatted response.
|
|
57
|
+
- `--json` forces machine-readable JSON (no color), even in a terminal.
|
|
58
|
+
- Results go to stdout; errors go to stderr.
|
|
59
|
+
|
|
60
|
+
## Long input (Markdown / JSON)
|
|
61
|
+
|
|
62
|
+
- `--content` accepts a literal string, `@file.md` (read a file), or `-` (stdin).
|
|
63
|
+
```
|
|
64
|
+
harvask wiki create -p PRJ --title "Docs" --content @docs.md
|
|
65
|
+
harvask note create --content - # read from stdin
|
|
66
|
+
```
|
|
67
|
+
- The `api` escape hatch accepts a JSON body the same way (`@body.json`, `-`).
|
|
68
|
+
|
|
69
|
+
## Common errors and what to do
|
|
70
|
+
|
|
71
|
+
- **exit 3 / 401** — token invalid or expired → `harvask login --tenant <domain>`.
|
|
72
|
+
- **403 tenant_mismatch** — token is for another tenant → use `--profile <domain>`
|
|
73
|
+
or re-login against the correct tenant.
|
|
74
|
+
- **403 not_project_member** — you are not a member of that project → pick a
|
|
75
|
+
project from `harvask project list` or ask an admin to add you.
|
|
76
|
+
- **404** — wrong id/key → verify with `harvask project list` / `harvask issue search`.
|
|
77
|
+
- **422** — validation: read `Why:` for the offending field
|
|
78
|
+
(e.g. `issue_type`/`priority` enums, `title` ≤ 255, wiki/note content ≤ 500000).
|
|
79
|
+
|
|
80
|
+
## Escape hatch (anything not wrapped)
|
|
81
|
+
|
|
82
|
+
`harvask api <method> <path> [json]` calls any tenant API endpoint directly,
|
|
83
|
+
for anything not covered by a dedicated command. Relative paths are namespaced
|
|
84
|
+
automatically; absolute URLs and `..` are rejected.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
harvask api GET projects
|
|
88
|
+
harvask api GET projects/5/issues?status=open
|
|
89
|
+
harvask api POST notes '{"content":"hi","is_pinned":true}'
|
|
90
|
+
harvask api POST projects/5/issues @issue.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Command summary
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
harvask login | logout | whoami
|
|
97
|
+
|
|
98
|
+
# project-scoped (pass -p <key|id>)
|
|
99
|
+
harvask project list | show <key|id> | create | members list|add|remove <key|id>
|
|
100
|
+
harvask issue list | show | create | update | search | status | assign
|
|
101
|
+
harvask issue comment list | create
|
|
102
|
+
harvask issue todo list | show | create | update | delete
|
|
103
|
+
harvask issue memory list | show | create | update | delete
|
|
104
|
+
harvask issue dependency list | add | remove
|
|
105
|
+
harvask issue bulk-create | bulk-update (--items @file.json [--atomic])
|
|
106
|
+
harvask wiki list | show | create | update | search | history | delete | bulk-create | bulk-update
|
|
107
|
+
harvask milestone list | create | update
|
|
108
|
+
harvask workflow list | show | create | update
|
|
109
|
+
harvask scheduled-task list | show | create | update
|
|
110
|
+
harvask file search | stats
|
|
111
|
+
harvask storage search | stats | upload | folder | move | download | content
|
|
112
|
+
harvask preview-config show | update
|
|
113
|
+
harvask site-credential # list browser credentials
|
|
114
|
+
|
|
115
|
+
# personal / tenant-wide (no -p)
|
|
116
|
+
harvask note list | show | create | update | delete | search
|
|
117
|
+
harvask calendar list | show | create | update | delete
|
|
118
|
+
harvask global-wiki list | show | create | update | search | delete | bulk-create | bulk-update
|
|
119
|
+
harvask user list | search
|
|
120
|
+
harvask chat history <chatId> | search <q> | list | thread <kind> <uuid>
|
|
121
|
+
harvask attachment upload | download <id>
|
|
122
|
+
|
|
123
|
+
harvask api <method> <path> [json] # escape hatch
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Notes:
|
|
127
|
+
- `personal-wiki` is intentionally not available via the CLI.
|
|
128
|
+
- File/binary: `storage upload` / `attachment upload` take `--file <path>`;
|
|
129
|
+
`storage download` / `attachment download` save with `--out <path>`.
|
|
130
|
+
|
|
131
|
+
Use `harvask <command> --help` for options, examples, and prerequisites.
|