usecomputer 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +324 -0
  3. package/build.zig +95 -11
  4. package/build.zig.zon +5 -0
  5. package/dist/bridge-contract.test.js +61 -67
  6. package/dist/bridge.d.ts.map +1 -1
  7. package/dist/bridge.js +241 -46
  8. package/dist/cli-parsing.test.js +34 -11
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +323 -28
  11. package/dist/coord-map.d.ts +14 -0
  12. package/dist/coord-map.d.ts.map +1 -0
  13. package/dist/coord-map.js +75 -0
  14. package/dist/coord-map.test.d.ts +2 -0
  15. package/dist/coord-map.test.d.ts.map +1 -0
  16. package/dist/coord-map.test.js +157 -0
  17. package/dist/darwin-arm64/usecomputer.node +0 -0
  18. package/dist/darwin-x64/usecomputer.node +0 -0
  19. package/dist/debug-point-image.d.ts +8 -0
  20. package/dist/debug-point-image.d.ts.map +1 -0
  21. package/dist/debug-point-image.js +43 -0
  22. package/dist/debug-point-image.test.d.ts +2 -0
  23. package/dist/debug-point-image.test.d.ts.map +1 -0
  24. package/dist/debug-point-image.test.js +44 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -1
  28. package/dist/lib.d.ts +26 -0
  29. package/dist/lib.d.ts.map +1 -0
  30. package/dist/lib.js +88 -0
  31. package/dist/native-click-smoke.test.js +69 -29
  32. package/dist/native-lib.d.ts +59 -1
  33. package/dist/native-lib.d.ts.map +1 -1
  34. package/dist/terminal-table.d.ts +10 -0
  35. package/dist/terminal-table.d.ts.map +1 -0
  36. package/dist/terminal-table.js +55 -0
  37. package/dist/terminal-table.test.d.ts +2 -0
  38. package/dist/terminal-table.test.d.ts.map +1 -0
  39. package/dist/terminal-table.test.js +41 -0
  40. package/dist/types.d.ts +45 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +19 -5
  43. package/src/bridge-contract.test.ts +68 -73
  44. package/src/bridge.ts +293 -53
  45. package/src/cli-parsing.test.ts +61 -0
  46. package/src/cli.ts +393 -32
  47. package/src/coord-map.test.ts +178 -0
  48. package/src/coord-map.ts +105 -0
  49. package/src/debug-point-image.test.ts +50 -0
  50. package/src/debug-point-image.ts +69 -0
  51. package/src/index.ts +3 -1
  52. package/src/lib.ts +125 -0
  53. package/src/native-click-smoke.test.ts +81 -63
  54. package/src/native-lib.ts +39 -1
  55. package/src/terminal-table.test.ts +44 -0
  56. package/src/terminal-table.ts +88 -0
  57. package/src/types.ts +50 -0
  58. package/zig/src/lib.zig +1966 -270
  59. package/zig/src/main.zig +382 -0
  60. package/zig/src/scroll.zig +213 -0
  61. package/zig/src/window.zig +123 -0
@@ -0,0 +1,55 @@
1
+ // Generic aligned terminal table renderer for CLI command output.
2
+ export function renderAlignedTable({ rows, columns, }) {
3
+ if (columns.length === 0) {
4
+ return [];
5
+ }
6
+ const widthByColumn = columns.map((column) => {
7
+ const rowWidth = rows.reduce((maxWidth, row) => {
8
+ const width = printableWidth(column.value(row));
9
+ return Math.max(maxWidth, width);
10
+ }, 0);
11
+ return Math.max(printableWidth(column.header), rowWidth);
12
+ });
13
+ const formatCell = ({ value, width, align, }) => {
14
+ const currentWidth = printableWidth(value);
15
+ const padSize = Math.max(0, width - currentWidth);
16
+ const padding = ' '.repeat(padSize);
17
+ if (align === 'right') {
18
+ return `${padding}${value}`;
19
+ }
20
+ return `${value}${padding}`;
21
+ };
22
+ const renderRow = ({ values, }) => {
23
+ return values.map((value, index) => {
24
+ const column = columns[index];
25
+ if (!column) {
26
+ return value;
27
+ }
28
+ return formatCell({
29
+ value,
30
+ width: widthByColumn[index] ?? value.length,
31
+ align: column.align ?? 'left',
32
+ });
33
+ }).join(' ');
34
+ };
35
+ const header = renderRow({
36
+ values: columns.map((column) => {
37
+ return column.header;
38
+ }),
39
+ });
40
+ const divider = widthByColumn.map((width) => {
41
+ return '-'.repeat(width);
42
+ }).join(' ');
43
+ const lines = rows.map((row) => {
44
+ return renderRow({
45
+ values: columns.map((column) => {
46
+ return column.value(row);
47
+ }),
48
+ });
49
+ });
50
+ return [header, divider, ...lines];
51
+ }
52
+ function printableWidth(value) {
53
+ const ansiStripped = value.replace(/\u001b\[[0-9;]*m/g, '');
54
+ return ansiStripped.length;
55
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=terminal-table.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-table.test.d.ts","sourceRoot":"","sources":["../src/terminal-table.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ // Tests aligned terminal table formatting for deterministic CLI rendering.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { renderAlignedTable } from './terminal-table.js';
4
+ describe('terminal table', () => {
5
+ test('renders aligned columns for mixed widths', () => {
6
+ const lines = renderAlignedTable({
7
+ rows: [
8
+ { id: 2, app: 'Zed', size: '1720x1440' },
9
+ { id: 102, app: 'Google Chrome', size: '3440x1440' },
10
+ ],
11
+ columns: [
12
+ {
13
+ header: 'id',
14
+ align: 'right',
15
+ value: (row) => {
16
+ return String(row.id);
17
+ },
18
+ },
19
+ {
20
+ header: 'app',
21
+ value: (row) => {
22
+ return row.app;
23
+ },
24
+ },
25
+ {
26
+ header: 'size',
27
+ align: 'right',
28
+ value: (row) => {
29
+ return row.size;
30
+ },
31
+ },
32
+ ],
33
+ });
34
+ expect(lines.join('\n')).toMatchInlineSnapshot(`
35
+ " id app size
36
+ --- ------------- ---------
37
+ 2 Zed 1720x1440
38
+ 102 Google Chrome 3440x1440"
39
+ `);
40
+ });
41
+ });
package/dist/types.d.ts CHANGED
@@ -10,8 +10,17 @@ export type Region = {
10
10
  width: number;
11
11
  height: number;
12
12
  };
13
+ export type CoordMap = {
14
+ captureX: number;
15
+ captureY: number;
16
+ captureWidth: number;
17
+ captureHeight: number;
18
+ imageWidth: number;
19
+ imageHeight: number;
20
+ };
13
21
  export type DisplayInfo = {
14
22
  id: number;
23
+ index: number;
15
24
  name: string;
16
25
  x: number;
17
26
  y: number;
@@ -20,14 +29,35 @@ export type DisplayInfo = {
20
29
  scale: number;
21
30
  isPrimary: boolean;
22
31
  };
32
+ export type WindowInfo = {
33
+ id: number;
34
+ ownerPid: number;
35
+ ownerName: string;
36
+ title: string;
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ desktopIndex: number;
42
+ };
23
43
  export type ScreenshotInput = {
24
44
  path?: string;
25
45
  display?: number;
46
+ window?: number;
26
47
  region?: Region;
27
48
  annotate?: boolean;
28
49
  };
29
50
  export type ScreenshotResult = {
30
51
  path: string;
52
+ desktopIndex: number;
53
+ captureX: number;
54
+ captureY: number;
55
+ captureWidth: number;
56
+ captureHeight: number;
57
+ imageWidth: number;
58
+ imageHeight: number;
59
+ coordMap: string;
60
+ hint: string;
31
61
  };
32
62
  export type ClickInput = {
33
63
  point: Point;
@@ -55,6 +85,20 @@ export type DragInput = {
55
85
  durationMs?: number;
56
86
  button: MouseButton;
57
87
  };
88
+ export type NativeErrorObject = {
89
+ code: string;
90
+ message: string;
91
+ command: string;
92
+ };
93
+ export type NativeCommandResult = {
94
+ ok: boolean;
95
+ error?: NativeErrorObject;
96
+ };
97
+ export type NativeDataResult<T> = {
98
+ ok: boolean;
99
+ data?: T;
100
+ error?: NativeErrorObject;
101
+ };
58
102
  export interface UseComputerBridge {
59
103
  screenshot(input: ScreenshotInput): Promise<ScreenshotResult>;
60
104
  click(input: ClickInput): Promise<void>;
@@ -72,6 +116,7 @@ export interface UseComputerBridge {
72
116
  }): Promise<void>;
73
117
  mousePosition(): Promise<Point>;
74
118
  displayList(): Promise<DisplayInfo[]>;
119
+ windowList(): Promise<WindowInfo[]>;
75
120
  clipboardGet(): Promise<string>;
76
121
  clipboardSet(input: {
77
122
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usecomputer",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "Fast computer automation CLI for AI agents. Control any desktop with accessibility snapshots, clicks, typing, scrolling, and more.",
6
6
  "bin": "./bin.js",
@@ -12,6 +12,14 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
+ "./lib": {
16
+ "types": "./dist/lib.d.ts",
17
+ "default": "./dist/lib.js"
18
+ },
19
+ "./coord-map": {
20
+ "types": "./dist/coord-map.d.ts",
21
+ "default": "./dist/coord-map.js"
22
+ },
15
23
  "./src": {
16
24
  "types": "./src/index.ts",
17
25
  "default": "./src/index.ts"
@@ -47,18 +55,20 @@
47
55
  "license": "MIT",
48
56
  "repository": {
49
57
  "type": "git",
50
- "url": "git+https://github.com/remorses/kimakivoice.git",
58
+ "url": "git+https://github.com/remorses/kimaki.git",
51
59
  "directory": "usecomputer"
52
60
  },
53
- "homepage": "https://github.com/remorses/kimakivoice/tree/main/usecomputer",
61
+ "homepage": "https://github.com/remorses/kimaki/tree/main/usecomputer",
54
62
  "bugs": {
55
- "url": "https://github.com/remorses/kimakivoice/issues"
63
+ "url": "https://github.com/remorses/kimaki/issues"
56
64
  },
57
65
  "os": [
58
- "darwin"
66
+ "darwin",
67
+ "linux"
59
68
  ],
60
69
  "dependencies": {
61
70
  "goke": "^6.3.0",
71
+ "picocolors": "^1.1.1",
62
72
  "string-dedent": "^3.0.1",
63
73
  "zod": "^4.3.6"
64
74
  },
@@ -68,11 +78,15 @@
68
78
  "typescript": "^5.8.3",
69
79
  "vitest": "^4.0.18"
70
80
  },
81
+ "optionalDependencies": {
82
+ "sharp": "^0.34.5"
83
+ },
71
84
  "scripts": {
72
85
  "build": "tsc && chmod +x bin.js",
73
86
  "build:zig": "zig build",
74
87
  "build:native": "tsx scripts/build.ts",
75
88
  "build:native:macos": "tsx scripts/build.ts darwin-arm64 darwin-x64",
89
+ "vm": "tsx scripts/vm.ts",
76
90
  "test": "vitest --run",
77
91
  "typecheck": "tsc --noEmit"
78
92
  }
@@ -1,85 +1,80 @@
1
- // Contract tests for command names and payloads emitted to the native bridge.
1
+ // Contract tests for direct native method calls emitted by the TS bridge.
2
+ // These tests intentionally call the real Zig native module.
2
3
 
4
+ import fs from 'node:fs'
5
+ import os from 'node:os'
3
6
  import { describe, expect, test } from 'vitest'
4
7
  import { createBridgeFromNative } from './bridge.js'
5
- import type { NativeModule } from './native-lib.js'
8
+ import { native } from './native-lib.js'
6
9
 
7
- type Call = {
8
- command: string
9
- payload: unknown
10
- }
11
-
12
- function createFakeNative({ calls }: { calls: Call[] }): NativeModule {
13
- return {
14
- execute(command: string, payloadJson: string): string {
15
- const payload = JSON.parse(payloadJson) as unknown
16
- calls.push({ command, payload })
17
- if (command === 'mouse-position') {
18
- return JSON.stringify({ ok: true, data: { x: 10, y: 20 } })
19
- }
20
- if (command === 'display-list') {
21
- return JSON.stringify({
22
- ok: true,
23
- data: [
24
- { id: 1, name: 'Built-in', x: 0, y: 0, width: 1512, height: 982, scale: 2, isPrimary: true },
25
- ],
26
- })
27
- }
28
- if (command === 'clipboard-get') {
29
- return JSON.stringify({ ok: true, data: { text: 'hello' } })
30
- }
31
- if (command === 'screenshot') {
32
- return JSON.stringify({ ok: true, data: { path: '/tmp/test.png' } })
33
- }
34
- return JSON.stringify({ ok: true, data: null })
35
- },
36
- }
37
- }
10
+ const isMacOS = os.platform() === 'darwin'
38
11
 
39
12
  describe('native bridge contract', () => {
40
- test('maps high-level calls to native commands', async () => {
41
- const calls: Call[] = []
42
- const bridge = createBridgeFromNative({ nativeModule: createFakeNative({ calls }) })
13
+ test('bridge calls hit real Zig module', async () => {
14
+ expect(native).toBeTruthy()
15
+ if (!native) {
16
+ return
17
+ }
43
18
 
44
- await bridge.click({
45
- point: { x: 100, y: 200 },
46
- button: 'left',
47
- count: 2,
48
- modifiers: ['cmd'],
49
- })
50
- await bridge.typeText({ text: 'hello', delayMs: 30 })
51
- await bridge.press({ key: 'enter', count: 1 })
52
- await bridge.scroll({ direction: 'down', amount: 300 })
53
- await bridge.drag({ from: { x: 10, y: 10 }, to: { x: 200, y: 120 }, button: 'left' })
54
- await bridge.hover({ x: 33, y: 44 })
55
- await bridge.mouseMove({ x: 60, y: 70 })
19
+ const bridge = createBridgeFromNative({ nativeModule: native })
20
+
21
+ const safeTarget = { x: 0, y: 0 }
22
+
23
+ // -- Mouse commands --
24
+ await bridge.click({ point: safeTarget, button: 'left', count: 1, modifiers: [] })
25
+ await bridge.hover(safeTarget)
26
+ await bridge.mouseMove(safeTarget)
56
27
  await bridge.mouseDown({ button: 'left' })
57
28
  await bridge.mouseUp({ button: 'left' })
58
- await bridge.mousePosition()
59
- await bridge.displayList()
60
- await bridge.clipboardGet()
61
- await bridge.clipboardSet({ text: 'copied' })
62
- await bridge.screenshot({ path: './out.png' })
29
+ await bridge.drag({
30
+ from: safeTarget,
31
+ to: { x: safeTarget.x + 6, y: safeTarget.y + 6 },
32
+ button: 'left',
33
+ durationMs: 10,
34
+ })
35
+
36
+ // -- Screenshot --
37
+ const screenshotPath = `${process.cwd()}/tmp/bridge-contract-shot.png`
38
+ const shot = await bridge.screenshot({ path: screenshotPath })
39
+ expect(shot.captureWidth).toBeGreaterThan(0)
40
+ expect(shot.captureHeight).toBeGreaterThan(0)
41
+ expect(shot.imageWidth).toBeGreaterThan(0)
42
+ expect(shot.imageHeight).toBeGreaterThan(0)
43
+ expect(shot.coordMap.split(',').length).toBe(6)
44
+ expect(shot.hint).toContain('--coord-map')
45
+ expect(fs.existsSync(screenshotPath)).toBe(true)
46
+ const stat = fs.statSync(screenshotPath)
47
+ expect(stat.size).toBeGreaterThan(100)
48
+
49
+ // -- Keyboard (works on both platforms) --
50
+ await bridge.typeText({ text: 'h', delayMs: 30 })
51
+ await bridge.press({ key: 'backspace', count: 1 })
52
+
53
+ // -- Scroll --
54
+ await bridge.scroll({ direction: 'down', amount: 1 })
55
+ await bridge.scroll({ direction: 'right', amount: 1, at: safeTarget })
56
+
57
+ // -- Display list --
58
+ const displayList = await bridge.displayList()
59
+ expect(displayList.length).toBeGreaterThan(0)
60
+ const firstDisplay = displayList[0]!
61
+ expect(firstDisplay.width).toBeGreaterThan(0)
62
+ expect(firstDisplay.height).toBeGreaterThan(0)
63
+ expect(typeof firstDisplay.id).toBe('number')
64
+ expect(typeof firstDisplay.index).toBe('number')
65
+
66
+ // -- Window list --
67
+ if (isMacOS) {
68
+ const windowList = await bridge.windowList()
69
+ expect(windowList.length).toBeGreaterThan(0)
70
+ const firstWindow = windowList[0]!
71
+ expect(typeof firstWindow.id).toBe('number')
72
+ expect(typeof firstWindow.ownerName).toBe('string')
73
+ expect(typeof firstWindow.desktopIndex).toBe('number')
74
+ }
63
75
 
64
- expect(calls.map((call) => {
65
- return call.command
66
- })).toMatchInlineSnapshot(`
67
- [
68
- "click",
69
- "type-text",
70
- "press",
71
- "scroll",
72
- "drag",
73
- "hover",
74
- "mouse-move",
75
- "mouse-down",
76
- "mouse-up",
77
- "mouse-position",
78
- "display-list",
79
- "clipboard-get",
80
- "clipboard-set",
81
- "screenshot",
82
- ]
83
- `)
76
+ // -- Clipboard (TODO on all platforms — Zig returns "TODO not implemented") --
77
+ await expect(bridge.clipboardSet({ text: 'bridge-contract-test' })).rejects.toThrow('TODO not implemented')
78
+ await expect(bridge.clipboardGet()).rejects.toThrow('TODO not implemented')
84
79
  })
85
80
  })