ask-computer 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE +21 -0
- data/README.md +141 -0
- data/exe/ask-computer +7 -0
- data/lib/ask/computer/app.rb +266 -0
- data/lib/ask/computer/cli.rb +138 -0
- data/lib/ask/computer/config.rb +22 -0
- data/lib/ask/computer/context.rb +32 -0
- data/lib/ask/computer/driver.rb +101 -0
- data/lib/ask/computer/errors.rb +45 -0
- data/lib/ask/computer/history.rb +204 -0
- data/lib/ask/computer/installer.rb +150 -0
- data/lib/ask/computer/sandbox.rb +84 -0
- data/lib/ask/computer/version.rb +7 -0
- data/lib/ask/computer.rb +70 -0
- data/lib/ask/skills/computer.use_computer/SKILL.md +121 -0
- data/lib/ask/skills/computer.use_computer/references/history.md +80 -0
- data/lib/ask/tools/computer/click.rb +27 -0
- data/lib/ask/tools/computer/history_query.rb +56 -0
- data/lib/ask/tools/computer/history_status.rb +36 -0
- data/lib/ask/tools/computer/installer_setup.rb +32 -0
- data/lib/ask/tools/computer/installer_status.rb +26 -0
- data/lib/ask/tools/computer/key.rb +23 -0
- data/lib/ask/tools/computer/screenshot.rb +22 -0
- data/lib/ask/tools/computer/type.rb +23 -0
- data/lib/ask-computer.rb +23 -0
- metadata +156 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: computer.use_computer
|
|
3
|
+
description: Drive desktop apps in the background via Cua Driver and query encrypted Computer History — screenshots, clicks, typing, sandboxed VMs, and prior-run context
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use this skill when you need to automate desktop applications, inspect screen state, or resume prior work using Computer History.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
Cua Driver must be installed. If `Ask::Computer.driver.start` fails, tell the user how to install it:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
/bin/bash -c "$(curl -fsSL https://cua.ai/driver/install.sh)"
|
|
14
|
+
# then follow post-install instructions at https://cua.ai/docs/tutorials/drive-your-first-app
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For Computer History (prior-run context), the daemon must be a nightly build with the preview admitted and history enabled:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cua-driver history enable
|
|
21
|
+
cua-driver history status
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
All history access is permission-gated and metadata-only. Never assume history is available.
|
|
25
|
+
|
|
26
|
+
## Step 1: Discover and Check History
|
|
27
|
+
|
|
28
|
+
Before broader desktop inspection, check whether history is available — but only when the user asks to continue, resume, recall recent activity, or explain what a prior Cua run did. Do not query history for unrelated tasks.
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "ask-computer"
|
|
32
|
+
|
|
33
|
+
driver = Ask::Computer.driver
|
|
34
|
+
driver.start
|
|
35
|
+
|
|
36
|
+
history = Ask::Computer.history
|
|
37
|
+
status = history.status
|
|
38
|
+
|
|
39
|
+
# status fields: supported, admitted, enabled, paused, encrypted, profile,
|
|
40
|
+
# retention_days, quota_bytes, bytes_used, dropped_events, health
|
|
41
|
+
# health values: ready, disabled, paused, not_admitted, key_locked, key_unavailable,
|
|
42
|
+
# storage_corrupt, quota_reached, events_dropped, writer_stopped
|
|
43
|
+
|
|
44
|
+
if status.enabled? && status.healthy?
|
|
45
|
+
result = history.recent(limit: 20)
|
|
46
|
+
# result.events => Array<Ask::Computer::Event> (CloudEvents 1.0, see references/history.md)
|
|
47
|
+
# result.metadata_only? => true (always — no screenshots or typed text)
|
|
48
|
+
else
|
|
49
|
+
# Continue without history. Preserve the reason (disabled/paused/unhealthy) in reasoning.
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If `history_status` or `history_query` is not advertised, or permission is denied, continue without history. Do not retry with a broader tool, read files directly, or ask the model to reconstruct history.
|
|
54
|
+
|
|
55
|
+
## Step 2: Drive the Desktop
|
|
56
|
+
|
|
57
|
+
Use the computer tools to observe and act. Always verify state changes with a screenshot after acting.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# Observe
|
|
61
|
+
sandbox = Ask::Computer.sandbox
|
|
62
|
+
image = sandbox.screenshot # or driver.call_tool("computer_screenshot", {})
|
|
63
|
+
|
|
64
|
+
# Act (background — does not steal cursor or focus)
|
|
65
|
+
driver.call_tool("computer_click", { x: 120, y: 80 })
|
|
66
|
+
driver.call_tool("computer_type", { text: "Hello" })
|
|
67
|
+
driver.call_tool("computer_key", { key: "Enter" })
|
|
68
|
+
|
|
69
|
+
# Or via Ask::Tool subclasses (for agent sessions)
|
|
70
|
+
# Ask::Tools::Computer::Screenshot, Click, Type, Key, HistoryStatus, HistoryQuery
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Prefer accessibility-aware targeting when available over raw coordinates. After each state-changing action, re-screenshot to confirm the change.
|
|
74
|
+
|
|
75
|
+
## Step 3: Query History with Bounded Reads
|
|
76
|
+
|
|
77
|
+
Events are ordered by `data.sequence` ascending. Treat missing sequence numbers as gap evidence. Paginate toward older records with `until_sequence`; toward newer with `since_sequence`. Bounds are inclusive — subtract/add 1 for non-overlapping pages.
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Filtered query
|
|
81
|
+
history.query(limit: 20, session_id: "abc", since_sequence: 40, until_sequence: 100)
|
|
82
|
+
|
|
83
|
+
# Paginate through all recent history (newest-first, bounded pages)
|
|
84
|
+
history.each_page(limit: 50) do |page|
|
|
85
|
+
page.events.each { |e| puts "#{e.type} seq=#{e.data[:sequence]}" }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Ergonomic: maps RFC error codes to typed errors
|
|
89
|
+
# HistoryNotAdmitted, HistoryKeyLocked, HistoryStorageCorrupt, InvalidHistoryQuery, etc.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Handle health warnings explicitly:
|
|
93
|
+
|
|
94
|
+
- `dropped_events > 0` or `health == "events_dropped"` → treat interval as incomplete.
|
|
95
|
+
- `health == "quota_reached"` → history after that point is incomplete.
|
|
96
|
+
- `health == "storage_corrupt"` → stop querying; direct user to `cua-driver history delete --yes` if they accept data loss.
|
|
97
|
+
- `health == "key_locked"` → user must unlock Keychain/Credential Manager; do not prompt via another tool.
|
|
98
|
+
|
|
99
|
+
## Step 4: Use History as a Lead, Not a Transcript
|
|
100
|
+
|
|
101
|
+
Returned events contain only: time, sequence, session/action IDs, capability, optional `application` (`bundle_id`, `display_name`), outcome/route/delivery/evidence categories. They never contain screenshots, typed text, clipboard, tool args/results, window titles, or URLs.
|
|
102
|
+
|
|
103
|
+
Treat events as hints. Use an identified `application` or `capability` to locate the app, then verify live state (screenshot, accessibility tree) before acting. Never treat history as a complete transcript.
|
|
104
|
+
|
|
105
|
+
## Sandbox VMs (Optional)
|
|
106
|
+
|
|
107
|
+
For isolated or parallel runs, use sandboxed VMs:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
sandbox = Ask::Computer.sandbox
|
|
111
|
+
|
|
112
|
+
sandbox.ephemeral(:linux) do |vm|
|
|
113
|
+
vm.shell("echo hello")
|
|
114
|
+
vm.screenshot
|
|
115
|
+
vm.click(100, 200)
|
|
116
|
+
vm.type("Hello from sandbox")
|
|
117
|
+
end
|
|
118
|
+
# vm.destroy is called automatically on block exit
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Requires a driver that advertises `sandbox_create` / `sandbox_*` tools. Degrade gracefully when unavailable.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Computer History Reference
|
|
2
|
+
|
|
3
|
+
Detailed schema for `Ask::Computer::History` responses. The live contract is the Cua RFC; this file is a local summary so the skill stays compact.
|
|
4
|
+
|
|
5
|
+
## Status Response
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{
|
|
9
|
+
supported: bool, admitted: bool, enabled: bool, paused: bool,
|
|
10
|
+
encrypted: bool, profile: String, retention_days: int (default 7),
|
|
11
|
+
quota_bytes: int (default 104857600), bytes_used: int,
|
|
12
|
+
dropped_events: int, health: String
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Health values:
|
|
17
|
+
|
|
18
|
+
| health | meaning |
|
|
19
|
+
|---|---|
|
|
20
|
+
| ready | Healthy |
|
|
21
|
+
| disabled | Capture off |
|
|
22
|
+
| paused | Capture paused, history still queryable |
|
|
23
|
+
| not_admitted | Daemon not started with preview flag |
|
|
24
|
+
| key_unavailable / key_locked / key_corrupt / key_destroy_failed | Credential store issue |
|
|
25
|
+
| storage_unavailable / storage_corrupt | Encrypted store unreadable |
|
|
26
|
+
| quota_reached | Store at quota, new events may be incomplete |
|
|
27
|
+
| events_dropped | Nonblocking writer dropped events |
|
|
28
|
+
| writer_stopped | Writer unavailable |
|
|
29
|
+
|
|
30
|
+
## Event (CloudEvents 1.0, dataschema urn:cua-driver:schema:history-event:v0)
|
|
31
|
+
|
|
32
|
+
Each `history_query` event:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
{
|
|
36
|
+
specversion, id, source, type, subject, time, datacontenttype, dataschema,
|
|
37
|
+
data: {
|
|
38
|
+
session_id, action_id, sequence, platform, process_model, capability,
|
|
39
|
+
caller_category,
|
|
40
|
+
application?: { bundle_id?, display_name? },
|
|
41
|
+
payload: { kind, effect?, route?, delivery?, delivered_count?, evidence_kinds?[] }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Event Types
|
|
47
|
+
|
|
48
|
+
| type | payload kind |
|
|
49
|
+
|---|---|
|
|
50
|
+
| cua-driver.history.control.v0 | control |
|
|
51
|
+
| cua-driver.history.action_started.v0 | action_started |
|
|
52
|
+
| cua-driver.history.action_completed.v0 | action_completed |
|
|
53
|
+
| cua-driver.history.session_started.v0 | session |
|
|
54
|
+
| cua-driver.history.session_ended.v0 | session |
|
|
55
|
+
| cua-driver.history.access.v0 | access |
|
|
56
|
+
| cua-driver.history.health.v0 | health |
|
|
57
|
+
|
|
58
|
+
Ordering: `data.sequence` ascending. `limit` keeps newest N then returns them ascending. No pagination token — page with `since_sequence` / `until_sequence` (inclusive).
|
|
59
|
+
|
|
60
|
+
## Error Codes
|
|
61
|
+
|
|
62
|
+
Tool failures map to typed errors via `Ask::Computer::Error.from_code`:
|
|
63
|
+
|
|
64
|
+
- `invalid_history_query` / `invalid_history_query_range` → `InvalidHistoryQuery*`
|
|
65
|
+
- `history_preview_not_admitted` → `HistoryNotAdmitted`
|
|
66
|
+
- `history_key_*` → `HistoryKey*`
|
|
67
|
+
- `history_storage_*` → `HistoryStorage*`
|
|
68
|
+
- `history_quota_reached` → `HistoryQuotaReached`
|
|
69
|
+
- `history_writer_stopped` → `HistoryWriterStopped`
|
|
70
|
+
- `history_events_dropped` → `HistoryEventsDropped`
|
|
71
|
+
|
|
72
|
+
Permission denials surface as `Ask::Computer::PermissionDenied` (do not retry with a broader tool).
|
|
73
|
+
|
|
74
|
+
## Storage Profile
|
|
75
|
+
|
|
76
|
+
CBOR Sequence of COSE_Encrypt0, CloudEvents JSON inside, ChaCha20-Poly1305, HKDF per chunk, root key in platform credential store (Keychain / Credential Manager / Secret Service). No plaintext fallback.
|
|
77
|
+
|
|
78
|
+
For full spec see:
|
|
79
|
+
- https://github.com/trycua/cua/blob/main/libs/cua-driver/docs/computer-history-preview.md
|
|
80
|
+
- https://github.com/trycua/cua/blob/main/libs/cua-driver/docs/computer-history-agent-integration-rfc.md
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class Click < Ask::Tool
|
|
9
|
+
description "Click at screen coordinates via Cua Driver. Background click — does not steal cursor."
|
|
10
|
+
|
|
11
|
+
param :x, type: :integer, desc: "X coordinate in screen pixels", required: true
|
|
12
|
+
param :y, type: :integer, desc: "Y coordinate in screen pixels", required: true
|
|
13
|
+
param :button, type: :string, desc: "Mouse button: left, right, middle", required: false
|
|
14
|
+
|
|
15
|
+
def execute(x:, y:, button: "left")
|
|
16
|
+
driver = Ask::Computer.driver
|
|
17
|
+
result = driver.call_tool("computer_click", { x: Integer(x), y: Integer(y), button: button.to_s })
|
|
18
|
+
Ask::Result.ok(data: result)
|
|
19
|
+
rescue Ask::Computer::Error => e
|
|
20
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
21
|
+
rescue ArgumentError => e
|
|
22
|
+
Ask::Result.error(message: e.message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class HistoryQuery < Ask::Tool
|
|
9
|
+
description "Query encrypted Computer History for a bounded slice of metadata-only events. " \
|
|
10
|
+
"Returns at most 200 events ordered by sequence. Requires history.query capability. " \
|
|
11
|
+
"Check history_status first. Results are metadata-only — no screenshots or typed text."
|
|
12
|
+
|
|
13
|
+
param :limit, type: :integer, desc: "Max events to return (1..200, default 50)", required: false
|
|
14
|
+
param :session_id, type: :string, desc: "Opaque session ID filter (1..128 chars)", required: false
|
|
15
|
+
param :since_sequence, type: :integer, desc: "Inclusive lower sequence bound (>=1)", required: false
|
|
16
|
+
param :until_sequence, type: :integer, desc: "Inclusive upper sequence bound (>=1)", required: false
|
|
17
|
+
|
|
18
|
+
def execute(limit: 50, session_id: nil, since_sequence: nil, until_sequence: nil)
|
|
19
|
+
history = Ask::Computer.history
|
|
20
|
+
result = history.query(
|
|
21
|
+
limit: limit,
|
|
22
|
+
session_id: session_id,
|
|
23
|
+
since_sequence: since_sequence,
|
|
24
|
+
until_sequence: until_sequence
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
Ask::Result.ok(data: {
|
|
28
|
+
events: result.events.map { |e| serialize_event(e) },
|
|
29
|
+
metadata_only: result.metadata_only,
|
|
30
|
+
model_context_disclosure: result.model_context_disclosure
|
|
31
|
+
})
|
|
32
|
+
rescue Ask::Computer::InvalidHistoryQuery, Ask::Computer::InvalidHistoryQueryRange => e
|
|
33
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name, code: e.class.name })
|
|
34
|
+
rescue Ask::Computer::Error => e
|
|
35
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def serialize_event(event)
|
|
41
|
+
{
|
|
42
|
+
specversion: event.specversion,
|
|
43
|
+
id: event.id,
|
|
44
|
+
source: event.source,
|
|
45
|
+
type: event.type,
|
|
46
|
+
subject: event.subject,
|
|
47
|
+
time: event.time,
|
|
48
|
+
datacontenttype: event.datacontenttype,
|
|
49
|
+
dataschema: event.dataschema,
|
|
50
|
+
data: event.data
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class HistoryStatus < Ask::Tool
|
|
9
|
+
description "Check Cua Computer History status — whether capture is enabled, " \
|
|
10
|
+
"paused, healthy, and how much encrypted storage is used. " \
|
|
11
|
+
"Call this before history_query."
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
history = Ask::Computer.history
|
|
15
|
+
status = history.status
|
|
16
|
+
|
|
17
|
+
Ask::Result.ok(data: {
|
|
18
|
+
supported: status.supported,
|
|
19
|
+
admitted: status.admitted,
|
|
20
|
+
enabled: status.enabled,
|
|
21
|
+
paused: status.paused,
|
|
22
|
+
encrypted: status.encrypted,
|
|
23
|
+
profile: status.profile,
|
|
24
|
+
retention_days: status.retention_days,
|
|
25
|
+
quota_bytes: status.quota_bytes,
|
|
26
|
+
bytes_used: status.bytes_used,
|
|
27
|
+
dropped_events: status.dropped_events,
|
|
28
|
+
health: status.health
|
|
29
|
+
})
|
|
30
|
+
rescue Ask::Computer::Error => e
|
|
31
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class InstallerSetup < Ask::Tool
|
|
9
|
+
description "Install Cua Driver (if missing) and optionally enable Computer History. " \
|
|
10
|
+
"Use dry_run: true to preview the shell command without executing it. " \
|
|
11
|
+
"Channel is stable or nightly."
|
|
12
|
+
|
|
13
|
+
param :channel, type: :string, desc: "stable or nightly", required: false
|
|
14
|
+
param :enable_history, type: :boolean, desc: "Enable Computer History after install", required: false
|
|
15
|
+
param :dry_run, type: :boolean, desc: "Preview only, do not run installer", required: false
|
|
16
|
+
|
|
17
|
+
def execute(channel: "stable", enable_history: false, dry_run: false)
|
|
18
|
+
steps = Ask::Computer::Installer.setup(
|
|
19
|
+
channel: channel,
|
|
20
|
+
enable_history: enable_history,
|
|
21
|
+
dry_run: dry_run
|
|
22
|
+
)
|
|
23
|
+
Ask::Result.ok(data: {
|
|
24
|
+
steps: steps.map { |name, r| { name: name.to_s, ok: r.ok?, message: r.message, command: r.command } }
|
|
25
|
+
})
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class InstallerStatus < Ask::Tool
|
|
9
|
+
description "Check whether Cua Driver is installed and report its version and " \
|
|
10
|
+
"Computer History health. Read-only, safe to call without the daemon."
|
|
11
|
+
|
|
12
|
+
def execute
|
|
13
|
+
s = Ask::Computer::Installer.status
|
|
14
|
+
Ask::Result.ok(data: {
|
|
15
|
+
installed: s[:installed],
|
|
16
|
+
version: s[:version],
|
|
17
|
+
bin: s[:bin],
|
|
18
|
+
history: s[:history]
|
|
19
|
+
})
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class Key < Ask::Tool
|
|
9
|
+
description "Press a key or key combination via Cua Driver (e.g. Enter, Tab, Control+C)."
|
|
10
|
+
|
|
11
|
+
param :key, type: :string, desc: "Key or combination to press", required: true
|
|
12
|
+
|
|
13
|
+
def execute(key:)
|
|
14
|
+
driver = Ask::Computer.driver
|
|
15
|
+
result = driver.call_tool("computer_key", { key: key.to_s })
|
|
16
|
+
Ask::Result.ok(data: result)
|
|
17
|
+
rescue Ask::Computer::Error => e
|
|
18
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class Screenshot < Ask::Tool
|
|
9
|
+
description "Capture a screenshot of the current desktop via Cua Driver. " \
|
|
10
|
+
"Returns an image that vision models can analyze."
|
|
11
|
+
|
|
12
|
+
def execute
|
|
13
|
+
sandbox = Ask::Computer.sandbox
|
|
14
|
+
image = sandbox.screenshot
|
|
15
|
+
Ask::Result.ok(data: image)
|
|
16
|
+
rescue Ask::Computer::Error => e
|
|
17
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/tools"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Computer
|
|
8
|
+
class Type < Ask::Tool
|
|
9
|
+
description "Type text via Cua Driver. Sends keystrokes in the background."
|
|
10
|
+
|
|
11
|
+
param :text, type: :string, desc: "Text to type", required: true
|
|
12
|
+
|
|
13
|
+
def execute(text:)
|
|
14
|
+
driver = Ask::Computer.driver
|
|
15
|
+
result = driver.call_tool("computer_type", { text: text.to_s })
|
|
16
|
+
Ask::Result.ok(data: result)
|
|
17
|
+
rescue Ask::Computer::Error => e
|
|
18
|
+
Ask::Result.error(message: e.message, metadata: { error_class: e.class.name })
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/ask-computer.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask"
|
|
4
|
+
require "ask/tools"
|
|
5
|
+
require_relative "ask/computer/version"
|
|
6
|
+
require_relative "ask/computer/errors"
|
|
7
|
+
require_relative "ask/computer/config"
|
|
8
|
+
require_relative "ask/computer/context"
|
|
9
|
+
require_relative "ask/computer"
|
|
10
|
+
require_relative "ask/computer/driver"
|
|
11
|
+
require_relative "ask/computer/app"
|
|
12
|
+
require_relative "ask/computer/history"
|
|
13
|
+
require_relative "ask/computer/sandbox"
|
|
14
|
+
require_relative "ask/computer/installer"
|
|
15
|
+
require_relative "ask/computer/cli"
|
|
16
|
+
require_relative "ask/tools/computer/history_status"
|
|
17
|
+
require_relative "ask/tools/computer/history_query"
|
|
18
|
+
require_relative "ask/tools/computer/screenshot"
|
|
19
|
+
require_relative "ask/tools/computer/click"
|
|
20
|
+
require_relative "ask/tools/computer/type"
|
|
21
|
+
require_relative "ask/tools/computer/key"
|
|
22
|
+
require_relative "ask/tools/computer/installer_status"
|
|
23
|
+
require_relative "ask/tools/computer/installer_setup"
|
metadata
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ask-computer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ask-core
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.11.3
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.11.3
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ask-mcp
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.4.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.4.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ask-tools
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.6.2
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.6.2
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.25'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.25'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: mocha
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.1'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
description: Drive desktop apps in the background via Cua Driver — screenshots, clicks,
|
|
97
|
+
typing, sandboxed VMs, and encrypted Computer History. Wraps Cua's MCP tools as
|
|
98
|
+
Ask::Tool subclasses and ships a bundled computer.use_computer skill.
|
|
99
|
+
email:
|
|
100
|
+
- kaka@myrrlabs.com
|
|
101
|
+
executables:
|
|
102
|
+
- ask-computer
|
|
103
|
+
extensions: []
|
|
104
|
+
extra_rdoc_files: []
|
|
105
|
+
files:
|
|
106
|
+
- CHANGELOG.md
|
|
107
|
+
- LICENSE
|
|
108
|
+
- README.md
|
|
109
|
+
- exe/ask-computer
|
|
110
|
+
- lib/ask-computer.rb
|
|
111
|
+
- lib/ask/computer.rb
|
|
112
|
+
- lib/ask/computer/app.rb
|
|
113
|
+
- lib/ask/computer/cli.rb
|
|
114
|
+
- lib/ask/computer/config.rb
|
|
115
|
+
- lib/ask/computer/context.rb
|
|
116
|
+
- lib/ask/computer/driver.rb
|
|
117
|
+
- lib/ask/computer/errors.rb
|
|
118
|
+
- lib/ask/computer/history.rb
|
|
119
|
+
- lib/ask/computer/installer.rb
|
|
120
|
+
- lib/ask/computer/sandbox.rb
|
|
121
|
+
- lib/ask/computer/version.rb
|
|
122
|
+
- lib/ask/skills/computer.use_computer/SKILL.md
|
|
123
|
+
- lib/ask/skills/computer.use_computer/references/history.md
|
|
124
|
+
- lib/ask/tools/computer/click.rb
|
|
125
|
+
- lib/ask/tools/computer/history_query.rb
|
|
126
|
+
- lib/ask/tools/computer/history_status.rb
|
|
127
|
+
- lib/ask/tools/computer/installer_setup.rb
|
|
128
|
+
- lib/ask/tools/computer/installer_status.rb
|
|
129
|
+
- lib/ask/tools/computer/key.rb
|
|
130
|
+
- lib/ask/tools/computer/screenshot.rb
|
|
131
|
+
- lib/ask/tools/computer/type.rb
|
|
132
|
+
homepage: https://github.com/ask-rb/ask-computer
|
|
133
|
+
licenses:
|
|
134
|
+
- MIT
|
|
135
|
+
metadata:
|
|
136
|
+
homepage_uri: https://github.com/ask-rb/ask-computer
|
|
137
|
+
source_code_uri: https://github.com/ask-rb/ask-computer
|
|
138
|
+
changelog_uri: https://github.com/ask-rb/ask-computer/blob/main/CHANGELOG.md
|
|
139
|
+
rdoc_options: []
|
|
140
|
+
require_paths:
|
|
141
|
+
- lib
|
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
143
|
+
requirements:
|
|
144
|
+
- - ">="
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: '3.2'
|
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
152
|
+
requirements: []
|
|
153
|
+
rubygems_version: 4.0.18
|
|
154
|
+
specification_version: 4
|
|
155
|
+
summary: Computer use for the ask-rb ecosystem
|
|
156
|
+
test_files: []
|