@pecot/tools-mcp 1.0.2 → 1.1.1

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 (40) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +30 -3
  3. package/cli/dist/cli.d.ts +1 -2
  4. package/cli/dist/cli.js +9 -13
  5. package/cli/dist/diagnostics.d.ts +38 -0
  6. package/cli/dist/diagnostics.js +46 -0
  7. package/cli/dist/domain/arguments.d.ts +2 -15
  8. package/cli/dist/domain/arguments.js +38 -99
  9. package/cli/dist/domain/athena-query.d.ts +2 -8
  10. package/cli/dist/domain/athena-query.js +10 -35
  11. package/cli/dist/domain/s3-input.d.ts +2 -9
  12. package/cli/dist/domain/s3-input.js +8 -21
  13. package/cli/dist/infra/awscli.d.ts +8 -36
  14. package/cli/dist/infra/awscli.js +15 -55
  15. package/cli/dist/infra/sso/login-registry.d.ts +4 -21
  16. package/cli/dist/infra/sso/login-registry.js +13 -56
  17. package/cli/dist/mcp/serve.d.ts +6 -28
  18. package/cli/dist/mcp/serve.js +20 -64
  19. package/cli/dist/mcp/server.d.ts +4 -20
  20. package/cli/dist/mcp/server.js +9 -35
  21. package/cli/dist/mcp/tools/_shared.d.ts +6 -27
  22. package/cli/dist/mcp/tools/_shared.js +36 -65
  23. package/cli/dist/mcp/tools/athena.js +17 -27
  24. package/cli/dist/mcp/tools/cloudwatch.js +13 -15
  25. package/cli/dist/mcp/tools/ec2.js +7 -10
  26. package/cli/dist/mcp/tools/ecr.js +10 -16
  27. package/cli/dist/mcp/tools/index.js +1 -5
  28. package/cli/dist/mcp/tools/lambda.js +10 -10
  29. package/cli/dist/mcp/tools/logs.js +22 -22
  30. package/cli/dist/mcp/tools/rds.js +15 -18
  31. package/cli/dist/mcp/tools/s3.js +19 -29
  32. package/cli/dist/mcp/tools/scheduler.js +10 -13
  33. package/cli/dist/mcp/tools/sqs.js +8 -8
  34. package/cli/dist/mcp/tools/sso.d.ts +1 -5
  35. package/cli/dist/mcp/tools/sso.js +33 -59
  36. package/cli/dist/mcp/tools/sts.js +2 -2
  37. package/cli/dist/node-runtime.d.ts +6 -1
  38. package/cli/dist/node-runtime.js +16 -8
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pecot-t",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "pecot-t(pecot-tools)— 許可リストで限定した CLI コマンドを MCP ツールとして提供するプラグイン",
5
5
  "author": {
6
6
  "name": "pecot"
package/README.md CHANGED
@@ -22,7 +22,32 @@ MCP クライアントに 1 エントリ登録するだけで pecot-tools のツ
22
22
  "mcpServers": {
23
23
  "pecot-tools": {
24
24
  "command": "npx",
25
- "args": ["-y", "@pecot/tools-mcp"]
25
+ "args": ["-y", "--prefix=/", "@pecot/tools-mcp"]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ `--prefix=/` は、pnpm の `devEngines` を宣言したリポジトリの中で起動しても止まらないようにするための指定です。
32
+ `--prefix` を付けないと、そのリポジトリを cwd にした npx が `EBADDEVENGINES` で止まります。
33
+ npm には devEngines の判定だけを外す公式の手段が無く、要望(npm/rfcs#830)は not_planned で閉じています。
34
+
35
+ パッケージは `--package=` ではなく、位置引数で渡してください。
36
+ `--package=` で渡すと、npm が `npm_config_package` をサーバの子プロセスに残します。
37
+ すると、サーバから起動したプロセスで動く npx が、これを読んで別のパッケージを実行します。
38
+
39
+ `--prefix=/` には次の副作用があります。
40
+
41
+ - 利用者のリポジトリの `.npmrc` を読まず、globalconfig も `/etc/npmrc` になる。registry・proxy・cafile は `~/.npmrc` に置く
42
+ - `npm_config_prefix=/` などの環境変数がサーバに引き継がれる。サーバの子で `npm i -g` を打つと `/lib/node_modules` を向く
43
+
44
+ この副作用を避けたいときは、先に `npm i -g @pecot/tools-mcp` で入れて、コマンドを直接登録します。
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "pecot-tools": {
50
+ "command": "pecot-tools-mcp"
26
51
  }
27
52
  }
28
53
  }
@@ -35,14 +60,14 @@ Windows の MCP クライアントから使う場合は WSL2 経由で起動し
35
60
  "mcpServers": {
36
61
  "pecot-tools": {
37
62
  "command": "wsl",
38
- "args": ["npx", "-y", "@pecot/tools-mcp"]
63
+ "args": ["npx", "-y", "--prefix=/", "@pecot/tools-mcp"]
39
64
  }
40
65
  }
41
66
  }
42
67
  ```
43
68
 
44
69
  WSL 内の npx が非ログインシェルの PATH に無い場合は、`args` を差し替えます。
45
- 差し替え後は `"args": ["-e", "bash", "-lc", "npx -y @pecot/tools-mcp"]` です。
70
+ 差し替え後は `"args": ["-e", "bash", "-lc", "npx -y --prefix=/ @pecot/tools-mcp"]` です。
46
71
 
47
72
  版を固定するときは、`@pecot/tools-mcp@1.2.3` のように版を付けます。
48
73
  依存は `npm-shrinkwrap.json` で固定されているため、同じ版なら同じ依存が入ります。
@@ -109,6 +134,8 @@ SSO で認証するときは、開始と完了確認の 2 つのツールを順
109
134
  | `timeout` | 時間切れ。取得範囲を狭めて再実行する |
110
135
  | `other` | 上記以外。応答の `stderr` を確認する |
111
136
 
137
+ `EBADDEVENGINES` で止まるときは、登録の `args` に `--prefix=/` が付いているか確認してください。
138
+
112
139
  ## 版
113
140
 
114
141
  npm の版は pecot-tools プラグインの版と一致します。
package/cli/dist/cli.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  export declare function createProgram(): Command;
3
3
  /**
4
- commander の既定である process.exit() 直呼びを止め、例外へ振り替える。
5
- サブコマンドを足したときに取りこぼさないよう再帰で適用する。
4
+ commander の process.exit() 直呼びを止め、例外へ振り替える。再帰で全サブコマンドに適用する
6
5
  */
7
6
  export declare function applyExitOverrideRecursively(command: Command): void;
8
7
  export declare function main(nodeVersion?: string): void;
package/cli/dist/cli.js CHANGED
@@ -1,19 +1,17 @@
1
- // pecot-tools CLI のエントリポイント。MCP サーバの起動 (`mcp`) だけを持つ薄い Command で、
2
- // MCP SDK の読み込みは action 内の dynamic import に閉じる。
1
+ // pecot-tools CLI のエントリポイント。MCP SDK は action 内の dynamic import に閉じる。
3
2
  import { realpathSync } from 'node:fs';
4
3
  import { pathToFileURL } from 'node:url';
5
4
  import { Command, CommanderError } from 'commander';
6
- import { checkNodeRuntime } from './node-runtime.js';
5
+ import { createDiagnostics } from './diagnostics.js';
6
+ import { nodeRuntimeGuidance } from './node-runtime.js';
7
7
  import { readVersion } from './version.js';
8
8
  export function createProgram() {
9
9
  const program = new Command();
10
10
  program.name('pecot-tools').version(readVersion());
11
11
  const mcp = program
12
12
  .command('mcp')
13
- .description('MCP サーバとして stdio (stdin/stdout) で待ち受ける');
14
- /* c8 ignore start -- action は dynamic import して実 process.stdin/stdout へ接続する。
15
- 単体テストから起動すると vitest worker の stdout ごと差し替えてしまう。
16
- tests/e2e/mcp-stdio.test.ts が実プロセスの spawn で担保する。 */
13
+ .description('Serve as an MCP server over stdio (stdin/stdout)');
14
+ /* c8 ignore start -- 実 stdin/stdout へ接続する。tests/e2e/mcp-stdio.test.ts が担保 */
17
15
  mcp.action(async () => {
18
16
  const { executeMcpServe, mcpServeDefaultDependencies } = await import('./mcp/serve.js');
19
17
  executeMcpServe(mcpServeDefaultDependencies);
@@ -22,8 +20,7 @@ export function createProgram() {
22
20
  return program;
23
21
  }
24
22
  /**
25
- commander の既定である process.exit() 直呼びを止め、例外へ振り替える。
26
- サブコマンドを足したときに取りこぼさないよう再帰で適用する。
23
+ commander の process.exit() 直呼びを止め、例外へ振り替える。再帰で全サブコマンドに適用する
27
24
  */
28
25
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- exitOverride() で変更する
29
26
  export function applyExitOverrideRecursively(command) {
@@ -32,9 +29,9 @@ export function applyExitOverrideRecursively(command) {
32
29
  applyExitOverrideRecursively(child);
33
30
  }
34
31
  export function main(nodeVersion = process.versions.node) {
35
- const runtimeError = checkNodeRuntime(nodeVersion);
32
+ const runtimeError = nodeRuntimeGuidance(nodeVersion);
36
33
  if (runtimeError !== undefined) {
37
- process.stderr.write(`pecot-tools: ${runtimeError}\n`);
34
+ createDiagnostics(process.stderr).error(runtimeError);
38
35
  process.exitCode = 1;
39
36
  return;
40
37
  }
@@ -53,8 +50,7 @@ export function main(nodeVersion = process.versions.node) {
53
50
  }
54
51
  /* v8 ignore start -- 直接起動時にだけ通る。挙動は tests/e2e/version.test.ts で担保 */
55
52
  if (process.argv[1] !== undefined) {
56
- // import.meta.url は symlink を解決した実体パスを指すため、argv[1] 側も realpath に揃える。
57
- // 揃えないと bin ラッパー等の symlink 経由起動で一致せず、何もせず exit 0 で終わる。
53
+ // import.meta.url は実体パスなので argv[1] も realpath に揃える。symlink 経由起動で一致しなくなるため。
58
54
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- 起動された自分自身のパス
59
55
  const selfUrl = pathToFileURL(realpathSync(process.argv[1])).href;
60
56
  if (import.meta.url === selfUrl) {
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export interface DiagnosticStream {
3
+ write(text: string): unknown;
4
+ }
5
+ /**
6
+ done の動詞。自由な文字列を受け取らない
7
+ */
8
+ export declare const DiagnosticVerbSchema: z.ZodEnum<{
9
+ add: "add";
10
+ create: "create";
11
+ }>;
12
+ export type DiagnosticVerb = z.infer<typeof DiagnosticVerbSchema>;
13
+ export interface DiagnosticOperation {
14
+ readonly verb: DiagnosticVerb;
15
+ readonly target: string;
16
+ }
17
+ export interface Diagnostics {
18
+ readonly error: (message: string) => void;
19
+ readonly warning: (message: string) => void;
20
+ /**
21
+ 情報の診断だけに使う。進捗の行には使わない
22
+ */
23
+ readonly note: (message: string) => void;
24
+ /**
25
+ 実行した操作を `<過去分詞>: <target>` で出す
26
+ */
27
+ readonly done: (operation: DiagnosticOperation) => void;
28
+ }
29
+ export declare function createDiagnostics(stderr: DiagnosticStream): Diagnostics;
30
+ /**
31
+ 結果の本体を stdout にそのまま出す。末尾に改行が無ければ 1 つ足す
32
+ */
33
+ export declare function writeData(stdout: DiagnosticStream, text: string): void;
34
+ /**
35
+ commander が組み立てた使い方エラー・ヘルプの枠を、手を加えずに書く。commander が `error: ` を付ける
36
+ */
37
+ export declare function passthrough(stream: DiagnosticStream, text: string): void;
38
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1,46 @@
1
+ // stderr の診断行と stdout のデータを書く、唯一の場所。
2
+ // 行の形は specs/021-cli-output-functions/contracts/output-lines.md が決める。
3
+ // 期待の行は tests/diagnostics-cases.json に置く。
4
+ import { z } from 'zod';
5
+ /**
6
+ done の動詞。自由な文字列を受け取らない
7
+ */
8
+ export const DiagnosticVerbSchema = z.enum(['create', 'add']);
9
+ function pastOf(verb) {
10
+ switch (verb) {
11
+ case 'create': {
12
+ return 'created';
13
+ }
14
+ case 'add': {
15
+ return 'added';
16
+ }
17
+ }
18
+ }
19
+ /**
20
+ 末尾の改行を取り除き、2 行目以降の先頭に空白 2 つを付け、末尾に改行を 1 つ付ける
21
+ */
22
+ function formatMessage(prefix, message) {
23
+ const lines = message.replace(/\n+$/, '').split('\n');
24
+ return `${prefix}: ${lines.join('\n ')}\n`;
25
+ }
26
+ export function createDiagnostics(stderr) {
27
+ return {
28
+ error: (message) => { stderr.write(formatMessage('error', message)); },
29
+ warning: (message) => { stderr.write(formatMessage('warning', message)); },
30
+ note: (message) => { stderr.write(formatMessage('note', message)); },
31
+ done: ({ verb, target }) => { stderr.write(`${pastOf(verb)}: ${target}\n`); },
32
+ };
33
+ }
34
+ /**
35
+ 結果の本体を stdout にそのまま出す。末尾に改行が無ければ 1 つ足す
36
+ */
37
+ export function writeData(stdout, text) {
38
+ stdout.write(text.endsWith('\n') ? text : `${text}\n`);
39
+ }
40
+ /**
41
+ commander が組み立てた使い方エラー・ヘルプの枠を、手を加えずに書く。commander が `error: ` を付ける
42
+ */
43
+ export function passthrough(stream, text) {
44
+ stream.write(text);
45
+ }
46
+ //# sourceMappingURL=diagnostics.js.map
@@ -14,24 +14,11 @@ export interface CommonOptions {
14
14
  readonly region?: string;
15
15
  }
16
16
  /**
17
- 入力を検証する。問題があれば invalid-input を、無ければ undefined を返す。
18
-
19
- profile / region も同じ処理で検証する(別扱いにすると書式検証が抜ける)。
17
+ 入力を検証する。問題があれば invalid-input を、無ければ undefined を返す。profile / region も同じ処理で検証する。
20
18
  */
21
19
  export declare function validateInput(entry: Readonly<AllowlistEntry>, input: Readonly<Record<string, unknown>>): InvalidInput | undefined;
22
20
  /**
23
- awscli へ渡す引数配列を組み立てる。
24
-
25
- 並びは `<service> <subcommand> <位置引数…> <フラグ…> --output json --no-cli-pager
26
- --profile <p> --region <r>`。位置引数を先・フラグを後に置くと、string-list(nargs が貪欲)と
27
- 位置引数が同居しても awscli v2 の argparse が取り違えない(動作確認済み)。
28
-
29
- `--no-cli-pager` は jsonOutput の有無に依らず常に付ける(非 tty では無害で、分岐を持たない方が単純)。
30
-
31
- `--profile` / `--region` は末尾で 1 度だけ付ける。エントリ側が inputs に同名を宣言していても
32
- フラグ列挙では飛ばす。
33
-
34
- 値が '-' で始まる string は `--flag=value` の 1 トークンにする(flagTokens)。
21
+ awscli へ渡す引数配列を組み立てる。位置引数を先・フラグを後に置くと、nargs が貪欲な string-list と同居しても取り違えない。
35
22
  */
36
23
  export declare function buildArguments(entry: Readonly<AllowlistEntry>, input: Readonly<Record<string, unknown>>, common?: Readonly<CommonOptions>): string[];
37
24
  //# sourceMappingURL=arguments.d.ts.map
@@ -1,29 +1,13 @@
1
1
  // 入力の検証と awscli 引数配列の組み立て。
2
- //
3
- // shell を介さず spawn するため、利用者の値は必ず 1 要素 1 値で配列へ入れる。
4
- // 値が '-' で始まると awscli の引数解析でオプションと解釈され得るので、原則として先頭 '-' を
5
- // 拒否する(research R3)。例外はフラグで渡す 1 値の string だけで、こちらは
6
- // `--flag=value` の 1 トークン形にして渡す(doesAllowLeadingDash / flagTokens)。
7
- //
8
- // shorthand-list(`--dimensions` / `--filters` / `--image-ids`)は JSON.stringify で 1 トークンに
9
- // する。awscli はこれらのフラグを shorthand と JSON の双方で受ける。
10
- // JSON を選べば区切り文字の解釈がエスケープに閉じ、利用者の値が構文へ漏れる余地が無くなる(`,` を含むタグ値も渡せる)。
2
+ // 先頭 '-' の値は原則拒否し、例外の string は `--flag=value` の 1 トークンで渡す(research R3)。
11
3
  import { PROFILE_INPUT, REGION_INPUT } from './allowlist.js';
12
4
  import { validateLocalPath, validateS3ListUri, validateS3Uri } from './s3-input.js';
13
5
  /**
14
- shorthand の構成要素の値に混入すると構文として解釈される文字。
15
-
16
- awscli の shorthand は `Key=value,Key=value` の書式。
17
- 構成要素の値に `,` や `=` を含めると、契約に無いキー(`ExpectedBucketOwner` 等)を後ろに足せる。
18
- 実 awscli が受理することを動作確認済みなので、組み立て前に弾く。対象は文字列を連結する `shorthand`(Athena / ECR の
19
- `--filter`)だけで、JSON で組み立てる `shorthand-list` には要らない。
6
+ shorthand の値に混入すると構文になる文字。`,` や `=` で契約外のキーを足せるため、組み立て前に弾く。
20
7
  */
21
8
  const SHORTHAND_SYNTAX = /[,={}]/;
22
9
  /**
23
- 制御文字(U+0000〜U+001F と U+007F)を含むか。
24
-
25
- argv に NUL を載せると spawn が同期 throw し、ExecutionResult を伴わない応答になる。
26
- 他の制御文字も awscli の解釈が値ごとに変わるため、検証の段階で invalid-input にする。
10
+ 制御文字(U+0000〜U+001F と U+007F)を含むか。NUL は spawn が同期 throw するため、検証で弾く。
27
11
  */
28
12
  function hasControlCharacter(text) {
29
13
  for (const character of text) {
@@ -34,10 +18,7 @@ function hasControlCharacter(text) {
34
18
  return false;
35
19
  }
36
20
  /**
37
- 共通入力として末尾で付与するフラグ。
38
-
39
- エントリ側が同じフラグを inputs に宣言していても、付与箇所は末尾の 1 本に絞る
40
- (フラグ列挙でも出すと `--profile x --profile x` が並ぶ)。
21
+ 共通入力として末尾で付与するフラグ。inputs 側の宣言と重複して並ばないよう、付与は末尾の 1 本に絞る。
41
22
  */
42
23
  const COMMON_FLAGS = new Set([PROFILE_INPUT.awsFlag, REGION_INPUT.awsFlag].filter(flag => flag !== undefined));
43
24
  /**
@@ -108,17 +89,7 @@ function kindPredicate(kind, value) {
108
89
  }
109
90
  }
110
91
  /**
111
- 値の先頭 '-' を許す入力か。
112
-
113
- 対象はフラグで渡す `kind: 'string'` の 1 値だけ。CloudWatch Logs の除外パターン(`-INFO`)の
114
- ように、先頭 '-' に意味を持つ値が実在する。
115
- 実 awscli 2.36.40 では `--filter-pattern=-INFO` が受理される(`--filter-pattern -INFO` は 252)。組み立ては buildArguments が
116
- `--flag=value` の 1 トークン形で行うため、オプション注入にはならない。
117
-
118
- string-list は nargs が貪欲で、1 トークン形へ畳めない。
119
- 位置引数には `--flag=value` の逃がし先が無い。
120
- そのため、いずれも従来どおり拒否する(enum は許容値が許可リストの静的宣言なので、
121
- そもそも利用者の値が本判定に届かない)。
92
+ 値の先頭 '-' を許す入力か。対象はフラグで渡す string の 1 値だけで、`--flag=value` の 1 トークンで組み立てる。
122
93
  */
123
94
  function doesAllowLeadingDash(spec) {
124
95
  return spec.kind === 'string' && spec.awsFlag !== undefined && spec.positional === undefined;
@@ -128,65 +99,55 @@ string 系 1 値の検証。制御文字・先頭 '-' 拒否と kind 固有の
128
99
  */
129
100
  function validateStringValue(spec, value) {
130
101
  if (hasControlCharacter(value)) {
131
- return invalid(`${spec.name} は制御文字を含まない値で指定してください`);
102
+ return invalid(`${spec.name} must not contain control characters`);
132
103
  }
133
104
  if (value.startsWith('-') && !doesAllowLeadingDash(spec)) {
134
- return invalid(`${spec.name} の値は '-' で始められません: ${value}`);
105
+ return invalid(`${spec.name} must not start with '-': ${value}`);
135
106
  }
136
107
  const predicate = kindPredicate(spec.kind, value);
137
108
  if (predicate !== undefined) {
138
109
  return invalid(`${spec.name} ${predicate}: ${value}`);
139
110
  }
140
111
  if (spec.pattern !== undefined && !spec.pattern.test(value)) {
141
- return invalid(`${spec.name} の形式が不正です: ${value}`);
112
+ return invalid(`${spec.name} has an invalid format: ${value}`);
142
113
  }
143
114
  return undefined;
144
115
  }
145
116
  /**
146
- shorthand の構成要素として載る値を検証する。
147
-
148
- shorthand 自身は入力レコードから読まない(利用者は指定できない)。見るのは構成要素の
149
- 取得元入力の値だけで、取得元自体の型・書式は通常の入力として別途検証されている。
117
+ shorthand の構成要素の値を検証する。shorthand 自身は読まず、取得元入力の値だけを見る。
150
118
  */
151
119
  function validateShorthandParts(spec, input) {
152
120
  const declaredParts = spec.parts ?? [];
153
121
  for (const part of declaredParts) {
154
122
  const value = readInput(input, part.from);
155
123
  if (typeof value === 'string' && SHORTHAND_SYNTAX.test(value)) {
156
- return invalid(`${part.from} に , = { } は含められません: ${value}`);
124
+ return invalid(`${part.from} must not contain , = { }: ${value}`);
157
125
  }
158
126
  }
159
127
  return undefined;
160
128
  }
161
129
  /**
162
- shorthand-list の構成要素の値 1 つずつを検証する。
163
-
164
- 区切り文字(`, = { }`)は見ない。組み立てが JSON.stringify で、値はエスケープされて JSON の
165
- 文字列リテラルに収まるため、要素の中へ契約外のキーを足す手段が無い。
166
- 空文字は awscli が空値として解釈するので弾く。制御文字は spawn へ届く前にここで拒否する。
130
+ shorthand-list の構成要素の値を検証する。JSON.stringify がエスケープするので、区切り文字は見ない。
167
131
  */
168
132
  function validateElementTexts(spec, field, texts) {
169
133
  for (const text of texts) {
170
134
  if (text === '')
171
- return invalid(`${spec.name} の ${field} に空文字は指定できません`);
135
+ return invalid(`${spec.name} ${field} must not be empty`);
172
136
  if (hasControlCharacter(text)) {
173
- return invalid(`${spec.name} の ${field} は制御文字を含まない値で指定してください`);
137
+ return invalid(`${spec.name} ${field} must not contain control characters`);
174
138
  }
175
139
  }
176
140
  return undefined;
177
141
  }
178
142
  /**
179
- shorthand-list の要素 1 件を検証する。
180
-
181
- 値を持つ構成要素が 1 つも無い要素は拒否する。`--filters` に空の要素を渡すと awscli が
182
- 空トークンを受け取り、意図しない絞り込みになるため。
143
+ shorthand-list の要素 1 件を検証する。値を持つ構成要素が無い要素は、意図しない絞り込みになるので拒否する。
183
144
  */
184
145
  function validateShorthandListElement(spec, element) {
185
146
  const parts = spec.elements ?? [];
186
147
  /* c8 ignore start -- inputSchema の z.strictObject が入力検証エラーで先に落とすため到達しない(二重防御) */
187
148
  for (const key of Object.keys(element)) {
188
149
  if (parts.every(part => part.field !== key)) {
189
- return invalid(`${spec.name} の要素に指定できないキーです: ${key}`);
150
+ return invalid(`${spec.name} element has an unsupported key: ${key}`);
190
151
  }
191
152
  }
192
153
  /* c8 ignore stop */
@@ -198,16 +159,16 @@ function validateShorthandListElement(spec, element) {
198
159
  let texts;
199
160
  if (part.list === true) {
200
161
  if (!isStringArray(value)) {
201
- return invalid(`${spec.name} の ${part.field} は文字列の配列で指定してください`);
162
+ return invalid(`${spec.name} ${part.field} must be an array of strings`);
202
163
  }
203
164
  if (value.length === 0) {
204
- return invalid(`${spec.name} の ${part.field} は 1 つ以上の値で指定してください`);
165
+ return invalid(`${spec.name} ${part.field} must have at least one value`);
205
166
  }
206
167
  texts = value;
207
168
  }
208
169
  else {
209
170
  if (typeof value !== 'string') {
210
- return invalid(`${spec.name} の ${part.field} は文字列で指定してください`);
171
+ return invalid(`${spec.name} ${part.field} must be a string`);
211
172
  }
212
173
  texts = [value];
213
174
  }
@@ -216,17 +177,17 @@ function validateShorthandListElement(spec, element) {
216
177
  return rejected;
217
178
  filled += 1;
218
179
  }
219
- return filled === 0 ? invalid(`${spec.name} の要素は ${parts.map(part => part.field).join(' / ')} のいずれかで指定してください`) : undefined;
180
+ return filled === 0 ? invalid(`${spec.name} element must have at least one of ${parts.map(part => part.field).join(' / ')}`) : undefined;
220
181
  }
221
182
  /**
222
183
  shorthand-list の値(オブジェクトの配列)を検証する。
223
184
  */
224
185
  function validateShorthandList(spec, value) {
225
186
  if (!Array.isArray(value))
226
- return invalid(`${spec.name} はオブジェクトの配列で指定してください`);
187
+ return invalid(`${spec.name} must be an array of objects`);
227
188
  for (const element of value) {
228
189
  if (!isElementRecord(element)) {
229
- return invalid(`${spec.name} の要素はオブジェクトで指定してください`);
190
+ return invalid(`${spec.name} element must be an object`);
230
191
  }
231
192
  const rejected = validateShorthandListElement(spec, element);
232
193
  if (rejected !== undefined)
@@ -240,11 +201,11 @@ function validateValue(spec, value) {
240
201
  case 'abs-path':
241
202
  case 's3-uri':
242
203
  case 's3-list-uri': {
243
- return typeof value === 'string' ? validateStringValue(spec, value) : invalid(`${spec.name} は文字列で指定してください`);
204
+ return typeof value === 'string' ? validateStringValue(spec, value) : invalid(`${spec.name} must be a string`);
244
205
  }
245
206
  case 'string-list': {
246
207
  if (!isStringArray(value))
247
- return invalid(`${spec.name} は文字列の配列で指定してください`);
208
+ return invalid(`${spec.name} must be an array of strings`);
248
209
  for (const element of value) {
249
210
  const rejected = validateStringValue(spec, element);
250
211
  if (rejected !== undefined)
@@ -252,31 +213,31 @@ function validateValue(spec, value) {
252
213
  // 列挙を持つ string-list(--statistics 等)は要素ごとに比較する。awscli は列挙を
253
214
  // クライアント側で検証しないため、ここを抜くと綴り違いがそのまま API へ届く。
254
215
  if (spec.values !== undefined && !spec.values.includes(element)) {
255
- return invalid(`${spec.name} に指定できない値です: ${element}`);
216
+ return invalid(`${spec.name} has an unsupported value: ${element}`);
256
217
  }
257
218
  }
258
219
  return undefined;
259
220
  }
260
221
  case 'integer': {
261
222
  if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
262
- return invalid(`${spec.name} は整数で指定してください`);
223
+ return invalid(`${spec.name} must be an integer`);
263
224
  }
264
225
  if (spec.min !== undefined && value < spec.min) {
265
- return invalid(`${spec.name} は ${String(spec.min)} 以上で指定してください: ${String(value)}`);
226
+ return invalid(`${spec.name} must be at least ${String(spec.min)}: ${String(value)}`);
266
227
  }
267
228
  if (spec.max !== undefined && value > spec.max) {
268
- return invalid(`${spec.name} は ${String(spec.max)} 以下で指定してください: ${String(value)}`);
229
+ return invalid(`${spec.name} must be at most ${String(spec.max)}: ${String(value)}`);
269
230
  }
270
231
  return undefined;
271
232
  }
272
233
  case 'boolean': {
273
- return typeof value === 'boolean' ? undefined : invalid(`${spec.name} は true / false で指定してください`);
234
+ return typeof value === 'boolean' ? undefined : invalid(`${spec.name} must be true or false`);
274
235
  }
275
236
  case 'enum': {
276
237
  if (typeof value !== 'string')
277
- return invalid(`${spec.name} は文字列で指定してください`);
238
+ return invalid(`${spec.name} must be a string`);
278
239
  if (spec.values?.includes(value) !== true) {
279
- return invalid(`${spec.name} に指定できない値です: ${value}`);
240
+ return invalid(`${spec.name} has an unsupported value: ${value}`);
280
241
  }
281
242
  return undefined;
282
243
  }
@@ -285,15 +246,13 @@ function validateValue(spec, value) {
285
246
  }
286
247
  /* c8 ignore start -- shorthand は validateInput が先に分岐するため到達しない。switch の網羅性のために置く */
287
248
  case 'shorthand': {
288
- return invalid(`${spec.name} は直接指定できません`);
249
+ return invalid(`${spec.name} cannot be specified directly`);
289
250
  }
290
251
  /* c8 ignore stop */
291
252
  }
292
253
  }
293
254
  /**
294
- 入力を検証する。問題があれば invalid-input を、無ければ undefined を返す。
295
-
296
- profile / region も同じ処理で検証する(別扱いにすると書式検証が抜ける)。
255
+ 入力を検証する。問題があれば invalid-input を、無ければ undefined を返す。profile / region も同じ処理で検証する。
297
256
  */
298
257
  export function validateInput(entry, input) {
299
258
  for (const spec of specsOf(entry)) {
@@ -306,7 +265,7 @@ export function validateInput(entry, input) {
306
265
  const value = readInput(input, spec.name);
307
266
  if (value === undefined) {
308
267
  if (spec.required)
309
- return invalid(`${spec.name} は必須です`);
268
+ return invalid(`${spec.name} is required`);
310
269
  continue;
311
270
  }
312
271
  const rejected = validateValue(spec, value);
@@ -317,7 +276,7 @@ export function validateInput(entry, input) {
317
276
  for (const group of exclusiveGroups) {
318
277
  const specified = group.filter(name => readInput(input, name) !== undefined);
319
278
  if (specified.length > 1) {
320
- return invalid(`${specified.join(' と ')} は同時に指定できません`);
279
+ return invalid(`${specified.join(' and ')} cannot be combined`);
321
280
  }
322
281
  }
323
282
  return undefined;
@@ -330,9 +289,7 @@ function effectiveValue(spec, input) {
330
289
  return isInputValue(value) ? value : undefined;
331
290
  }
332
291
  /**
333
- shorthand フラグに載せる `Key=value,…` を組み立てる。
334
-
335
- 値を持つ構成要素だけを並べ、1 つも無ければ undefined(= フラグごと省く)。
292
+ shorthand フラグに載せる `Key=value,…` を組み立てる。値を持つ構成要素が無ければ undefined(フラグごと省く)。
336
293
  */
337
294
  function shorthandValue(spec, input) {
338
295
  const parts = [];
@@ -345,11 +302,7 @@ function shorthandValue(spec, input) {
345
302
  return parts.length === 0 ? undefined : parts.join(',');
346
303
  }
347
304
  /**
348
- shorthand-list フラグに載せる JSON 1 トークンを組み立てる。
349
-
350
- 配列全体が `[{"Name":"…","Value":"…"}]` の 1 トークンになり、`list` の構成要素は JSON の配列のまま載せる。
351
- 値を持つ構成要素が無い要素は落とす。要素が 1 つも残らなければ undefined
352
- (= フラグごと省く)。
305
+ shorthand-list フラグに載せる JSON 1 トークンを組み立てる。要素が 1 つも残らなければ undefined。
353
306
  */
354
307
  function shorthandListToken(spec, input) {
355
308
  const value = readInput(input, spec.name);
@@ -384,27 +337,13 @@ function valueTexts(value) {
384
337
  return [typeof value === 'string' ? value : String(value)];
385
338
  }
386
339
  /**
387
- フラグ 1 つ分のトークン列。値が '-' で始まる string のときだけ `--flag=value` の 1 トークンにする。
388
-
389
- 別トークンで渡すと awscli の argparse が値をオプションとして読む。`=` 連結は shell を
390
- 介さない配列渡しなので、別トークンへ割れることも別コマンドへ委譲されることも無い。
340
+ フラグ 1 つ分のトークン列。値が '-' で始まる string は、argparse の誤読を避けて `--flag=value` の 1 トークンにする。
391
341
  */
392
342
  function flagTokens(flag, value) {
393
343
  return typeof value === 'string' && value.startsWith('-') ? [`${flag}=${value}`] : [flag, ...valueTexts(value)];
394
344
  }
395
345
  /**
396
- awscli へ渡す引数配列を組み立てる。
397
-
398
- 並びは `<service> <subcommand> <位置引数…> <フラグ…> --output json --no-cli-pager
399
- --profile <p> --region <r>`。位置引数を先・フラグを後に置くと、string-list(nargs が貪欲)と
400
- 位置引数が同居しても awscli v2 の argparse が取り違えない(動作確認済み)。
401
-
402
- `--no-cli-pager` は jsonOutput の有無に依らず常に付ける(非 tty では無害で、分岐を持たない方が単純)。
403
-
404
- `--profile` / `--region` は末尾で 1 度だけ付ける。エントリ側が inputs に同名を宣言していても
405
- フラグ列挙では飛ばす。
406
-
407
- 値が '-' で始まる string は `--flag=value` の 1 トークンにする(flagTokens)。
346
+ awscli へ渡す引数配列を組み立てる。位置引数を先・フラグを後に置くと、nargs が貪欲な string-list と同居しても取り違えない。
408
347
  */
409
348
  export function buildArguments(entry, input, common = {}) {
410
349
  const argv = [entry.service, entry.subcommand];
@@ -1,8 +1,5 @@
1
1
  /**
2
- 拒否理由。message は呼び出し元エージェントへそのまま返す。
3
-
4
- `unterminated`(引用符・コメントが閉じていない)を `not-read-only` と分けるのは、reason だけを
5
- 見る呼び出し元が「変更系だから拒否」と誤読しないようにするため。倒す先はどちらも拒否で同じ。
2
+ 拒否理由。message は呼び出し元へそのまま返す。unterminated を分けるのは、変更系と誤読させないため。
6
3
  */
7
4
  export type AthenaQueryRejectionReason = 'empty' | 'multi-statement' | 'not-read-only' | 'unterminated';
8
5
  export type AthenaQueryClassification = {
@@ -13,10 +10,7 @@ export type AthenaQueryClassification = {
13
10
  readonly message: string;
14
11
  };
15
12
  /**
16
- クエリを参照専用 / 拒否に分類する。
17
-
18
- 判定順は 未終端 → 複文 → 空 → 先頭キーワード。複文を空より先に見るのは、
19
- `; SELECT 1` のように 1 文目が空の入力を「空」ではなく「複文」と伝えるため。
13
+ クエリを参照専用 / 拒否に分類する。複文を空より先に見るのは、`; SELECT 1` を「複文」と伝えるため。
20
14
  */
21
15
  export declare function classifyAthenaQuery(query: string): AthenaQueryClassification;
22
16
  //# sourceMappingURL=athena-query.d.ts.map