@hasna/computer 0.1.2 → 0.1.4

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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Headless mode support for macOS computer use.
3
+ *
4
+ * Three strategies (tried in order):
5
+ * 1. Virtual display via macOS screen sharing (built-in VNC)
6
+ * 2. Lume VM integration (if installed — trycua/cua)
7
+ * 3. Fallback: error with instructions
8
+ *
9
+ * For most users, the practical headless approach is:
10
+ * - Enable macOS Screen Sharing (System Settings > General > Sharing)
11
+ * - The Mac creates a virtual display accessible via VNC
12
+ * - `screencapture` works against this display
13
+ * - Or use a headless Mac mini / Mac Studio with no monitor
14
+ */
15
+ export interface HeadlessConfig {
16
+ /** Strategy to use */
17
+ strategy: "vnc" | "lume" | "auto";
18
+ /** VNC host:port (default: localhost:5900) */
19
+ vncAddress?: string;
20
+ /** Lume VM name */
21
+ lumeVmName?: string;
22
+ }
23
+ /**
24
+ * Check if a display is available for screencapture.
25
+ * Returns false if no display is attached (headless server).
26
+ */
27
+ export declare function hasDisplay(): Promise<boolean>;
28
+ /**
29
+ * Check if macOS Screen Sharing (VNC) is enabled.
30
+ */
31
+ export declare function isScreenSharingEnabled(): Promise<boolean>;
32
+ /**
33
+ * Check if Lume CLI is installed (trycua/cua).
34
+ */
35
+ export declare function isLumeInstalled(): Promise<boolean>;
36
+ /**
37
+ * Start a Lume VM for headless computer use.
38
+ * Returns the VNC address to connect to.
39
+ */
40
+ export declare function startLumeVm(vmName?: string): Promise<string>;
41
+ /**
42
+ * Get headless mode status and instructions.
43
+ */
44
+ export declare function getHeadlessStatus(): Promise<{
45
+ display: boolean;
46
+ screenSharing: boolean;
47
+ lume: boolean;
48
+ recommendation: string;
49
+ }>;
50
+ //# sourceMappingURL=headless.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless.d.ts","sourceRoot":"","sources":["../../../src/drivers/mac/headless.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,QAAQ,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CASnD;AAED;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAIxD;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BvF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAuBD"}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export { createMacDriver, MacDriver } from "./drivers/mac/index.js";
10
10
  export { captureScreenshot, getScreenSize, saveScreenshotToFile } from "./drivers/mac/screenshot.js";
11
11
  export { executeAction } from "./drivers/mac/input.js";
12
12
  export { createProvider, createAnthropicProvider, createOpenAIProvider } from "./providers/index.js";
13
+ export { hasDisplay, isScreenSharingEnabled, isLumeInstalled, getHeadlessStatus } from "./drivers/mac/headless.js";
14
+ export type { HeadlessConfig } from "./drivers/mac/headless.js";
13
15
  export { queryAccessibilityTree, summarizeAccessibilityTree } from "./drivers/mac/accessibility.js";
14
16
  export type { AXElement } from "./drivers/mac/accessibility.js";
15
17
  export { runPostSessionIntegrations, saveToRecordings, registerWithSessions, pushToLogs } from "./lib/integrations.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,QAAQ,EACR,WAAW,EACX,KAAK,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,OAAO,EACP,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACrG,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGrG,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AACpG,YAAY,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAGhE,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGvH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC1F,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxH,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAClG,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,EACL,KAAK,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,QAAQ,EACR,cAAc,EACd,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,QAAQ,EACR,WAAW,EACX,KAAK,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,OAAO,EACP,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACrG,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGrG,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnH,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AACpG,YAAY,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAGhE,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGvH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC1F,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxH,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAClG,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,EACL,KAAK,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,QAAQ,EACR,cAAc,EACd,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -19074,6 +19074,44 @@ function remapCoordinates(action, from, to) {
19074
19074
  break;
19075
19075
  }
19076
19076
  }
19077
+ // src/drivers/mac/headless.ts
19078
+ async function hasDisplay() {
19079
+ const proc = Bun.spawn(["system_profiler", "SPDisplaysDataType", "-detailLevel", "mini"], { stdout: "pipe", stderr: "pipe" });
19080
+ await proc.exited;
19081
+ const stdout = await new Response(proc.stdout).text();
19082
+ return stdout.includes("Resolution:");
19083
+ }
19084
+ async function isScreenSharingEnabled() {
19085
+ const proc = Bun.spawn(["launchctl", "print", "system/com.apple.screensharing"], { stdout: "pipe", stderr: "pipe" });
19086
+ await proc.exited;
19087
+ return proc.exitCode === 0;
19088
+ }
19089
+ async function isLumeInstalled() {
19090
+ const proc = Bun.spawn(["which", "lume"], { stdout: "pipe", stderr: "pipe" });
19091
+ await proc.exited;
19092
+ return proc.exitCode === 0;
19093
+ }
19094
+ async function getHeadlessStatus() {
19095
+ const [display, screenSharing, lume] = await Promise.all([
19096
+ hasDisplay(),
19097
+ isScreenSharingEnabled(),
19098
+ isLumeInstalled()
19099
+ ]);
19100
+ let recommendation;
19101
+ if (display) {
19102
+ recommendation = "Display detected. Headless mode not needed \u2014 use normal mode.";
19103
+ } else if (screenSharing) {
19104
+ recommendation = "No display but Screen Sharing enabled. Connect via VNC to use computer use.";
19105
+ } else if (lume) {
19106
+ recommendation = "No display. Lume is installed \u2014 use --headless to spin up a macOS VM.";
19107
+ } else {
19108
+ recommendation = `No display detected. To use headless mode:
19109
+ ` + ` 1. Enable Screen Sharing: System Settings > General > Sharing > Screen Sharing
19110
+ ` + ` 2. Or install Lume: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)"
19111
+ ` + " 3. Or connect a display/dummy HDMI adapter";
19112
+ }
19113
+ return { display, screenSharing, lume, recommendation };
19114
+ }
19077
19115
  // src/drivers/mac/accessibility.ts
19078
19116
  import { join as join6, dirname as dirname2 } from "path";
19079
19117
  import { existsSync as existsSync4 } from "fs";
@@ -19345,11 +19383,15 @@ export {
19345
19383
  listSessions,
19346
19384
  listPricing,
19347
19385
  listAgents,
19386
+ isScreenSharingEnabled,
19387
+ isLumeInstalled,
19348
19388
  heartbeat,
19389
+ hasDisplay,
19349
19390
  getStats,
19350
19391
  getSession,
19351
19392
  getScreenSize,
19352
19393
  getScaledSize,
19394
+ getHeadlessStatus,
19353
19395
  getDb,
19354
19396
  getConfigValue,
19355
19397
  getConfigPath,
@@ -2,6 +2,11 @@
2
2
  // @bun
3
3
  var __require = import.meta.require;
4
4
 
5
+ // src/server/index.ts
6
+ import { join as join6, dirname as dirname2 } from "path";
7
+ import { fileURLToPath as fileURLToPath2 } from "url";
8
+ import { existsSync as existsSync4 } from "fs";
9
+
5
10
  // src/agent/loop.ts
6
11
  import { randomUUID } from "crypto";
7
12
  import { mkdir } from "fs/promises";
@@ -18992,6 +18997,12 @@ function remapCoordinates(action, from, to) {
18992
18997
 
18993
18998
  // src/server/index.ts
18994
18999
  var PORT = parseInt(process.env.COMPUTER_PORT ?? "19450");
19000
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
19001
+ var DASHBOARD_DIRS = [
19002
+ join6(__dirname2, "..", "..", "dashboard", "dist"),
19003
+ join6(__dirname2, "..", "dashboard", "dist")
19004
+ ];
19005
+ var DASHBOARD_DIR = DASHBOARD_DIRS.find((d) => existsSync4(d));
18995
19006
  var server = Bun.serve({
18996
19007
  port: PORT,
18997
19008
  async fetch(req) {
@@ -19059,6 +19070,21 @@ var server = Bun.serve({
19059
19070
  if (method === "GET" && (path === "/health" || path === "/")) {
19060
19071
  return Response.json({ status: "ok", name: "computer", version: "0.1.0", port: PORT }, { headers: corsHeaders });
19061
19072
  }
19073
+ if (DASHBOARD_DIR && method === "GET" && (path.startsWith("/dashboard") || path === "/")) {
19074
+ if (path === "/" || path === "/dashboard" || path === "/dashboard/") {
19075
+ return new Response(Bun.file(join6(DASHBOARD_DIR, "index.html")), {
19076
+ headers: { "Content-Type": "text/html", "Access-Control-Allow-Origin": "*" }
19077
+ });
19078
+ }
19079
+ const filePath = path.replace("/dashboard", "");
19080
+ const fullPath = join6(DASHBOARD_DIR, filePath);
19081
+ if (existsSync4(fullPath)) {
19082
+ return new Response(Bun.file(fullPath), { headers: { "Access-Control-Allow-Origin": "*" } });
19083
+ }
19084
+ return new Response(Bun.file(join6(DASHBOARD_DIR, "index.html")), {
19085
+ headers: { "Content-Type": "text/html", "Access-Control-Allow-Origin": "*" }
19086
+ });
19087
+ }
19062
19088
  return Response.json({ error: "Not found" }, { status: 404, headers: corsHeaders });
19063
19089
  } catch (err) {
19064
19090
  const message = err instanceof Error ? err.message : String(err);
package/helpers/record ADDED
Binary file
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env swift
2
+ // record.swift — Record mouse/keyboard events via CGEvent tap
3
+ // Usage: record [--duration <seconds>]
4
+ // Output: JSON array of events to stdout
5
+ // Stop: Ctrl+C or duration expires
6
+ //
7
+ // Requires: Accessibility permissions in System Settings
8
+
9
+ import CoreGraphics
10
+ import Foundation
11
+
12
+ struct RecordedEvent: Codable {
13
+ let type: String
14
+ let x: Double?
15
+ let y: Double?
16
+ let button: String?
17
+ let keyCode: Int?
18
+ let characters: String?
19
+ let timestamp: Double
20
+ }
21
+
22
+ var events: [RecordedEvent] = []
23
+ var startTime: Double = 0
24
+ var maxDuration: Double = 60 // default 60 seconds
25
+
26
+ // Parse args
27
+ var args = Array(CommandLine.arguments.dropFirst())
28
+ while !args.isEmpty {
29
+ let arg = args.removeFirst()
30
+ if arg == "--duration" && !args.isEmpty {
31
+ maxDuration = Double(args.removeFirst()) ?? 60
32
+ }
33
+ }
34
+
35
+ func eventType(_ type: CGEventType) -> String? {
36
+ switch type {
37
+ case .leftMouseDown: return "left_click"
38
+ case .rightMouseDown: return "right_click"
39
+ case .leftMouseUp: return "left_mouse_up"
40
+ case .rightMouseUp: return "right_mouse_up"
41
+ case .mouseMoved: return "mouse_move"
42
+ case .leftMouseDragged: return "left_drag"
43
+ case .scrollWheel: return "scroll"
44
+ case .keyDown: return "key_down"
45
+ case .keyUp: return "key_up"
46
+ default: return nil
47
+ }
48
+ }
49
+
50
+ func buttonName(_ type: CGEventType) -> String? {
51
+ switch type {
52
+ case .leftMouseDown, .leftMouseUp, .leftMouseDragged: return "left"
53
+ case .rightMouseDown, .rightMouseUp: return "right"
54
+ default: return nil
55
+ }
56
+ }
57
+
58
+ // Callback for CGEvent tap
59
+ let callback: CGEventTapCallBack = { proxy, type, event, refcon in
60
+ guard let typeName = eventType(type) else { return Unmanaged.passRetained(event) }
61
+
62
+ let now = CFAbsoluteTimeGetCurrent()
63
+ if startTime == 0 { startTime = now }
64
+ let elapsed = now - startTime
65
+
66
+ // Check duration limit
67
+ if elapsed > maxDuration {
68
+ CFRunLoopStop(CFRunLoopGetCurrent())
69
+ return Unmanaged.passRetained(event)
70
+ }
71
+
72
+ let location = event.location
73
+
74
+ let recorded = RecordedEvent(
75
+ type: typeName,
76
+ x: typeName.contains("mouse") || typeName.contains("click") || typeName.contains("drag") || typeName == "scroll" ? Double(location.x) : nil,
77
+ y: typeName.contains("mouse") || typeName.contains("click") || typeName.contains("drag") || typeName == "scroll" ? Double(location.y) : nil,
78
+ button: buttonName(type),
79
+ keyCode: typeName.contains("key") ? Int(event.getIntegerValueField(.keyboardEventKeycode)) : nil,
80
+ characters: nil,
81
+ timestamp: elapsed
82
+ )
83
+
84
+ // For mouse_move, only record every ~50ms to avoid flooding
85
+ if typeName == "mouse_move" {
86
+ if let last = events.last, last.type == "mouse_move" && (elapsed - last.timestamp) < 0.05 {
87
+ return Unmanaged.passRetained(event)
88
+ }
89
+ }
90
+
91
+ events.append(recorded)
92
+
93
+ // Print progress to stderr
94
+ fputs("\r\u{1B}[K Recording... \(events.count) events (\(String(format: "%.1f", elapsed))s / \(String(format: "%.0f", maxDuration))s)", stderr)
95
+
96
+ return Unmanaged.passRetained(event)
97
+ }
98
+
99
+ // Create event tap
100
+ var eventMask: CGEventMask = 0
101
+ eventMask |= (1 << CGEventType.leftMouseDown.rawValue)
102
+ eventMask |= (1 << CGEventType.leftMouseUp.rawValue)
103
+ eventMask |= (1 << CGEventType.rightMouseDown.rawValue)
104
+ eventMask |= (1 << CGEventType.rightMouseUp.rawValue)
105
+ eventMask |= (1 << CGEventType.mouseMoved.rawValue)
106
+ eventMask |= (1 << CGEventType.leftMouseDragged.rawValue)
107
+ eventMask |= (1 << CGEventType.scrollWheel.rawValue)
108
+ eventMask |= (1 << CGEventType.keyDown.rawValue)
109
+ eventMask |= (1 << CGEventType.keyUp.rawValue)
110
+
111
+ guard let tap = CGEvent.tapCreate(
112
+ tap: .cgSessionEventTap,
113
+ place: .headInsertEventTap,
114
+ options: .listenOnly, // Listen only, don't modify events
115
+ eventsOfInterest: eventMask,
116
+ callback: callback,
117
+ userInfo: nil
118
+ ) else {
119
+ fputs("Error: Failed to create event tap. Check Accessibility permissions.\n", stderr)
120
+ exit(1)
121
+ }
122
+
123
+ let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
124
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
125
+ CGEvent.tapEnable(tap: tap, enable: true)
126
+
127
+ fputs("Recording mouse/keyboard events (max \(String(format: "%.0f", maxDuration))s, Ctrl+C to stop)...\n", stderr)
128
+
129
+ // Handle Ctrl+C
130
+ signal(SIGINT) { _ in
131
+ CFRunLoopStop(CFRunLoopGetCurrent())
132
+ }
133
+
134
+ // Run
135
+ CFRunLoopRun()
136
+
137
+ // Output JSON
138
+ fputs("\n", stderr)
139
+ let encoder = JSONEncoder()
140
+ encoder.outputFormatting = [.prettyPrinted]
141
+ if let data = try? encoder.encode(events) {
142
+ print(String(data: data, encoding: .utf8)!)
143
+ }
144
+ fputs("Recorded \(events.count) events\n", stderr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/computer",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Open-source computer use for AI agents — control your Mac with Anthropic or OpenAI. CLI + MCP server + REST API + Dashboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,6 +22,8 @@
22
22
  "helpers/scroll.swift",
23
23
  "helpers/accessibility",
24
24
  "helpers/accessibility.swift",
25
+ "helpers/record",
26
+ "helpers/record.swift",
25
27
  "src/db/migrations",
26
28
  "dashboard/dist",
27
29
  "LICENSE",