@orderful/droid 0.16.1 → 0.17.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.
- package/CHANGELOG.md +21 -0
- package/bun.lock +12 -0
- package/dist/commands/tui/components/Badge.test.d.ts +2 -0
- package/dist/commands/tui/components/Badge.test.d.ts.map +1 -0
- package/dist/commands/tui/components/TabBar.test.d.ts +2 -0
- package/dist/commands/tui/components/TabBar.test.d.ts.map +1 -0
- package/dist/tools/brain/TOOL.yaml +1 -1
- package/dist/tools/brain/skills/brain/SKILL.md +2 -2
- package/dist/tools/brain/skills/brain-obsidian/SKILL.md +1 -1
- package/dist/tools/coach/TOOL.yaml +1 -1
- package/dist/tools/coach/skills/coach/SKILL.md +1 -1
- package/dist/tools/code-review/TOOL.yaml +1 -1
- package/dist/tools/code-review/skills/code-review/SKILL.md +1 -1
- package/dist/tools/comments/TOOL.yaml +1 -1
- package/dist/tools/comments/skills/comments/SKILL.md +1 -1
- package/dist/tools/droid/TOOL.yaml +1 -1
- package/dist/tools/droid/skills/droid/SKILL.md +1 -1
- package/dist/tools/project/TOOL.yaml +1 -1
- package/dist/tools/project/skills/project/SKILL.md +1 -1
- package/package.json +6 -1
- package/playwright.config.ts +20 -0
- package/scripts/screenshot-tui.ts +126 -0
- package/src/commands/tui/components/Badge.test.tsx +94 -0
- package/src/commands/tui/components/TabBar.test.tsx +39 -0
- package/src/tools/brain/TOOL.yaml +1 -1
- package/src/tools/brain/skills/brain/SKILL.md +2 -2
- package/src/tools/brain/skills/brain-obsidian/SKILL.md +1 -1
- package/src/tools/coach/TOOL.yaml +1 -1
- package/src/tools/coach/skills/coach/SKILL.md +1 -1
- package/src/tools/code-review/TOOL.yaml +1 -1
- package/src/tools/code-review/skills/code-review/SKILL.md +1 -1
- package/src/tools/comments/TOOL.yaml +1 -1
- package/src/tools/comments/skills/comments/SKILL.md +1 -1
- package/src/tools/droid/TOOL.yaml +1 -1
- package/src/tools/droid/skills/droid/SKILL.md +1 -1
- package/src/tools/project/TOOL.yaml +1 -1
- package/src/tools/project/skills/project/SKILL.md +1 -1
- package/tests/e2e/tui.spec.ts +128 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @orderful/droid
|
|
2
2
|
|
|
3
|
+
## 0.17.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#90](https://github.com/Orderful/droid/pull/90) [`30abc1c`](https://github.com/Orderful/droid/commit/30abc1cd699a41ca7bbb699586b27246e99762e3) Thanks [@frytyler](https://github.com/frytyler)! - Improve /brain check to delegate to /comments check when available
|
|
8
|
+
|
|
9
|
+
- [#89](https://github.com/Orderful/droid/pull/89) [`624518e`](https://github.com/Orderful/droid/commit/624518ec2ad0b9ac303ead7ae39684b8db73459e) Thanks [@frytyler](https://github.com/frytyler)! - Improve skill descriptions with "Use when" pattern for better Claude matching
|
|
10
|
+
|
|
11
|
+
Updated all skill frontmatter descriptions to follow the official Claude Code pattern:
|
|
12
|
+
`{What it does}. Use when {scenarios}. User prompts like {examples}.`
|
|
13
|
+
|
|
14
|
+
This helps Claude better recognise when to invoke skills based on user intent.
|
|
15
|
+
|
|
16
|
+
Bumps tool versions: brain 0.2.1, coach 0.1.1, code-review 0.1.1, comments 0.2.4, droid 0.2.1, project 0.1.3
|
|
17
|
+
|
|
18
|
+
## 0.17.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- [#86](https://github.com/Orderful/droid/pull/86) [`af57231`](https://github.com/Orderful/droid/commit/af5723101f301dc7d5357558325bd9d6997259f7) Thanks [@frytyler](https://github.com/frytyler)! - Add TUI testing infrastructure with ink-testing-library for component tests and Playwright + ttyd for E2E visual testing
|
|
23
|
+
|
|
3
24
|
## 0.16.1
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/bun.lock
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@changesets/changelog-github": "^0.5.2",
|
|
20
20
|
"@changesets/cli": "^2.29.8",
|
|
21
|
+
"@playwright/test": "^1.57.0",
|
|
21
22
|
"@types/bun": "latest",
|
|
22
23
|
"@types/ink-text-input": "^2.0.5",
|
|
23
24
|
"@types/inquirer": "^9.0.7",
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
"@typescript-eslint/parser": "^8.49.0",
|
|
28
29
|
"esbuild": "^0.27.1",
|
|
29
30
|
"eslint": "^8.57.0",
|
|
31
|
+
"ink-testing-library": "^4.0.0",
|
|
30
32
|
"prettier": "^3.2.5",
|
|
31
33
|
"typescript": "^5.4.2",
|
|
32
34
|
},
|
|
@@ -155,6 +157,8 @@
|
|
|
155
157
|
|
|
156
158
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
|
157
159
|
|
|
160
|
+
"@playwright/test": ["@playwright/test@1.57.0", "", { "dependencies": { "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" } }, "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA=="],
|
|
161
|
+
|
|
158
162
|
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
|
|
159
163
|
|
|
160
164
|
"@types/ink": ["@types/ink@0.5.2", "", { "dependencies": { "@types/node": "*", "@types/prop-types": "*" } }, "sha512-yEuhWTRMXJkIWiaM58c5kuRot5HFccv9kjjgy0fBZG0+tYb+sMBaxHNPVqyZXspGGlmwDOQ1d3BsygWpWlW17w=="],
|
|
@@ -341,6 +345,8 @@
|
|
|
341
345
|
|
|
342
346
|
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
|
343
347
|
|
|
348
|
+
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
|
349
|
+
|
|
344
350
|
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
|
345
351
|
|
|
346
352
|
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
|
@@ -379,6 +385,8 @@
|
|
|
379
385
|
|
|
380
386
|
"ink-select-input": ["ink-select-input@6.2.0", "", { "dependencies": { "figures": "^6.1.0", "to-rotated": "^1.0.0" }, "peerDependencies": { "ink": ">=5.0.0", "react": ">=18.0.0" } }, "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ=="],
|
|
381
387
|
|
|
388
|
+
"ink-testing-library": ["ink-testing-library@4.0.0", "", { "peerDependencies": { "@types/react": ">=18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q=="],
|
|
389
|
+
|
|
382
390
|
"ink-text-input": ["ink-text-input@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="],
|
|
383
391
|
|
|
384
392
|
"inquirer": ["inquirer@9.3.8", "", { "dependencies": { "@inquirer/external-editor": "^1.0.2", "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w=="],
|
|
@@ -487,6 +495,10 @@
|
|
|
487
495
|
|
|
488
496
|
"pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
|
|
489
497
|
|
|
498
|
+
"playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
|
|
499
|
+
|
|
500
|
+
"playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
|
|
501
|
+
|
|
490
502
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
|
491
503
|
|
|
492
504
|
"prettier": ["prettier@3.7.4", "", { "bin": "bin/prettier.cjs" }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Badge.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/tui/components/Badge.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TabBar.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/tui/components/TabBar.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: brain
|
|
2
2
|
description: "Your scratchpad (or brain) - a collaborative space for planning and research. Create docs with /brain plan, /brain research, or /brain review. Use @mentions for async discussion. Docs persist across sessions."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain
|
|
3
|
-
description: "
|
|
3
|
+
description: "Collaborative scratchpad for planning and research. Use when planning a feature, exploring a problem, or capturing thinking that should persist across sessions. User prompts like 'let's think through', 'open a scratchpad', 'plan this out', 'use our brain'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/brain/**/*.md"
|
|
6
6
|
alwaysApply: false
|
|
@@ -148,7 +148,7 @@ Full procedure: `references/workflows.md` § Adding
|
|
|
148
148
|
|
|
149
149
|
**Trigger:** `/brain check`
|
|
150
150
|
|
|
151
|
-
**TLDR:**
|
|
151
|
+
**TLDR:** Invoke `/comments check {active_doc_path}`. If unavailable, find `> @droid` comments and address each (preserve original, add response below).
|
|
152
152
|
|
|
153
153
|
Full procedure: `references/workflows.md` § Checking
|
|
154
154
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain-obsidian
|
|
3
|
-
description: "Obsidian extension for brain skill
|
|
3
|
+
description: "Obsidian extension for brain skill with YAML frontmatter, wikilinks, and PARA folder organization. Use when your brain docs live in an Obsidian vault."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/brain/**/*.md"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: coach
|
|
2
2
|
description: "Learning-mode AI assistance - AI as coach, not crutch. Use /coach plan for co-authored planning, /coach scaffold for structure with hints, /coach review for Socratic questions."
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coach
|
|
3
|
-
description: "Learning-mode AI assistance -
|
|
3
|
+
description: "Learning-mode AI assistance - scaffolds don't implement, questions don't fix. Use when learning a new codebase, wanting to understand deeply, or building skills to retain. User prompts like 'coach me on', 'help me think through', 'I want to learn how to', 'don't just give me the answer'."
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
allowed-tools: Read, Grep, Glob
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-review
|
|
3
|
-
description: "Comprehensive code review using specialized agents.
|
|
3
|
+
description: "Comprehensive code review using specialized agents. Use when reviewing PRs, checking staged changes, or analysing code quality. User prompts like 'review this PR', 'check my changes', 'review before I commit'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: comments
|
|
2
2
|
description: "Enable inline conversations using @droid/@user markers. Tag @droid to ask the AI, AI responds with @{your-name}. Use /comments check to address markers, /comments cleanup to remove resolved threads. Ideal for code review notes and async collaboration."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.4
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: comments
|
|
3
|
-
description: "
|
|
3
|
+
description: "Inline conversations using @droid/@user markers in any file. Use when leaving async questions in code or having discussion that should stay near the code. User prompts like 'check for comments', 'address the @droid markers', 'clean up resolved comments'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: droid
|
|
2
2
|
description: "Core droid meta-skill for update awareness and tool discovery. Checks for updates and helps users find the right tools."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
# System tool - always stays current regardless of auto-update settings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: droid
|
|
3
|
-
description: "Core droid meta-skill for
|
|
3
|
+
description: "Core droid meta-skill for updates and tool discovery. Use when checking for droid updates or discovering available tools. User prompts like 'any droid updates?', 'what tools do I have?', 'is droid up to date?'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project
|
|
3
|
-
description: "
|
|
3
|
+
description: "Persistent project context for AI memory across sessions. Use when working on multi-session features, refactors, or any work that benefits from accumulated context. User prompts like 'load the project', 'update project context', 'what's the current project?'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/PROJECT.md"
|
|
6
6
|
alwaysApply: false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orderful/droid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"start": "bun dist/bin/droid.js",
|
|
14
14
|
"test": "bun test src/",
|
|
15
15
|
"test:watch": "bun test src/ --watch",
|
|
16
|
+
"test:tui": "bun test src/commands/tui/",
|
|
17
|
+
"test:e2e": "playwright test",
|
|
18
|
+
"screenshot": "bun scripts/screenshot-tui.ts",
|
|
16
19
|
"lint": "eslint src --ext .ts,.tsx",
|
|
17
20
|
"format": "prettier --write 'src/**/*.ts'",
|
|
18
21
|
"changeset": "changeset",
|
|
@@ -54,6 +57,7 @@
|
|
|
54
57
|
"devDependencies": {
|
|
55
58
|
"@changesets/changelog-github": "^0.5.2",
|
|
56
59
|
"@changesets/cli": "^2.29.8",
|
|
60
|
+
"@playwright/test": "^1.57.0",
|
|
57
61
|
"@types/bun": "latest",
|
|
58
62
|
"@types/ink-text-input": "^2.0.5",
|
|
59
63
|
"@types/inquirer": "^9.0.7",
|
|
@@ -63,6 +67,7 @@
|
|
|
63
67
|
"@typescript-eslint/parser": "^8.49.0",
|
|
64
68
|
"esbuild": "^0.27.1",
|
|
65
69
|
"eslint": "^8.57.0",
|
|
70
|
+
"ink-testing-library": "^4.0.0",
|
|
66
71
|
"prettier": "^3.2.5",
|
|
67
72
|
"typescript": "^5.4.2"
|
|
68
73
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: './tests/e2e',
|
|
5
|
+
timeout: 30000,
|
|
6
|
+
expect: {
|
|
7
|
+
timeout: 5000,
|
|
8
|
+
},
|
|
9
|
+
fullyParallel: false, // ttyd can only serve one session at a time
|
|
10
|
+
forbidOnly: !!process.env.CI,
|
|
11
|
+
retries: process.env.CI ? 2 : 0,
|
|
12
|
+
workers: 1, // Single worker since ttyd serves one session
|
|
13
|
+
reporter: 'html',
|
|
14
|
+
use: {
|
|
15
|
+
baseURL: 'http://localhost:7681',
|
|
16
|
+
trace: 'on-first-retry',
|
|
17
|
+
screenshot: 'only-on-failure',
|
|
18
|
+
},
|
|
19
|
+
outputDir: 'tests/screenshots',
|
|
20
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Take a screenshot of the TUI at a specific state
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bun scripts/screenshot-tui.ts # Screenshot welcome screen
|
|
7
|
+
* bun scripts/screenshot-tui.ts enter # Screenshot after pressing enter
|
|
8
|
+
* bun scripts/screenshot-tui.ts enter right # Screenshot Settings tab
|
|
9
|
+
* bun scripts/screenshot-tui.ts -o my-screenshot # Custom output name
|
|
10
|
+
*
|
|
11
|
+
* Actions:
|
|
12
|
+
* enter, right, left, up, down, escape, tab
|
|
13
|
+
* wait:500 (wait 500ms)
|
|
14
|
+
* type:hello (type "hello")
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { chromium } from 'playwright';
|
|
18
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
21
|
+
|
|
22
|
+
const SCREENSHOTS_DIR = 'tests/screenshots';
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
// Parse output name
|
|
28
|
+
let outputName = 'screenshot';
|
|
29
|
+
const outputIndex = args.indexOf('-o');
|
|
30
|
+
if (outputIndex !== -1 && args[outputIndex + 1]) {
|
|
31
|
+
outputName = args[outputIndex + 1];
|
|
32
|
+
args.splice(outputIndex, 2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Remaining args are actions
|
|
36
|
+
const actions = args.length > 0 ? args : [];
|
|
37
|
+
|
|
38
|
+
// Ensure screenshots directory exists
|
|
39
|
+
if (!existsSync(SCREENSHOTS_DIR)) {
|
|
40
|
+
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Start ttyd
|
|
44
|
+
console.log('Starting ttyd...');
|
|
45
|
+
const distPath = join(process.cwd(), 'dist/bin/droid.js');
|
|
46
|
+
|
|
47
|
+
if (!existsSync(distPath)) {
|
|
48
|
+
console.error('Error: dist/bin/droid.js not found. Run `bun run build` first.');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ttyd: ChildProcess = spawn('ttyd', ['-W', '-p', '7681', 'node', distPath], {
|
|
53
|
+
stdio: 'ignore',
|
|
54
|
+
detached: true,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Wait for ttyd to start
|
|
58
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
console.log('Launching browser...');
|
|
62
|
+
const browser = await chromium.launch();
|
|
63
|
+
const page = await browser.newPage();
|
|
64
|
+
|
|
65
|
+
await page.goto('http://localhost:7681');
|
|
66
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
67
|
+
|
|
68
|
+
// Wait for initial render
|
|
69
|
+
await page.waitForTimeout(1000);
|
|
70
|
+
|
|
71
|
+
// Focus the terminal - click the screen first, then focus the textarea
|
|
72
|
+
await page.click('.xterm-screen');
|
|
73
|
+
await page.waitForTimeout(100);
|
|
74
|
+
|
|
75
|
+
const textarea = page.locator('textarea.xterm-helper-textarea');
|
|
76
|
+
if ((await textarea.count()) > 0) {
|
|
77
|
+
await textarea.click();
|
|
78
|
+
await page.waitForTimeout(100);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Execute actions
|
|
82
|
+
for (const action of actions) {
|
|
83
|
+
console.log(` Action: ${action}`);
|
|
84
|
+
|
|
85
|
+
if (action.startsWith('wait:')) {
|
|
86
|
+
const ms = parseInt(action.slice(5), 10);
|
|
87
|
+
await page.waitForTimeout(ms);
|
|
88
|
+
} else if (action.startsWith('type:')) {
|
|
89
|
+
const text = action.slice(5);
|
|
90
|
+
await page.keyboard.type(text);
|
|
91
|
+
} else {
|
|
92
|
+
// Key press
|
|
93
|
+
const keyMap: Record<string, string> = {
|
|
94
|
+
enter: 'Enter',
|
|
95
|
+
right: 'ArrowRight',
|
|
96
|
+
left: 'ArrowLeft',
|
|
97
|
+
up: 'ArrowUp',
|
|
98
|
+
down: 'ArrowDown',
|
|
99
|
+
escape: 'Escape',
|
|
100
|
+
esc: 'Escape',
|
|
101
|
+
tab: 'Tab',
|
|
102
|
+
};
|
|
103
|
+
const key = keyMap[action.toLowerCase()] || action;
|
|
104
|
+
await page.keyboard.press(key);
|
|
105
|
+
await page.waitForTimeout(200);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Wait for final render
|
|
110
|
+
await page.waitForTimeout(300);
|
|
111
|
+
|
|
112
|
+
// Take screenshot
|
|
113
|
+
const outputPath = `${SCREENSHOTS_DIR}/${outputName}.png`;
|
|
114
|
+
await page.screenshot({ path: outputPath });
|
|
115
|
+
console.log(`Screenshot saved: ${outputPath}`);
|
|
116
|
+
|
|
117
|
+
await browser.close();
|
|
118
|
+
} finally {
|
|
119
|
+
ttyd.kill();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main().catch((err) => {
|
|
124
|
+
console.error('Error:', err);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { render } from 'ink-testing-library';
|
|
3
|
+
import { Badge, ComponentBadges } from './Badge';
|
|
4
|
+
import type { ToolManifest } from '../../../lib/types';
|
|
5
|
+
|
|
6
|
+
describe('Badge', () => {
|
|
7
|
+
it('renders with default label from type', () => {
|
|
8
|
+
const { lastFrame } = render(<Badge type="skill" />);
|
|
9
|
+
expect(lastFrame()).toContain('Skill');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders with custom label', () => {
|
|
13
|
+
const { lastFrame } = render(<Badge type="command" label="Custom" />);
|
|
14
|
+
expect(lastFrame()).toContain('Custom');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders dimmed style', () => {
|
|
18
|
+
const { lastFrame } = render(<Badge type="agent" dimmed />);
|
|
19
|
+
expect(lastFrame()).toContain('Agent');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders selected style with leading space', () => {
|
|
23
|
+
const { lastFrame } = render(<Badge type="skill" isSelected />);
|
|
24
|
+
// Selected badges have space padding - at minimum leading space
|
|
25
|
+
expect(lastFrame()).toContain(' Skill');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders all component types', () => {
|
|
29
|
+
const types = ['skill', 'command', 'agent'] as const;
|
|
30
|
+
for (const type of types) {
|
|
31
|
+
const { lastFrame } = render(<Badge type={type} />);
|
|
32
|
+
expect(lastFrame()).toContain(type.charAt(0).toUpperCase() + type.slice(1));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('ComponentBadges', () => {
|
|
38
|
+
const mockTool: ToolManifest = {
|
|
39
|
+
name: 'test-tool',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
description: 'A test tool',
|
|
42
|
+
includes: {
|
|
43
|
+
skills: [
|
|
44
|
+
{ name: 'skill1', required: true },
|
|
45
|
+
{ name: 'skill2', required: false },
|
|
46
|
+
],
|
|
47
|
+
commands: ['cmd1'],
|
|
48
|
+
agents: [],
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
it('renders badges for skills and commands', () => {
|
|
53
|
+
const { lastFrame } = render(<ComponentBadges tool={mockTool} />);
|
|
54
|
+
expect(lastFrame()).toContain('2 skills');
|
|
55
|
+
expect(lastFrame()).toContain('1 cmd');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not render agent badge when no agents', () => {
|
|
59
|
+
const { lastFrame } = render(<ComponentBadges tool={mockTool} />);
|
|
60
|
+
expect(lastFrame()).not.toContain('agent');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('renders singular form for single item', () => {
|
|
64
|
+
const singleSkillTool: ToolManifest = {
|
|
65
|
+
...mockTool,
|
|
66
|
+
includes: { skills: [{ name: 'skill1', required: true }], commands: [], agents: [] },
|
|
67
|
+
};
|
|
68
|
+
const { lastFrame } = render(<ComponentBadges tool={singleSkillTool} />);
|
|
69
|
+
expect(lastFrame()).toContain('1 skill');
|
|
70
|
+
expect(lastFrame()).not.toContain('1 skills');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('renders compact view', () => {
|
|
74
|
+
const { lastFrame } = render(<ComponentBadges tool={mockTool} compact />);
|
|
75
|
+
// Compact view shows colored squares, not full labels
|
|
76
|
+
expect(lastFrame()).not.toContain('skills');
|
|
77
|
+
expect(lastFrame()).not.toContain('cmd');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders all badge types when tool has everything', () => {
|
|
81
|
+
const fullTool: ToolManifest = {
|
|
82
|
+
...mockTool,
|
|
83
|
+
includes: {
|
|
84
|
+
skills: [{ name: 's1', required: true }],
|
|
85
|
+
commands: ['c1'],
|
|
86
|
+
agents: ['a1'],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const { lastFrame } = render(<ComponentBadges tool={fullTool} />);
|
|
90
|
+
expect(lastFrame()).toContain('1 skill');
|
|
91
|
+
expect(lastFrame()).toContain('1 cmd');
|
|
92
|
+
expect(lastFrame()).toContain('1 agent');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { render } from 'ink-testing-library';
|
|
3
|
+
import { TabBar } from './TabBar';
|
|
4
|
+
import type { Tab } from '../types';
|
|
5
|
+
|
|
6
|
+
describe('TabBar', () => {
|
|
7
|
+
const tabs: { id: Tab; label: string }[] = [
|
|
8
|
+
{ id: 'tools', label: 'Tools' },
|
|
9
|
+
{ id: 'settings', label: 'Settings' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
it('renders all tabs', () => {
|
|
13
|
+
const { lastFrame } = render(<TabBar tabs={tabs} activeTab="tools" />);
|
|
14
|
+
expect(lastFrame()).toContain('Tools');
|
|
15
|
+
expect(lastFrame()).toContain('Settings');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('highlights the active tab', () => {
|
|
19
|
+
const { lastFrame } = render(<TabBar tabs={tabs} activeTab="tools" />);
|
|
20
|
+
// The active tab should be rendered (we can't easily check styling in text output)
|
|
21
|
+
expect(lastFrame()).toContain('Tools');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders with settings tab active', () => {
|
|
25
|
+
const { lastFrame } = render(<TabBar tabs={tabs} activeTab="settings" />);
|
|
26
|
+
expect(lastFrame()).toContain('Settings');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders empty tabs array', () => {
|
|
30
|
+
const { lastFrame } = render(<TabBar tabs={[]} activeTab="tools" />);
|
|
31
|
+
expect(lastFrame()).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders single tab', () => {
|
|
35
|
+
const singleTab: { id: Tab; label: string }[] = [{ id: 'tools', label: 'Tools' }];
|
|
36
|
+
const { lastFrame } = render(<TabBar tabs={singleTab} activeTab="tools" />);
|
|
37
|
+
expect(lastFrame()).toContain('Tools');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: brain
|
|
2
2
|
description: "Your scratchpad (or brain) - a collaborative space for planning and research. Create docs with /brain plan, /brain research, or /brain review. Use @mentions for async discussion. Docs persist across sessions."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain
|
|
3
|
-
description: "
|
|
3
|
+
description: "Collaborative scratchpad for planning and research. Use when planning a feature, exploring a problem, or capturing thinking that should persist across sessions. User prompts like 'let's think through', 'open a scratchpad', 'plan this out', 'use our brain'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/brain/**/*.md"
|
|
6
6
|
alwaysApply: false
|
|
@@ -148,7 +148,7 @@ Full procedure: `references/workflows.md` § Adding
|
|
|
148
148
|
|
|
149
149
|
**Trigger:** `/brain check`
|
|
150
150
|
|
|
151
|
-
**TLDR:**
|
|
151
|
+
**TLDR:** Invoke `/comments check {active_doc_path}`. If unavailable, find `> @droid` comments and address each (preserve original, add response below).
|
|
152
152
|
|
|
153
153
|
Full procedure: `references/workflows.md` § Checking
|
|
154
154
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain-obsidian
|
|
3
|
-
description: "Obsidian extension for brain skill
|
|
3
|
+
description: "Obsidian extension for brain skill with YAML frontmatter, wikilinks, and PARA folder organization. Use when your brain docs live in an Obsidian vault."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/brain/**/*.md"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: coach
|
|
2
2
|
description: "Learning-mode AI assistance - AI as coach, not crutch. Use /coach plan for co-authored planning, /coach scaffold for structure with hints, /coach review for Socratic questions."
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coach
|
|
3
|
-
description: "Learning-mode AI assistance -
|
|
3
|
+
description: "Learning-mode AI assistance - scaffolds don't implement, questions don't fix. Use when learning a new codebase, wanting to understand deeply, or building skills to retain. User prompts like 'coach me on', 'help me think through', 'I want to learn how to', 'don't just give me the answer'."
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
allowed-tools: Read, Grep, Glob
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-review
|
|
3
|
-
description: "Comprehensive code review using specialized agents.
|
|
3
|
+
description: "Comprehensive code review using specialized agents. Use when reviewing PRs, checking staged changes, or analysing code quality. User prompts like 'review this PR', 'check my changes', 'review before I commit'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: comments
|
|
2
2
|
description: "Enable inline conversations using @droid/@user markers. Tag @droid to ask the AI, AI responds with @{your-name}. Use /comments check to address markers, /comments cleanup to remove resolved threads. Ideal for code review notes and async collaboration."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.4
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: comments
|
|
3
|
-
description: "
|
|
3
|
+
description: "Inline conversations using @droid/@user markers in any file. Use when leaving async questions in code or having discussion that should stay near the code. User prompts like 'check for comments', 'address the @droid markers', 'clean up resolved comments'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: droid
|
|
2
2
|
description: "Core droid meta-skill for update awareness and tool discovery. Checks for updates and helps users find the right tools."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
# System tool - always stays current regardless of auto-update settings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: droid
|
|
3
|
-
description: "Core droid meta-skill for
|
|
3
|
+
description: "Core droid meta-skill for updates and tool discovery. Use when checking for droid updates or discovering available tools. User prompts like 'any droid updates?', 'what tools do I have?', 'is droid up to date?'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
6
6
|
alwaysApply: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project
|
|
3
|
-
description: "
|
|
3
|
+
description: "Persistent project context for AI memory across sessions. Use when working on multi-session features, refactors, or any work that benefits from accumulated context. User prompts like 'load the project', 'update project context', 'what's the current project?'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/PROJECT.md"
|
|
6
6
|
alwaysApply: false
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E tests for the droid TUI using ttyd + Playwright
|
|
3
|
+
*
|
|
4
|
+
* Prerequisites:
|
|
5
|
+
* - ttyd installed: `brew install ttyd`
|
|
6
|
+
* - Build droid: `bun run build`
|
|
7
|
+
*
|
|
8
|
+
* These tests start ttyd serving the TUI, then use Playwright
|
|
9
|
+
* to interact with it and take screenshots for visual verification.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { test } from '@playwright/test';
|
|
13
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
|
|
16
|
+
const distPath = join(process.cwd(), 'dist/bin/droid.js');
|
|
17
|
+
let portCounter = 7681;
|
|
18
|
+
|
|
19
|
+
// Helper to focus terminal for keyboard input
|
|
20
|
+
async function focusTerminal(page: import('@playwright/test').Page) {
|
|
21
|
+
await page.click('.xterm-screen');
|
|
22
|
+
await page.waitForTimeout(100);
|
|
23
|
+
const textarea = page.locator('textarea.xterm-helper-textarea');
|
|
24
|
+
if ((await textarea.count()) > 0) {
|
|
25
|
+
await textarea.click();
|
|
26
|
+
await page.waitForTimeout(100);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper to start ttyd on a unique port
|
|
31
|
+
async function startTtyd(): Promise<{ process: ChildProcess; port: number }> {
|
|
32
|
+
const port = portCounter++;
|
|
33
|
+
const ttyd = spawn('ttyd', ['-W', '-p', String(port), 'node', distPath], {
|
|
34
|
+
stdio: 'ignore',
|
|
35
|
+
detached: true,
|
|
36
|
+
});
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
38
|
+
return { process: ttyd, port };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Run tests serially since they share the same port
|
|
42
|
+
test.describe.configure({ mode: 'serial' });
|
|
43
|
+
|
|
44
|
+
test.describe('TUI Welcome Flow', () => {
|
|
45
|
+
let ttyd: { process: ChildProcess; port: number };
|
|
46
|
+
|
|
47
|
+
test.beforeAll(async () => {
|
|
48
|
+
ttyd = await startTtyd();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.afterAll(async () => {
|
|
52
|
+
ttyd?.process.kill();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('shows welcome screen', async ({ page }) => {
|
|
56
|
+
await page.goto(`http://localhost:${ttyd.port}`);
|
|
57
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
58
|
+
await page.waitForTimeout(2000);
|
|
59
|
+
|
|
60
|
+
await page.screenshot({ path: 'tests/screenshots/01-welcome.png' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('navigates past welcome screen', async ({ page }) => {
|
|
64
|
+
await page.goto(`http://localhost:${ttyd.port}`);
|
|
65
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
66
|
+
await page.waitForTimeout(1000);
|
|
67
|
+
|
|
68
|
+
await focusTerminal(page);
|
|
69
|
+
await page.keyboard.press('Enter');
|
|
70
|
+
await page.waitForTimeout(1000);
|
|
71
|
+
|
|
72
|
+
await page.screenshot({ path: 'tests/screenshots/02-after-welcome.png' });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test.describe('TUI Navigation', () => {
|
|
77
|
+
let ttyd: { process: ChildProcess; port: number };
|
|
78
|
+
|
|
79
|
+
test.beforeAll(async () => {
|
|
80
|
+
ttyd = await startTtyd();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test.afterAll(async () => {
|
|
84
|
+
ttyd?.process.kill();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('shows Tools tab by default', async ({ page }) => {
|
|
88
|
+
await page.goto(`http://localhost:${ttyd.port}`);
|
|
89
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
90
|
+
await page.waitForTimeout(2000);
|
|
91
|
+
|
|
92
|
+
await focusTerminal(page);
|
|
93
|
+
await page.keyboard.press('Enter');
|
|
94
|
+
await page.waitForTimeout(1000);
|
|
95
|
+
|
|
96
|
+
await page.screenshot({ path: 'tests/screenshots/03-tools-tab.png' });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('navigates to Settings tab', async ({ page }) => {
|
|
100
|
+
await page.goto(`http://localhost:${ttyd.port}`);
|
|
101
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
102
|
+
await page.waitForTimeout(2000);
|
|
103
|
+
|
|
104
|
+
await focusTerminal(page);
|
|
105
|
+
await page.keyboard.press('Enter');
|
|
106
|
+
await page.waitForTimeout(500);
|
|
107
|
+
await page.keyboard.press('ArrowRight');
|
|
108
|
+
await page.waitForTimeout(300);
|
|
109
|
+
|
|
110
|
+
await page.screenshot({ path: 'tests/screenshots/04-settings-tab.png' });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('navigates back to Tools tab', async ({ page }) => {
|
|
114
|
+
await page.goto(`http://localhost:${ttyd.port}`);
|
|
115
|
+
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
116
|
+
await page.waitForTimeout(2000);
|
|
117
|
+
|
|
118
|
+
await focusTerminal(page);
|
|
119
|
+
await page.keyboard.press('Enter');
|
|
120
|
+
await page.waitForTimeout(500);
|
|
121
|
+
await page.keyboard.press('ArrowRight');
|
|
122
|
+
await page.waitForTimeout(300);
|
|
123
|
+
await page.keyboard.press('ArrowLeft');
|
|
124
|
+
await page.waitForTimeout(300);
|
|
125
|
+
|
|
126
|
+
await page.screenshot({ path: 'tests/screenshots/05-back-to-tools.png' });
|
|
127
|
+
});
|
|
128
|
+
});
|