openclacky 1.2.4 → 1.2.6

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.
@@ -111,96 +111,6 @@ chrome-devtools-mcp --version 2>/dev/null
111
111
 
112
112
  If still missing after user confirms, stop with error message.
113
113
 
114
- ### Step 2.5 — WSL networking setup (only when session context shows `OS: WSL/Windows`)
115
-
116
- **Skip this entire step on macOS / Linux.** Look at the session context line that begins with `[Session context: ...]` — only run this step if it includes `OS: WSL/Windows`.
117
-
118
- #### Background (read this so you know what to do)
119
-
120
- The browser tool runs inside WSL but Chrome/Edge runs on Windows. By default WSL2 uses NAT networking, which means `127.0.0.1` inside WSL **cannot** reach Windows' Chrome debug port. The fix is to enable WSL2 **mirrored networking** (`networkingMode=mirrored` in `%USERPROFILE%\.wslconfig`), which makes WSL share Windows' network stack so `127.0.0.1` works directly.
121
-
122
- We have a helper script that handles all the Windows-side details:
123
-
124
- ```
125
- ~/.clacky/scripts/wsl_network_doctor.ps1
126
- ```
127
-
128
- It exposes three subcommands:
129
-
130
- | Subcommand | What it does | Exit code |
131
- |---|---|---|
132
- | `status` | Check whether mirrored is configured (auto-passes on WSL1) | `0` OK / `10` NEED_ENABLE |
133
- | `enable` | Write `networkingMode=mirrored` to `.wslconfig` (does NOT shut down WSL) | `0` success / `1` fail |
134
- | `repair` | Restart Windows Host Network Service (HNS) via UAC prompt | `0` launched / `1` fail |
135
-
136
- Invoke it from WSL like this:
137
-
138
- ```bash
139
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w ~/.clacky/scripts/wsl_network_doctor.ps1)" <subcommand>
140
- ```
141
-
142
- #### Step 2.5.1 — Check status
143
-
144
- ```bash
145
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w ~/.clacky/scripts/wsl_network_doctor.ps1)" status
146
- ```
147
-
148
- - Exit `0` (output starts with `OK:`) → either mirrored is configured (WSL2) or
149
- Ubuntu is running on WSL1 (which shares the Windows network stack and needs no
150
- config). Either way, proceed to Step 3.
151
- - Exit `10` (output starts with `NEED_ENABLE:`) → continue to Step 2.5.2.
152
- - Any other failure → show the output to the user and ask them to retry. Stop here.
153
-
154
- #### Step 2.5.2 — Enable mirrored (only when NEED_ENABLE)
155
-
156
- Tell the user what's about to happen (in their language):
157
-
158
- > WSL doesn't have mirrored networking enabled yet — the browser tool needs it to reach Chrome on Windows.
159
- > I'll add one line to `%USERPROFILE%\.wslconfig`. Your current WSL session will NOT be restarted.
160
-
161
- Run:
162
-
163
- ```bash
164
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w ~/.clacky/scripts/wsl_network_doctor.ps1)" enable
165
- ```
166
-
167
- If the script exits `0`:
168
-
169
- > ✅ `.wslconfig` updated. Tell the user (in their language):
170
- >
171
- > The config takes effect only after WSL restarts, but we can't restart WSL from inside WSL.
172
- > Please:
173
- >
174
- > 1. Open **PowerShell** on Windows
175
- > 2. Run: `wsl --shutdown`
176
- > 3. Reopen the Clacky terminal
177
- > 4. Run `/browser-setup` again
178
- >
179
- > Stop here. Wait for the user to come back in a new session.
180
-
181
- If the script exits non-zero, show the output to the user and stop. Do NOT proceed to Step 3 — without mirrored networking the browser tool will not work.
182
-
183
- #### Step 2.5.3 — When to run repair
184
-
185
- Do NOT run `repair` proactively. Only run it later if **all** of the following are true:
186
-
187
- - `status` returned `OK` (mirrored is configured)
188
- - The user has restarted WSL since the config was written
189
- - Step 3's `browser(action="status")` still fails with a "Chrome/Edge is not running or remote debugging is not enabled" error
190
-
191
- In that situation, tell the user (in their language):
192
-
193
- > The config looks correct but the browser still can't connect. Windows Host Network Service may be stuck — I'll restart it.
194
- > **A Windows User Account Control (UAC) prompt will appear shortly. Please click "Yes".**
195
-
196
- Then run:
197
-
198
- ```bash
199
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w ~/.clacky/scripts/wsl_network_doctor.ps1)" repair
200
- ```
201
-
202
- After it returns, tell the user to run `wsl --shutdown` in PowerShell and reopen Clacky. Stop and wait.
203
-
204
114
  ### Step 3 — Verify Chrome/Edge is running with remote debugging
205
115
 
206
116
  **CRITICAL**: Do NOT attempt `browser()` calls yet. First check if the browser is reachable using the API:
@@ -278,6 +188,12 @@ If still failing:
278
188
  >
279
189
  > Run `/browser-setup doctor` to diagnose the issue in detail.
280
190
 
191
+ Fetch the online troubleshooting guide for additional help:
192
+
193
+ ```
194
+ web_fetch(url="https://www.openclacky.com/docs/browser-troubleshooting")
195
+ ```
196
+
281
197
  Stop here and suggest running doctor.
282
198
 
283
199
  #### Case B: Other errors (MCP handshake timeout, daemon crash, etc.)
@@ -514,3 +430,13 @@ After fixing these issues, run `/browser-setup` again to verify.
514
430
 
515
431
  The browser tool is ready to use.
516
432
  ```
433
+
434
+ ### Online Troubleshooting Guide
435
+
436
+ If any ❌ items are found, or the user reports a problem you cannot resolve with the above scenarios, fetch the latest troubleshooting guide:
437
+
438
+ ```
439
+ web_fetch(url="https://www.openclacky.com/docs/browser-troubleshooting")
440
+ ```
441
+
442
+ Use the content from that page to provide up-to-date diagnosis and resolution steps. This online document is maintained separately and may contain newer solutions not listed here.
@@ -39,6 +39,17 @@ module Clacky
39
39
  msg[:content].select { |b| b[:type] == "tool_result" }.map { |b| b[:tool_use_id] }
40
40
  end
41
41
 
42
+ # Anthropic requires tool_use.id to match ^[a-zA-Z0-9_-]+$ (max 128 chars).
43
+ # Some OpenAI-compatible upstreams (e.g. kimi-k2.6) return ids like "tool_name:0"
44
+ # — fine for OpenAI, rejected by Anthropic. We replace illegal chars with "_"
45
+ # at the format boundary so ids stay self-consistent across use/result pairs
46
+ # (pure function → same input maps to same output in both directions).
47
+ def sanitize_tool_use_id(id)
48
+ s = id.to_s
49
+ s = s.gsub(/[^a-zA-Z0-9_-]/, "_")
50
+ s.length > 128 ? s[0, 128] : s
51
+ end
52
+
42
53
  # ── Request building ──────────────────────────────────────────────────────
43
54
 
44
55
  # Convert canonical @messages + tools into an Anthropic API request body.
@@ -156,7 +167,6 @@ module Clacky
156
167
  end
157
168
 
158
169
  # ── Tool result formatting ────────────────────────────────────────────────
159
-
160
170
  # Format tool results into canonical messages to append to @messages.
161
171
  # Input: response (canonical, has :tool_calls), tool_results array
162
172
  # Output: canonical messages: [{ role: "tool", tool_call_id:, content: }]
@@ -211,7 +221,7 @@ module Clacky
211
221
  else
212
222
  raw_args
213
223
  end
214
- blocks << { type: "tool_use", id: tc[:id], name: name, input: input || {} }
224
+ blocks << { type: "tool_use", id: sanitize_tool_use_id(tc[:id]), name: name, input: input || {} }
215
225
  end
216
226
 
217
227
  return { role: "assistant", content: blocks }
@@ -250,7 +260,7 @@ module Clacky
250
260
  else
251
261
  raw_content
252
262
  end
253
- block = { type: "tool_result", tool_use_id: msg[:tool_call_id], content: tool_content }
263
+ block = { type: "tool_result", tool_use_id: sanitize_tool_use_id(msg[:tool_call_id]), content: tool_content }
254
264
  block[:cache_control] = hoisted_cache_control if hoisted_cache_control
255
265
  return { role: "user", content: [block] }
256
266
  end
@@ -187,7 +187,7 @@ module Clacky
187
187
  name = func[:name] || tc[:name]
188
188
  raw_args = func[:arguments] || tc[:arguments]
189
189
  input = raw_args.is_a?(String) ? (JSON.parse(raw_args) rescue {}) : (raw_args || {})
190
- blocks << { toolUse: { toolUseId: tc[:id], name: name, input: input } }
190
+ blocks << { toolUse: { toolUseId: Anthropic.sanitize_tool_use_id(tc[:id]), name: name, input: input } }
191
191
  end
192
192
 
193
193
  return { role: "assistant", content: blocks }
@@ -208,7 +208,7 @@ module Clacky
208
208
  end
209
209
  return {
210
210
  role: "user",
211
- content: [{ toolResult: { toolUseId: msg[:tool_call_id], content: result_blocks } }]
211
+ content: [{ toolResult: { toolUseId: Anthropic.sanitize_tool_use_id(msg[:tool_call_id]), content: result_blocks } }]
212
212
  }
213
213
  end
214
214
 
@@ -228,12 +228,17 @@ module Clacky
228
228
  h.request(req) do |resp|
229
229
  case resp.code.to_i
230
230
  when 200
231
+ expected_len = resp["content-length"]&.to_i
231
232
  File.open(dest, "wb") do |f|
232
233
  resp.read_body do |chunk|
233
234
  f.write(chunk)
234
235
  written += chunk.bytesize
235
236
  end
236
237
  end
238
+ if expected_len && expected_len > 0 && written != expected_len
239
+ raise RetryableNetworkError,
240
+ "Truncated download: got #{written} bytes, expected #{expected_len}"
241
+ end
237
242
  when 301, 302, 303, 307, 308
238
243
  location = resp["location"]
239
244
  raise RetryableNetworkError, "Redirect with no Location header" if location.nil? || location.empty?
@@ -354,13 +359,35 @@ module Clacky
354
359
  { success: true, data: body["data"] || body }
355
360
  else
356
361
  error_code = body["code"]
362
+ server_msg = extract_server_error_message(body)
357
363
  error_msg = API_ERROR_MESSAGES[error_code] ||
358
- body["error"] ||
364
+ server_msg ||
359
365
  "Request failed (HTTP #{code}#{error_code ? ", code: #{error_code}" : ""}). Please contact support."
360
366
  { success: false, error: error_msg, data: body }
361
367
  end
362
368
  end
363
369
 
370
+ # Server error messages can come back under different keys / shapes:
371
+ # { "error": "msg" } — single string
372
+ # { "errors": ["msg1", "msg2"] } — array of strings (Rails .errors.full_messages)
373
+ # { "errors": "msg" } — string (less common)
374
+ # { "message": "msg" } — alternative key
375
+ # Returns the first non-blank human-readable string, or nil if none.
376
+ private def extract_server_error_message(body)
377
+ return nil unless body.is_a?(Hash)
378
+
379
+ [body["error"], body["errors"], body["message"]].each do |val|
380
+ case val
381
+ when String
382
+ return val unless val.strip.empty?
383
+ when Array
384
+ joined = val.compact.map(&:to_s).reject(&:empty?).join("; ")
385
+ return joined unless joined.empty?
386
+ end
387
+ end
388
+ nil
389
+ end
390
+
364
391
  # Raised for transient failures that should be retried (timeouts, conn resets, SSL errors).
365
392
  class RetryableNetworkError < StandardError; end
366
393
  end
@@ -237,33 +237,6 @@ module Clacky
237
237
  "website_url" => "https://console.anthropic.com/settings/keys"
238
238
  }.freeze,
239
239
 
240
- "clackyai-sea" => {
241
- "name" => "ClackyAI(Sea)",
242
- "base_url" => "https://api.clacky.ai",
243
- "api" => "bedrock",
244
- "default_model" => "abs-claude-sonnet-4-5",
245
- "models" => [
246
- "abs-claude-opus-4-6",
247
- "abs-claude-sonnet-4-6",
248
- "abs-claude-sonnet-4-5",
249
- "abs-claude-haiku-4-5"
250
- ],
251
- # Claude family — all vision-capable.
252
- "capabilities" => { "vision" => true }.freeze,
253
- # Per-primary lite pairing — see openclacky preset for rationale.
254
- "lite_models" => {
255
- "abs-claude-opus-4-6" => "abs-claude-haiku-4-5",
256
- "abs-claude-sonnet-4-6" => "abs-claude-haiku-4-5",
257
- "abs-claude-sonnet-4-5" => "abs-claude-haiku-4-5"
258
- },
259
- # Fallback chain: if a model is unavailable, try the next one in order.
260
- # Keys are primary model names; values are the fallback model to use instead.
261
- "fallback_models" => {
262
- "abs-claude-sonnet-4-6" => "abs-claude-sonnet-4-5"
263
- },
264
- "website_url" => "https://clacky.ai"
265
- }.freeze,
266
-
267
240
  "mimo" => {
268
241
  "name" => "MiMo (Xiaomi)",
269
242
  "base_url" => "https://api.xiaomimimo.com/v1",
@@ -436,6 +436,7 @@ module Clacky
436
436
  when ["GET", "/api/billing/summary"] then api_billing_summary(req, res)
437
437
  when ["GET", "/api/billing/daily"] then api_billing_daily(req, res)
438
438
  when ["GET", "/api/billing/records"] then api_billing_records(req, res)
439
+ when ["DELETE", "/api/billing/clear"] then api_billing_clear(req, res)
439
440
  when ["PATCH", "/api/sessions/:id/model"] then api_switch_session_model(req, res)
440
441
  when ["PATCH", "/api/sessions/:id/working_dir"] then api_change_session_working_dir(req, res)
441
442
  else
@@ -1113,30 +1114,32 @@ module Clacky
1113
1114
 
1114
1115
  # GET /api/billing/summary
1115
1116
  # Returns billing summary for a time period
1116
- # Query params: period (day|week|month|year|all, default: month)
1117
+ # Query params: period (day|week|month|year|all, default: month), model (optional)
1117
1118
  def api_billing_summary(req, res)
1118
1119
  require_relative "../billing/billing_store"
1119
1120
 
1120
1121
  query = URI.decode_www_form(req.query_string.to_s).to_h
1121
1122
  period = (query["period"] || "month").to_sym
1123
+ model = query["model"]
1122
1124
 
1123
1125
  store = Clacky::Billing::BillingStore.new
1124
- summary = store.summary(period: period)
1126
+ summary = store.summary(period: period, model: model)
1125
1127
 
1126
1128
  json_response(res, 200, summary)
1127
1129
  end
1128
1130
 
1129
1131
  # GET /api/billing/daily
1130
1132
  # Returns daily cost breakdown
1131
- # Query params: days (default: 30)
1133
+ # Query params: days (default: 30), model (optional)
1132
1134
  def api_billing_daily(req, res)
1133
1135
  require_relative "../billing/billing_store"
1134
1136
 
1135
1137
  query = URI.decode_www_form(req.query_string.to_s).to_h
1136
1138
  days = [(query["days"] || "30").to_i, 90].min
1139
+ model = query["model"]
1137
1140
 
1138
1141
  store = Clacky::Billing::BillingStore.new
1139
- daily = store.daily_breakdown(days: days)
1142
+ daily = store.daily_breakdown(days: days, model: model)
1140
1143
 
1141
1144
  json_response(res, 200, { days: daily })
1142
1145
  end
@@ -1161,6 +1164,23 @@ module Clacky
1161
1164
  })
1162
1165
  end
1163
1166
 
1167
+ # DELETE /api/billing/clear
1168
+ # Clears billing records
1169
+ # Query params: scope (today|all, default: today)
1170
+ def api_billing_clear(req, res)
1171
+ require_relative "../billing/billing_store"
1172
+
1173
+ query = URI.decode_www_form(req.query_string.to_s).to_h
1174
+ scope = query["scope"] || "today"
1175
+
1176
+ store = Clacky::Billing::BillingStore.new
1177
+ deleted = store.clear(scope: scope.to_sym)
1178
+
1179
+ json_response(res, 200, { ok: true, deleted: deleted, scope: scope })
1180
+ rescue => e
1181
+ json_response(res, 500, { error: e.message })
1182
+ end
1183
+
1164
1184
  # GET /api/version
1165
1185
  # Returns current version and latest version from RubyGems (cached for 1 hour).
1166
1186
  def api_get_version(res)
@@ -35,7 +35,7 @@ module Clacky
35
35
  Workflow: open → snapshot(interactive:true) → act(ref=...). New tab from `open` is auto-selected; only use `focus` to switch back to a previously-opened tab.
36
36
  snapshot: returns hierarchical a11y tree truncated to ~8KB. Use query="text" to seek, or offset=N to page.
37
37
  act kinds: click | dblclick | type | fill | press | hover | scroll | drag | select | wait | evaluate | click_at
38
- evaluate: `js` is a function body, e.g. `return document.title` or `const x=...; return x;` result is JSON-encoded.
38
+ evaluate: `js` is a JS expression, e.g. "document.title". For multi-line / async logic use an IIFE: "(async () => { const r = await fetch(...); return r.status })()". Result is JSON-encoded.
39
39
  screenshot: expensive — pass `ref` to capture one element instead of the whole page.
40
40
  DESC
41
41
  self.tool_category = "web"
@@ -58,7 +58,7 @@ module Clacky
58
58
  amount: { type: "integer", description: "act scroll pixels (default 300)" },
59
59
  ms: { type: "integer", description: "act wait ms" },
60
60
  selector: { type: "string", description: "act wait: text or CSS selector" },
61
- js: { type: "string", description: "act evaluate: JS function body (use return)" },
61
+ js: { type: "string", description: "act evaluate: JS expression (use IIFE for multi-line/async)" },
62
62
  target_ref: { type: "string", description: "act drag destination ref" },
63
63
  values: { type: "array", items: { type: "string" }, description: "act select options" },
64
64
  x: { type: "number", description: "click_at x px" },
@@ -264,7 +264,7 @@ module Clacky
264
264
  url = require_url(opts)
265
265
  return url if url.is_a?(Hash)
266
266
  invalidate_page_cache!
267
- mcp_call("navigate_page", { type: "url", url: url })
267
+ mcp_call("navigate_page", with_page({ type: "url", url: url }))
268
268
  wait_for_page_ready
269
269
  { action: "navigate", success: true, profile: "user", url: url, output: "Navigated to: #{url}" }
270
270
 
@@ -385,19 +385,10 @@ module Clacky
385
385
  pid ? args.merge(pageId: pid) : args
386
386
  end
387
387
 
388
- # Wrap user-supplied JS as a Chrome-DevTools-MCP `function` argument.
389
- # We treat `js` as a function body so users can write `const x = ...; return x;`
390
- # naturally. For pure expressions ("document.title"), we auto-prepend `return`
391
- # so the result still flows back. Detection is conservative — the presence of
392
- # `return` or any top-level statement keyword skips the auto-return.
393
388
  private def build_evaluate_function(js)
394
389
  body = js.to_s.strip
395
390
  return "() => {}" if body.empty?
396
-
397
- looks_like_statement = body.match?(/(^|[\s;{])(return|const|let|var|if|for|while|throw|try|switch|function|class|do|await|async)\b/) ||
398
- body.include?(";")
399
- body = "return (#{body})" unless looks_like_statement
400
- "() => { #{body} }"
391
+ "() => (#{body})"
401
392
  end
402
393
 
403
394
  SCREENSHOT_MAX_WIDTH = 800
@@ -1166,20 +1166,26 @@ module Clacky
1166
1166
  # the literal printf tail `"$__clacky_ec"`. Until we see that, we
1167
1167
  # accumulate; once we do, we strip the whole wrapper out and only
1168
1168
  # emit whatever real output came after it.
1169
+ #
1170
+ # However, when stty -echo is active (the normal case for our
1171
+ # persistent sessions), the wrapper is never echoed — so the tail
1172
+ # marker never appears. We detect this by checking: if we have a
1173
+ # complete line (\n present) and it does NOT contain the wrapper
1174
+ # fingerprint, echo was suppressed and we can start streaming
1175
+ # immediately.
1169
1176
  unless wrapper_swallowed
1170
1177
  tail_marker = '"$__clacky_ec"'
1171
1178
  tail_idx = stream_pending.index(tail_marker)
1172
1179
  if tail_idx
1173
- # Strip from start through end-of-line of the printf tail.
1174
1180
  eol_after = stream_pending.index("\n", tail_idx) || (stream_pending.bytesize - 1)
1175
1181
  stream_pending.replace(stream_pending.byteslice(eol_after + 1, stream_pending.bytesize - eol_after - 1).to_s)
1176
1182
  wrapper_swallowed = true
1177
1183
  elsif force_partial
1178
- # End of stream and we never saw the wrapper tail — give up
1179
- # on swallowing and emit what we have, run normal stripping.
1184
+ wrapper_swallowed = true
1185
+ elsif stream_pending.include?("\n") && !stream_pending.include?("__clacky_ec")
1186
+ # stty -echo suppressed the wrapper echo; real output is arriving.
1180
1187
  wrapper_swallowed = true
1181
1188
  else
1182
- # Still hunting; keep buffering. Emit nothing yet.
1183
1189
  return
1184
1190
  end
1185
1191
  end
@@ -1206,7 +1212,12 @@ module Clacky
1206
1212
  ln.include?("__clacky_pc") ||
1207
1213
  ln.match?(/\A\s*\}\s*>\s*\/dev\/null\s+2>&1;?\s*\z/)
1208
1214
  end.join
1209
- on_output.call(cleaned) unless cleaned.empty?
1215
+ # Collapse runs of 3+ blank lines into a single blank line so
1216
+ # PTY noise (cursor-positioning codes cleaned to empty lines)
1217
+ # doesn't produce a wall of whitespace in the streaming UI.
1218
+ cleaned = cleaned.gsub(/\n{3,}/, "\n\n")
1219
+ cleaned = cleaned.lstrip if cleaned.match?(/\A\n+\z/)
1220
+ on_output.call(cleaned) unless cleaned.empty? || cleaned.match?(/\A\s*\z/)
1210
1221
  rescue StandardError
1211
1222
  # Streaming is best-effort — never let a UI bug abort the command.
1212
1223
  end
@@ -1502,6 +1513,7 @@ module Clacky
1502
1513
  # Detect .exe invocations and redirect stdin from /dev/null unless
1503
1514
  # the command already has an explicit stdin redirect.
1504
1515
  private def redirect_exe_stdin(command)
1516
+ return command unless Clacky::Utils::EnvironmentDetector.wsl?
1505
1517
  return command unless command =~ /\.exe\b/i
1506
1518
  return command if command =~ /<\s*[^\s|&;]/
1507
1519
 
@@ -1520,8 +1532,7 @@ module Clacky
1520
1532
  # `???`. Inject UTF-8 setup into the user's PowerShell command so the
1521
1533
  # shell emits UTF-8 bytes regardless of host locale.
1522
1534
  POWERSHELL_PREAMBLE =
1523
- "[Console]::OutputEncoding=[Text.Encoding]::UTF8;" \
1524
- "$OutputEncoding=[Text.Encoding]::UTF8;"
1535
+ "[Console]::OutputEncoding=[Text.Encoding]::UTF8;"
1525
1536
 
1526
1537
  # Only rewrites simple `powershell[.exe]` / `pwsh[.exe]` invocations.
1527
1538
  # Skips -File / -EncodedCommand / commands already handling encoding /
@@ -43,10 +43,12 @@ module Clacky
43
43
  .mp3 .mp4 .avi .mov .mkv .wav .flac
44
44
  .ttf .otf .woff .woff2
45
45
  .db .sqlite .bin .dat
46
+ .wps .et .dps
46
47
  ].freeze
47
48
 
48
49
  GLOB_ALLOWED_BINARY_EXTENSIONS = %w[
49
50
  .pdf .doc .docx .ppt .pptx .xls .xlsx .odt .odp .ods
51
+ .wps .et .dps
50
52
  ].freeze
51
53
 
52
54
  LLM_BINARY_EXTENSIONS = %w[.png .jpg .jpeg .gif .webp .pdf].freeze
@@ -64,6 +66,7 @@ module Clacky
64
66
  ".docx" => :document, ".doc" => :document,
65
67
  ".xlsx" => :spreadsheet, ".xls" => :spreadsheet,
66
68
  ".pptx" => :presentation, ".ppt" => :presentation,
69
+ ".wps" => :document, ".et" => :spreadsheet, ".dps" => :presentation,
67
70
  ".pdf" => :pdf,
68
71
  ".zip" => :zip, ".gz" => :zip, ".tgz" => :zip, ".tar" => :zip, ".rar" => :zip, ".7z" => :zip,
69
72
  ".png" => :image, ".jpg" => :image, ".jpeg" => :image,
@@ -30,6 +30,9 @@ module Clacky
30
30
  ".xls" => "xlsx_parser.rb",
31
31
  ".pptx" => "pptx_parser.rb",
32
32
  ".ppt" => "pptx_parser.rb",
33
+ ".wps" => "wps_parser.rb",
34
+ ".et" => "wps_parser.rb",
35
+ ".dps" => "wps_parser.rb",
33
36
  }.freeze
34
37
 
35
38
  # Ensure ~/.clacky/parsers/ exists and all default parsers are present.
@@ -21,7 +21,6 @@ module Clacky
21
21
  install_browser.sh
22
22
  install_system_deps.sh
23
23
  install_rails_deps.sh
24
- wsl_network_doctor.ps1
25
24
  ].freeze
26
25
 
27
26
  # Copy bundled scripts to ~/.clacky/scripts/ if missing or outdated.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "1.2.4"
4
+ VERSION = "1.2.6"
5
5
  end