@orderful/droid 0.16.0 → 0.17.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 (83) hide show
  1. package/.claude/CLAUDE.md +3 -43
  2. package/AGENTS.md +75 -0
  3. package/CHANGELOG.md +18 -0
  4. package/bun.lock +12 -0
  5. package/dist/bin/droid.js +3064 -54
  6. package/dist/commands/tui/components/Badge.test.d.ts +2 -0
  7. package/dist/commands/tui/components/Badge.test.d.ts.map +1 -0
  8. package/dist/commands/tui/components/TabBar.test.d.ts +2 -0
  9. package/dist/commands/tui/components/TabBar.test.d.ts.map +1 -0
  10. package/dist/index.js +952 -6
  11. package/package.json +7 -2
  12. package/playwright.config.ts +20 -0
  13. package/scripts/build.ts +78 -0
  14. package/scripts/screenshot-tui.ts +126 -0
  15. package/src/commands/tui/components/Badge.test.tsx +94 -0
  16. package/src/commands/tui/components/TabBar.test.tsx +39 -0
  17. package/tests/e2e/tui.spec.ts +128 -0
  18. package/dist/bin/droid.js.map +0 -1
  19. package/dist/commands/config.js +0 -67
  20. package/dist/commands/config.js.map +0 -1
  21. package/dist/commands/install.js +0 -45
  22. package/dist/commands/install.js.map +0 -1
  23. package/dist/commands/setup.js +0 -269
  24. package/dist/commands/setup.js.map +0 -1
  25. package/dist/commands/skills.js +0 -144
  26. package/dist/commands/skills.js.map +0 -1
  27. package/dist/commands/tui/components/Badge.js +0 -29
  28. package/dist/commands/tui/components/Badge.js.map +0 -1
  29. package/dist/commands/tui/components/Markdown.js +0 -42
  30. package/dist/commands/tui/components/Markdown.js.map +0 -1
  31. package/dist/commands/tui/components/SettingsDetails.js +0 -11
  32. package/dist/commands/tui/components/SettingsDetails.js.map +0 -1
  33. package/dist/commands/tui/components/TabBar.js +0 -7
  34. package/dist/commands/tui/components/TabBar.js.map +0 -1
  35. package/dist/commands/tui/components/ToolDetails.js +0 -35
  36. package/dist/commands/tui/components/ToolDetails.js.map +0 -1
  37. package/dist/commands/tui/components/ToolItem.js +0 -11
  38. package/dist/commands/tui/components/ToolItem.js.map +0 -1
  39. package/dist/commands/tui/constants.js +0 -17
  40. package/dist/commands/tui/constants.js.map +0 -1
  41. package/dist/commands/tui/hooks/useAppUpdate.js +0 -52
  42. package/dist/commands/tui/hooks/useAppUpdate.js.map +0 -1
  43. package/dist/commands/tui/hooks/useToolUpdates.js +0 -77
  44. package/dist/commands/tui/hooks/useToolUpdates.js.map +0 -1
  45. package/dist/commands/tui/types.js +0 -2
  46. package/dist/commands/tui/types.js.map +0 -1
  47. package/dist/commands/tui/views/ReadmeViewer.js +0 -56
  48. package/dist/commands/tui/views/ReadmeViewer.js.map +0 -1
  49. package/dist/commands/tui/views/SetupScreen.js +0 -114
  50. package/dist/commands/tui/views/SetupScreen.js.map +0 -1
  51. package/dist/commands/tui/views/SkillConfigScreen.js +0 -148
  52. package/dist/commands/tui/views/SkillConfigScreen.js.map +0 -1
  53. package/dist/commands/tui/views/ToolExplorer.js +0 -86
  54. package/dist/commands/tui/views/ToolExplorer.js.map +0 -1
  55. package/dist/commands/tui/views/ToolUpdatePrompt.js +0 -38
  56. package/dist/commands/tui/views/ToolUpdatePrompt.js.map +0 -1
  57. package/dist/commands/tui/views/WelcomeScreen.js +0 -46
  58. package/dist/commands/tui/views/WelcomeScreen.js.map +0 -1
  59. package/dist/commands/tui.js +0 -307
  60. package/dist/commands/tui.js.map +0 -1
  61. package/dist/commands/uninstall.js +0 -26
  62. package/dist/commands/uninstall.js.map +0 -1
  63. package/dist/commands/update.js +0 -45
  64. package/dist/commands/update.js.map +0 -1
  65. package/dist/index.js.map +0 -1
  66. package/dist/lib/agents.js +0 -248
  67. package/dist/lib/agents.js.map +0 -1
  68. package/dist/lib/config.js +0 -196
  69. package/dist/lib/config.js.map +0 -1
  70. package/dist/lib/platforms.js +0 -52
  71. package/dist/lib/platforms.js.map +0 -1
  72. package/dist/lib/quotes.js +0 -24
  73. package/dist/lib/quotes.js.map +0 -1
  74. package/dist/lib/skill-config.js +0 -80
  75. package/dist/lib/skill-config.js.map +0 -1
  76. package/dist/lib/skills.js +0 -582
  77. package/dist/lib/skills.js.map +0 -1
  78. package/dist/lib/tools.js +0 -145
  79. package/dist/lib/tools.js.map +0 -1
  80. package/dist/lib/types.js +0 -50
  81. package/dist/lib/types.js.map +0 -1
  82. package/dist/lib/version.js +0 -100
  83. package/dist/lib/version.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,11 +8,14 @@
8
8
  },
9
9
  "main": "dist/index.js",
10
10
  "scripts": {
11
- "build": "eslint src --ext .ts,.tsx && tsc && cp -r src/tools dist/",
11
+ "build": "bun run lint && bun scripts/build.ts",
12
12
  "dev": "tsc --watch",
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,78 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Build script using esbuild for proper ESM bundling
4
+ *
5
+ * This bundles the CLI entry point so Node ESM can resolve imports
6
+ * without explicit .js extensions (which TypeScript doesn't add).
7
+ */
8
+
9
+ import { build, type Plugin } from 'esbuild';
10
+ import { execSync } from 'child_process';
11
+ import { cpSync, rmSync } from 'fs';
12
+
13
+ // Plugin to stub out optional dependencies that may not be installed
14
+ const stubOptionalDeps: Plugin = {
15
+ name: 'stub-optional-deps',
16
+ setup(build) {
17
+ // Stub react-devtools-core (optional Ink dependency for debugging)
18
+ build.onResolve({ filter: /^react-devtools-core$/ }, () => ({
19
+ path: 'react-devtools-core',
20
+ namespace: 'stub',
21
+ }));
22
+ build.onLoad({ filter: /.*/, namespace: 'stub' }, () => ({
23
+ contents: 'export default undefined; export const connectToDevTools = () => {};',
24
+ }));
25
+ },
26
+ };
27
+
28
+ async function main() {
29
+ // Clean dist
30
+ rmSync('dist', { recursive: true, force: true });
31
+
32
+ // Run TypeScript for type checking and declaration files
33
+ console.log('Running TypeScript type check...');
34
+ execSync('tsc --emitDeclarationOnly', { stdio: 'inherit' });
35
+
36
+ // Bundle CLI with esbuild
37
+ console.log('Bundling CLI with esbuild...');
38
+ await build({
39
+ entryPoints: ['src/bin/droid.ts'],
40
+ bundle: true,
41
+ platform: 'node',
42
+ target: 'node18',
43
+ format: 'esm',
44
+ outfile: 'dist/bin/droid.js',
45
+ // Note: shebang is preserved from source file, no banner needed
46
+ plugins: [stubOptionalDeps],
47
+ // Keep npm dependencies external - they'll be installed via package.json
48
+ // Only bundle our own code to fix ESM import resolution
49
+ packages: 'external',
50
+ // Handle React JSX
51
+ jsx: 'automatic',
52
+ jsxImportSource: 'react',
53
+ });
54
+
55
+ // Bundle index.ts for library exports
56
+ console.log('Bundling library exports...');
57
+ await build({
58
+ entryPoints: ['src/index.ts'],
59
+ bundle: true,
60
+ platform: 'node',
61
+ target: 'node18',
62
+ format: 'esm',
63
+ outfile: 'dist/index.js',
64
+ plugins: [stubOptionalDeps],
65
+ packages: 'external',
66
+ });
67
+
68
+ // Copy tools directory
69
+ console.log('Copying tools...');
70
+ cpSync('src/tools', 'dist/tools', { recursive: true });
71
+
72
+ console.log('Build complete!');
73
+ }
74
+
75
+ main().catch((err) => {
76
+ console.error(err);
77
+ process.exit(1);
78
+ });
@@ -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
+ });
@@ -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
+ });
@@ -1 +0,0 @@
1
- {"version":3,"file":"droid.js","sourceRoot":"","sources":["../../src/bin/droid.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAE7B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,oCAAoC,CAAC;KACjD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,4BAA4B,CAAC;KACzC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC;KAC7C,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,CAAC;KACxD,MAAM,CAAC,uBAAuB,EAAE,oBAAoB,CAAC;KACrD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,cAAc,CAAC,CAAC;AAE1B,OAAO;KACJ,OAAO,CAAC,kBAAkB,CAAC;KAC3B,WAAW,CAAC,kBAAkB,CAAC;KAC/B,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE5B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,SAAS,EAAE,mBAAmB,CAAC;KACtC,MAAM,CAAC,OAAO,EAAE,qBAAqB,CAAC;KACtC,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;KAC5C,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,UAAU,CAAC,CAAC;AAEtB,qCAAqC;AACrC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC9B,UAAU,EAAE,CAAC;AACf,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC"}
@@ -1,67 +0,0 @@
1
- import chalk from 'chalk';
2
- import { execSync } from 'child_process';
3
- import { loadConfig, getConfigPath, getConfigValue, setConfigValue, configExists, } from '../lib/config';
4
- export async function configCommand(options) {
5
- // Handle --edit flag
6
- if (options.edit) {
7
- const configPath = getConfigPath();
8
- const editor = process.env.EDITOR || 'vim';
9
- if (!configExists()) {
10
- console.log(chalk.yellow('No config file exists yet. Run `droid setup` first.'));
11
- return;
12
- }
13
- console.log(chalk.gray(`Opening ${configPath} in ${editor}...`));
14
- try {
15
- execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
16
- }
17
- catch {
18
- console.error(chalk.red('Failed to open editor'));
19
- }
20
- return;
21
- }
22
- // Handle --get flag
23
- if (options.get) {
24
- const value = getConfigValue(options.get);
25
- if (value === undefined) {
26
- console.log(chalk.yellow(`Config key '${options.get}' not found`));
27
- process.exit(1);
28
- }
29
- if (typeof value === 'object') {
30
- console.log(JSON.stringify(value, null, 2));
31
- }
32
- else {
33
- console.log(value);
34
- }
35
- return;
36
- }
37
- // Handle --set flag
38
- if (options.set) {
39
- const match = options.set.match(/^([^=]+)=(.*)$/);
40
- if (!match) {
41
- console.error(chalk.red('Invalid format. Use: --set key=value'));
42
- process.exit(1);
43
- }
44
- const [, key, rawValue] = match;
45
- // Try to parse as JSON, otherwise use as string
46
- let value;
47
- try {
48
- value = JSON.parse(rawValue);
49
- }
50
- catch {
51
- value = rawValue;
52
- }
53
- setConfigValue(key, value);
54
- console.log(chalk.green(`āœ“ Set ${key} = ${JSON.stringify(value)}`));
55
- return;
56
- }
57
- // Default: show full config
58
- if (!configExists()) {
59
- console.log(chalk.yellow('No config file exists yet. Run `droid setup` first.'));
60
- return;
61
- }
62
- const config = loadConfig();
63
- console.log(chalk.bold('\nšŸ“‹ Droid Config\n'));
64
- console.log(chalk.gray(`Path: ${getConfigPath()}\n`));
65
- console.log(JSON.stringify(config, null, 2));
66
- }
67
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"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"}
@@ -1,45 +0,0 @@
1
- import chalk from 'chalk';
2
- import { installSkill } from '../lib/skills';
3
- import { getBundledTools, isToolInstalled } from '../lib/tools';
4
- import { promptForSkillConfig } from '../lib/skill-config';
5
- export async function installCommand(toolName) {
6
- // Check if tool exists
7
- const tools = getBundledTools();
8
- const tool = tools.find((t) => t.name === toolName);
9
- if (!tool) {
10
- console.error(chalk.red(`\nāœ— Tool '${toolName}' not found`));
11
- console.log(chalk.gray('\nAvailable tools:'));
12
- for (const t of tools) {
13
- console.log(chalk.gray(` - ${t.name}`));
14
- }
15
- process.exit(1);
16
- }
17
- // Check if already installed
18
- if (isToolInstalled(toolName)) {
19
- console.log(chalk.yellow(`\n⚠ Tool '${toolName}' is already installed`));
20
- console.log(chalk.gray('Use `droid update` to update it, or uninstall first.'));
21
- return;
22
- }
23
- console.log(chalk.bold(`\nšŸ¤– Installing ${toolName}...\n`));
24
- // Install via primary skill
25
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || toolName;
26
- const result = installSkill(primarySkill);
27
- if (result.success) {
28
- console.log(chalk.green(`āœ“ Installed ${toolName}`));
29
- // Check if tool has configurable options
30
- if (tool.config_schema && Object.keys(tool.config_schema).length > 0) {
31
- // If any config option lacks a default, go straight to config (it's required)
32
- const hasRequiredConfig = Object.values(tool.config_schema).some((option) => option.default === undefined);
33
- await promptForSkillConfig(primarySkill, tool.config_schema, !hasRequiredConfig);
34
- }
35
- // Show next steps
36
- console.log(chalk.gray('\nNext steps:'));
37
- console.log(chalk.gray(' - Run your AI tool to start using it'));
38
- console.log(chalk.gray(` - Reconfigure anytime with \`droid\` → Configure`));
39
- }
40
- else {
41
- console.error(chalk.red(`āœ— ${result.message}`));
42
- process.exit(1);
43
- }
44
- }
45
- //# sourceMappingURL=install.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}