@enactprotocol/cli 1.2.13 → 2.0.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 (73) hide show
  1. package/README.md +88 -0
  2. package/package.json +34 -38
  3. package/src/commands/auth/index.ts +940 -0
  4. package/src/commands/cache/index.ts +361 -0
  5. package/src/commands/config/README.md +239 -0
  6. package/src/commands/config/index.ts +164 -0
  7. package/src/commands/env/README.md +197 -0
  8. package/src/commands/env/index.ts +392 -0
  9. package/src/commands/exec/README.md +110 -0
  10. package/src/commands/exec/index.ts +195 -0
  11. package/src/commands/get/index.ts +198 -0
  12. package/src/commands/index.ts +30 -0
  13. package/src/commands/inspect/index.ts +264 -0
  14. package/src/commands/install/README.md +146 -0
  15. package/src/commands/install/index.ts +682 -0
  16. package/src/commands/list/README.md +115 -0
  17. package/src/commands/list/index.ts +138 -0
  18. package/src/commands/publish/index.ts +350 -0
  19. package/src/commands/report/index.ts +366 -0
  20. package/src/commands/run/README.md +124 -0
  21. package/src/commands/run/index.ts +686 -0
  22. package/src/commands/search/index.ts +368 -0
  23. package/src/commands/setup/index.ts +274 -0
  24. package/src/commands/sign/index.ts +652 -0
  25. package/src/commands/trust/README.md +214 -0
  26. package/src/commands/trust/index.ts +453 -0
  27. package/src/commands/unyank/index.ts +107 -0
  28. package/src/commands/yank/index.ts +143 -0
  29. package/src/index.ts +96 -0
  30. package/src/types.ts +81 -0
  31. package/src/utils/errors.ts +409 -0
  32. package/src/utils/exit-codes.ts +159 -0
  33. package/src/utils/ignore.ts +147 -0
  34. package/src/utils/index.ts +107 -0
  35. package/src/utils/output.ts +242 -0
  36. package/src/utils/spinner.ts +214 -0
  37. package/tests/commands/auth.test.ts +217 -0
  38. package/tests/commands/cache.test.ts +286 -0
  39. package/tests/commands/config.test.ts +277 -0
  40. package/tests/commands/env.test.ts +293 -0
  41. package/tests/commands/exec.test.ts +112 -0
  42. package/tests/commands/get.test.ts +179 -0
  43. package/tests/commands/inspect.test.ts +201 -0
  44. package/tests/commands/install-integration.test.ts +343 -0
  45. package/tests/commands/install.test.ts +288 -0
  46. package/tests/commands/list.test.ts +160 -0
  47. package/tests/commands/publish.test.ts +186 -0
  48. package/tests/commands/report.test.ts +194 -0
  49. package/tests/commands/run.test.ts +231 -0
  50. package/tests/commands/search.test.ts +131 -0
  51. package/tests/commands/sign.test.ts +164 -0
  52. package/tests/commands/trust.test.ts +236 -0
  53. package/tests/commands/unyank.test.ts +114 -0
  54. package/tests/commands/yank.test.ts +154 -0
  55. package/tests/e2e.test.ts +554 -0
  56. package/tests/fixtures/calculator/enact.yaml +34 -0
  57. package/tests/fixtures/echo-tool/enact.md +31 -0
  58. package/tests/fixtures/env-tool/enact.yaml +19 -0
  59. package/tests/fixtures/greeter/enact.yaml +18 -0
  60. package/tests/fixtures/invalid-tool/enact.yaml +4 -0
  61. package/tests/index.test.ts +8 -0
  62. package/tests/types.test.ts +84 -0
  63. package/tests/utils/errors.test.ts +303 -0
  64. package/tests/utils/exit-codes.test.ts +189 -0
  65. package/tests/utils/ignore.test.ts +461 -0
  66. package/tests/utils/output.test.ts +126 -0
  67. package/tsconfig.json +17 -0
  68. package/tsconfig.tsbuildinfo +1 -0
  69. package/dist/index.js +0 -231612
  70. package/dist/index.js.bak +0 -231611
  71. package/dist/web/static/app.js +0 -663
  72. package/dist/web/static/index.html +0 -117
  73. package/dist/web/static/style.css +0 -291
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Tests for the run command
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { Command } from "commander";
9
+ import { configureRunCommand } from "../../src/commands/run";
10
+
11
+ // Test fixtures directory
12
+ const FIXTURES_DIR = join(import.meta.dir, "..", "fixtures", "run-cmd");
13
+
14
+ describe("run command", () => {
15
+ beforeAll(() => {
16
+ // Create test fixtures
17
+ mkdirSync(FIXTURES_DIR, { recursive: true });
18
+ });
19
+
20
+ afterAll(() => {
21
+ // Clean up
22
+ if (existsSync(FIXTURES_DIR)) {
23
+ rmSync(FIXTURES_DIR, { recursive: true, force: true });
24
+ }
25
+ });
26
+
27
+ describe("command configuration", () => {
28
+ test("configures run command on program", () => {
29
+ const program = new Command();
30
+ configureRunCommand(program);
31
+
32
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
33
+ expect(runCmd).toBeDefined();
34
+ });
35
+
36
+ test("has correct description", () => {
37
+ const program = new Command();
38
+ configureRunCommand(program);
39
+
40
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
41
+ expect(runCmd?.description()).toBe("Execute a tool with its manifest-defined command");
42
+ });
43
+
44
+ test("accepts tool argument", () => {
45
+ const program = new Command();
46
+ configureRunCommand(program);
47
+
48
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
49
+ const args = runCmd?.registeredArguments ?? [];
50
+ expect(args.length).toBeGreaterThan(0);
51
+ expect(args[0]?.name()).toBe("tool");
52
+ });
53
+
54
+ test("has --args option for JSON input", () => {
55
+ const program = new Command();
56
+ configureRunCommand(program);
57
+
58
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
59
+ const opts = runCmd?.options ?? [];
60
+ const argsOpt = opts.find((o) => o.long === "--args");
61
+ expect(argsOpt).toBeDefined();
62
+ });
63
+
64
+ test("has --input option for key=value pairs", () => {
65
+ const program = new Command();
66
+ configureRunCommand(program);
67
+
68
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
69
+ const opts = runCmd?.options ?? [];
70
+ const inputOpt = opts.find((o) => o.long === "--input");
71
+ expect(inputOpt).toBeDefined();
72
+ });
73
+
74
+ test("has --timeout option", () => {
75
+ const program = new Command();
76
+ configureRunCommand(program);
77
+
78
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
79
+ const opts = runCmd?.options ?? [];
80
+ const timeoutOpt = opts.find((o) => o.long === "--timeout");
81
+ expect(timeoutOpt).toBeDefined();
82
+ });
83
+
84
+ test("has --dry-run option", () => {
85
+ const program = new Command();
86
+ configureRunCommand(program);
87
+
88
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
89
+ const opts = runCmd?.options ?? [];
90
+ const dryRunOpt = opts.find((o) => o.long === "--dry-run");
91
+ expect(dryRunOpt).toBeDefined();
92
+ });
93
+
94
+ test("has --verbose option", () => {
95
+ const program = new Command();
96
+ configureRunCommand(program);
97
+
98
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
99
+ const opts = runCmd?.options ?? [];
100
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
101
+ expect(verboseOpt).toBeDefined();
102
+ });
103
+
104
+ test("has --json option", () => {
105
+ const program = new Command();
106
+ configureRunCommand(program);
107
+
108
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
109
+ const opts = runCmd?.options ?? [];
110
+ const jsonOpt = opts.find((o) => o.long === "--json");
111
+ expect(jsonOpt).toBeDefined();
112
+ });
113
+
114
+ test("has --no-cache option", () => {
115
+ const program = new Command();
116
+ configureRunCommand(program);
117
+
118
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
119
+ const opts = runCmd?.options ?? [];
120
+ const noCacheOpt = opts.find((o) => o.long === "--no-cache");
121
+ expect(noCacheOpt).toBeDefined();
122
+ });
123
+
124
+ test("has --local option", () => {
125
+ const program = new Command();
126
+ configureRunCommand(program);
127
+
128
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
129
+ const opts = runCmd?.options ?? [];
130
+ const localOpt = opts.find((o) => o.long === "--local");
131
+ expect(localOpt).toBeDefined();
132
+ });
133
+ });
134
+
135
+ describe("input parsing helpers", () => {
136
+ // Test the parseInputArgs logic through module testing
137
+ // We test the expected behavior patterns
138
+
139
+ test("JSON args should be parseable", () => {
140
+ const argsJson = '{"name": "World", "count": 5}';
141
+ const parsed = JSON.parse(argsJson);
142
+ expect(parsed.name).toBe("World");
143
+ expect(parsed.count).toBe(5);
144
+ });
145
+
146
+ test("key=value pairs should be splittable", () => {
147
+ const input = "name=Alice";
148
+ const eqIndex = input.indexOf("=");
149
+ const key = input.slice(0, eqIndex);
150
+ const value = input.slice(eqIndex + 1);
151
+ expect(key).toBe("name");
152
+ expect(value).toBe("Alice");
153
+ });
154
+
155
+ test("key=value with JSON value should be parseable", () => {
156
+ const input = 'data={"nested": true}';
157
+ const eqIndex = input.indexOf("=");
158
+ const value = input.slice(eqIndex + 1);
159
+ const parsed = JSON.parse(value);
160
+ expect(parsed.nested).toBe(true);
161
+ });
162
+
163
+ test("key=value with multiple equals signs", () => {
164
+ const input = "url=https://api.example.com?key=value";
165
+ const eqIndex = input.indexOf("=");
166
+ const key = input.slice(0, eqIndex);
167
+ const value = input.slice(eqIndex + 1);
168
+ expect(key).toBe("url");
169
+ expect(value).toBe("https://api.example.com?key=value");
170
+ });
171
+ });
172
+
173
+ describe("timeout parsing", () => {
174
+ // Test timeout format parsing patterns
175
+
176
+ test("parses seconds", () => {
177
+ const match = "30s".match(/^(\d+)(s|m|h)?$/);
178
+ expect(match).toBeTruthy();
179
+ expect(match?.[1]).toBe("30");
180
+ expect(match?.[2]).toBe("s");
181
+ });
182
+
183
+ test("parses minutes", () => {
184
+ const match = "5m".match(/^(\d+)(s|m|h)?$/);
185
+ expect(match).toBeTruthy();
186
+ expect(match?.[1]).toBe("5");
187
+ expect(match?.[2]).toBe("m");
188
+ });
189
+
190
+ test("parses hours", () => {
191
+ const match = "1h".match(/^(\d+)(s|m|h)?$/);
192
+ expect(match).toBeTruthy();
193
+ expect(match?.[1]).toBe("1");
194
+ expect(match?.[2]).toBe("h");
195
+ });
196
+
197
+ test("parses number without unit (defaults to seconds)", () => {
198
+ const match = "30".match(/^(\d+)(s|m|h)?$/);
199
+ expect(match).toBeTruthy();
200
+ expect(match?.[1]).toBe("30");
201
+ expect(match?.[2]).toBeUndefined();
202
+ });
203
+
204
+ test("rejects invalid format", () => {
205
+ const match = "30x".match(/^(\d+)(s|m|h)?$/);
206
+ expect(match).toBeNull();
207
+ });
208
+
209
+ test("converts to milliseconds correctly", () => {
210
+ const parseTimeout = (timeout: string): number => {
211
+ const match = timeout.match(/^(\d+)(s|m|h)?$/);
212
+ if (!match) throw new Error("Invalid format");
213
+ const value = Number.parseInt(match[1] ?? "0", 10);
214
+ const unit = match[2] || "s";
215
+ switch (unit) {
216
+ case "h":
217
+ return value * 60 * 60 * 1000;
218
+ case "m":
219
+ return value * 60 * 1000;
220
+ default:
221
+ return value * 1000;
222
+ }
223
+ };
224
+
225
+ expect(parseTimeout("30s")).toBe(30000);
226
+ expect(parseTimeout("5m")).toBe(300000);
227
+ expect(parseTimeout("1h")).toBe(3600000);
228
+ expect(parseTimeout("30")).toBe(30000);
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Tests for the search command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { Command } from "commander";
7
+ import { configureSearchCommand } from "../../src/commands/search";
8
+
9
+ describe("search command", () => {
10
+ describe("command configuration", () => {
11
+ test("configures search command on program", () => {
12
+ const program = new Command();
13
+ configureSearchCommand(program);
14
+
15
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
16
+ expect(searchCmd).toBeDefined();
17
+ });
18
+
19
+ test("has correct description", () => {
20
+ const program = new Command();
21
+ configureSearchCommand(program);
22
+
23
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
24
+ expect(searchCmd?.description()).toBe("Search the Enact registry for tools");
25
+ });
26
+
27
+ test("accepts query argument", () => {
28
+ const program = new Command();
29
+ configureSearchCommand(program);
30
+
31
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
32
+ const args = searchCmd?.registeredArguments ?? [];
33
+ expect(args.length).toBeGreaterThan(0);
34
+ expect(args[0]?.name()).toBe("query");
35
+ });
36
+
37
+ test("has --tags option", () => {
38
+ const program = new Command();
39
+ configureSearchCommand(program);
40
+
41
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
42
+ const opts = searchCmd?.options ?? [];
43
+ const tagsOpt = opts.find((o) => o.long === "--tags");
44
+ expect(tagsOpt).toBeDefined();
45
+ });
46
+
47
+ test("has -t short option for tags", () => {
48
+ const program = new Command();
49
+ configureSearchCommand(program);
50
+
51
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
52
+ const opts = searchCmd?.options ?? [];
53
+ const tagsOpt = opts.find((o) => o.short === "-t");
54
+ expect(tagsOpt).toBeDefined();
55
+ });
56
+
57
+ test("has --limit option", () => {
58
+ const program = new Command();
59
+ configureSearchCommand(program);
60
+
61
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
62
+ const opts = searchCmd?.options ?? [];
63
+ const limitOpt = opts.find((o) => o.long === "--limit");
64
+ expect(limitOpt).toBeDefined();
65
+ });
66
+
67
+ test("has -l short option for limit", () => {
68
+ const program = new Command();
69
+ configureSearchCommand(program);
70
+
71
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
72
+ const opts = searchCmd?.options ?? [];
73
+ const limitOpt = opts.find((o) => o.short === "-l");
74
+ expect(limitOpt).toBeDefined();
75
+ });
76
+
77
+ test("has --json option", () => {
78
+ const program = new Command();
79
+ configureSearchCommand(program);
80
+
81
+ const searchCmd = program.commands.find((cmd) => cmd.name() === "search");
82
+ const opts = searchCmd?.options ?? [];
83
+ const jsonOpt = opts.find((o) => o.long === "--json");
84
+ expect(jsonOpt).toBeDefined();
85
+ });
86
+ });
87
+
88
+ describe("search result display helpers", () => {
89
+ test("truncates long descriptions", () => {
90
+ const truncate = (text: string, max: number): string => {
91
+ if (text.length <= max) return text;
92
+ return `${text.slice(0, max - 3)}...`;
93
+ };
94
+
95
+ expect(truncate("short", 50)).toBe("short");
96
+ expect(truncate("a".repeat(60), 50)).toBe(`${"a".repeat(47)}...`);
97
+ });
98
+
99
+ test("formats download count", () => {
100
+ const formatDownloads = (downloads: number): string => {
101
+ if (downloads >= 1_000_000) return `${(downloads / 1_000_000).toFixed(1)}M`;
102
+ if (downloads >= 1_000) return `${(downloads / 1_000).toFixed(1)}k`;
103
+ return downloads.toString();
104
+ };
105
+
106
+ expect(formatDownloads(500)).toBe("500");
107
+ expect(formatDownloads(1500)).toBe("1.5k");
108
+ expect(formatDownloads(1_500_000)).toBe("1.5M");
109
+ });
110
+ });
111
+
112
+ describe("tag parsing", () => {
113
+ test("splits comma-separated tags", () => {
114
+ const tagsInput = "cli,testing,automation";
115
+ const tags = tagsInput.split(",").map((t) => t.trim());
116
+ expect(tags).toEqual(["cli", "testing", "automation"]);
117
+ });
118
+
119
+ test("trims whitespace from tags", () => {
120
+ const tagsInput = "cli , testing , automation";
121
+ const tags = tagsInput.split(",").map((t) => t.trim());
122
+ expect(tags).toEqual(["cli", "testing", "automation"]);
123
+ });
124
+
125
+ test("handles single tag", () => {
126
+ const tagsInput = "cli";
127
+ const tags = tagsInput.split(",").map((t) => t.trim());
128
+ expect(tags).toEqual(["cli"]);
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Tests for the sign command
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { Command } from "commander";
9
+ import { configureSignCommand } from "../../src/commands/sign";
10
+
11
+ // Test fixtures directory
12
+ const FIXTURES_DIR = join(import.meta.dir, "..", "fixtures", "sign-cmd");
13
+
14
+ describe("sign command", () => {
15
+ beforeAll(() => {
16
+ mkdirSync(FIXTURES_DIR, { recursive: true });
17
+
18
+ // Create a test manifest
19
+ writeFileSync(
20
+ join(FIXTURES_DIR, "enact.yaml"),
21
+ `enact: "2.0.0"
22
+ name: "test/sign-tool"
23
+ version: "1.0.0"
24
+ description: "A test tool for signing"
25
+ from: "alpine:latest"
26
+ command: "echo hello"
27
+ `
28
+ );
29
+ });
30
+
31
+ afterAll(() => {
32
+ if (existsSync(FIXTURES_DIR)) {
33
+ rmSync(FIXTURES_DIR, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ describe("command configuration", () => {
38
+ test("configures sign command on program", () => {
39
+ const program = new Command();
40
+ configureSignCommand(program);
41
+
42
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
43
+ expect(signCmd).toBeDefined();
44
+ });
45
+
46
+ test("has correct description", () => {
47
+ const program = new Command();
48
+ configureSignCommand(program);
49
+
50
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
51
+ expect(signCmd?.description()).toBe(
52
+ "Cryptographically sign a tool and submit attestation to registry"
53
+ );
54
+ });
55
+
56
+ test("accepts path argument", () => {
57
+ const program = new Command();
58
+ configureSignCommand(program);
59
+
60
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
61
+ const args = signCmd?.registeredArguments ?? [];
62
+ expect(args.length).toBeGreaterThanOrEqual(1);
63
+ expect(args[0]?.name()).toBe("path");
64
+ });
65
+
66
+ test("has --identity option", () => {
67
+ const program = new Command();
68
+ configureSignCommand(program);
69
+
70
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
71
+ const opts = signCmd?.options ?? [];
72
+ const identityOpt = opts.find((o) => o.long === "--identity");
73
+ expect(identityOpt).toBeDefined();
74
+ });
75
+
76
+ test("has --output option", () => {
77
+ const program = new Command();
78
+ configureSignCommand(program);
79
+
80
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
81
+ const opts = signCmd?.options ?? [];
82
+ const outputOpt = opts.find((o) => o.long === "--output");
83
+ expect(outputOpt).toBeDefined();
84
+ });
85
+
86
+ test("has --dry-run option", () => {
87
+ const program = new Command();
88
+ configureSignCommand(program);
89
+
90
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
91
+ const opts = signCmd?.options ?? [];
92
+ const dryRunOpt = opts.find((o) => o.long === "--dry-run");
93
+ expect(dryRunOpt).toBeDefined();
94
+ });
95
+
96
+ test("has --verbose option", () => {
97
+ const program = new Command();
98
+ configureSignCommand(program);
99
+
100
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
101
+ const opts = signCmd?.options ?? [];
102
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
103
+ expect(verboseOpt).toBeDefined();
104
+ });
105
+
106
+ test("has --json option", () => {
107
+ const program = new Command();
108
+ configureSignCommand(program);
109
+
110
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
111
+ const opts = signCmd?.options ?? [];
112
+ const jsonOpt = opts.find((o) => o.long === "--json");
113
+ expect(jsonOpt).toBeDefined();
114
+ });
115
+
116
+ test("has --local option", () => {
117
+ const program = new Command();
118
+ configureSignCommand(program);
119
+
120
+ const signCmd = program.commands.find((cmd) => cmd.name() === "sign");
121
+ const opts = signCmd?.options ?? [];
122
+ const localOpt = opts.find((o) => o.long === "--local");
123
+ expect(localOpt).toBeDefined();
124
+ });
125
+ });
126
+
127
+ describe("signing workflow", () => {
128
+ test("default bundle filename is .sigstore-bundle.json", () => {
129
+ // This is defined in the module
130
+ const expectedFilename = ".sigstore-bundle.json";
131
+ expect(expectedFilename).toBe(".sigstore-bundle.json");
132
+ });
133
+
134
+ test("signing requires OIDC authentication", () => {
135
+ // Sigstore keyless signing requires OIDC
136
+ const requiresOIDC = true;
137
+ expect(requiresOIDC).toBe(true);
138
+ });
139
+
140
+ test("signing creates in-toto attestation", () => {
141
+ // The sign command creates in-toto attestations
142
+ const attestationType = "https://in-toto.io/Statement/v1";
143
+ expect(attestationType).toContain("in-toto");
144
+ });
145
+ });
146
+
147
+ describe("Sigstore integration", () => {
148
+ test("uses Fulcio for certificate issuance", () => {
149
+ const fulcioUrl = "https://fulcio.sigstore.dev";
150
+ expect(fulcioUrl).toContain("fulcio");
151
+ });
152
+
153
+ test("uses Rekor for transparency logging", () => {
154
+ const rekorUrl = "https://rekor.sigstore.dev";
155
+ expect(rekorUrl).toContain("rekor");
156
+ });
157
+
158
+ test("creates Enact tool attestation predicate", () => {
159
+ const predicateType = "https://enact.tools/attestation/tool/v1";
160
+ expect(predicateType).toContain("enact.tools");
161
+ expect(predicateType).toContain("tool");
162
+ });
163
+ });
164
+ });