openclacky 1.1.2 → 1.1.3

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.clacky/skills/gem-release/SKILL.md +27 -31
  3. data/CHANGELOG.md +14 -0
  4. data/Dockerfile +28 -0
  5. data/docs/engineering-article.md +343 -0
  6. data/lib/clacky/agent/llm_caller.rb +1 -5
  7. data/lib/clacky/cli.rb +1 -1
  8. data/lib/clacky/message_format/anthropic.rb +17 -1
  9. data/lib/clacky/providers.rb +34 -0
  10. data/lib/clacky/server/channel/adapters/dingtalk/adapter.rb +142 -5
  11. data/lib/clacky/server/channel/adapters/dingtalk/api_client.rb +309 -0
  12. data/lib/clacky/ui2/ui_controller.rb +14 -0
  13. data/lib/clacky/ui_interface.rb +14 -0
  14. data/lib/clacky/utils/model_pricing.rb +96 -25
  15. data/lib/clacky/version.rb +1 -1
  16. data/lib/clacky/web/app.css +8 -0
  17. data/lib/clacky/web/index.html +1 -1
  18. data/lib/clacky/web/onboard.js +6 -0
  19. data/lib/clacky/web/settings.js +17 -5
  20. data/scripts/build/lib/apt.sh +30 -10
  21. data/scripts/build/lib/network.sh +3 -2
  22. data/scripts/install.sh +30 -9
  23. metadata +3 -16
  24. data/docs/HOW-TO-USE-CN.md +0 -96
  25. data/docs/HOW-TO-USE.md +0 -94
  26. data/docs/browser-cdp-native-design.md +0 -195
  27. data/docs/c-end-user-positioning.md +0 -64
  28. data/docs/config.example.yml +0 -27
  29. data/docs/deploy-architecture.md +0 -619
  30. data/docs/deploy_subagent_design.md +0 -540
  31. data/docs/install-script-simplification.md +0 -89
  32. data/docs/memory-architecture.md +0 -343
  33. data/docs/openclacky_cloud_api_reference.md +0 -584
  34. data/docs/security-design.md +0 -109
  35. data/docs/session-management-redesign.md +0 -202
  36. data/docs/system-skill-authoring-guide.md +0 -47
  37. data/docs/why-developer.md +0 -371
  38. data/docs/why-openclacky.md +0 -266
@@ -22,7 +22,7 @@ module Clacky
22
22
  read: 0.50 # $0.50/MTok cache read
23
23
  }
24
24
  },
25
-
25
+
26
26
  "claude-sonnet-4.5" => {
27
27
  input: {
28
28
  default: 3.00, # $3/MTok for prompts ≤ 200K tokens
@@ -39,7 +39,7 @@ module Clacky
39
39
  read_over_200k: 0.60 # $0.60/MTok cache read (> 200K)
40
40
  }
41
41
  },
42
-
42
+
43
43
  "claude-haiku-4.5" => {
44
44
  input: {
45
45
  default: 1.00, # $1/MTok
@@ -122,7 +122,7 @@ module Clacky
122
122
  },
123
123
  cache: {
124
124
  write: 0.14, # DeepSeek doesn't charge extra for writes; bill at miss rate
125
- read: 0.028 # $0.028/MTok cache hit
125
+ read: 0.0028 # $0.0028/MTok cache hit
126
126
  }
127
127
  },
128
128
 
@@ -137,7 +137,7 @@ module Clacky
137
137
  },
138
138
  cache: {
139
139
  write: 1.74, # no separate write charge; bill at miss rate
140
- read: 0.145 # $0.145/MTok cache hit
140
+ read: 0.0145 # $0.0145/MTok cache hit
141
141
  }
142
142
  },
143
143
 
@@ -352,6 +352,58 @@ module Clacky
352
352
  cache: { write: 0.30, read: 0.06 }
353
353
  },
354
354
 
355
+ # Qwen (Alibaba DashScope) — USD per 1M tokens, Singapore region list price.
356
+ # Source: Alibaba Cloud Model Studio international pricing.
357
+ # Cache convention (mirrors DeepSeek/Kimi/GLM "displayed ≤ actual"):
358
+ # - DashScope has two cache modes; implicit is auto-on, explicit is opt-in.
359
+ # Implicit: write @ 100% input, read @ 20% input (no setup, no guarantee)
360
+ # Explicit: write @ 125% input, read @ 10% input (cache_control marker)
361
+ # - We bill writes at the regular input rate (matches implicit, and avoids
362
+ # surprising users with the explicit 25% surcharge).
363
+ # - We bill reads at 20% (implicit rate) — the conservative side; users on
364
+ # explicit caching will see real bills slightly *lower* than displayed.
365
+ "qwen3.6-plus" => {
366
+ input: { default: 0.40, over_200k: 0.40 },
367
+ output: { default: 2.40, over_200k: 2.40 },
368
+ cache: { write: 0.40, read: 0.08 }
369
+ },
370
+
371
+ "qwen3.6-max" => {
372
+ input: { default: 1.20, over_200k: 1.20 },
373
+ output: { default: 6.00, over_200k: 6.00 },
374
+ cache: { write: 1.20, read: 0.24 }
375
+ },
376
+
377
+ "qwen3.6-27b" => {
378
+ input: { default: 0.20, over_200k: 0.20 },
379
+ output: { default: 0.80, over_200k: 0.80 },
380
+ cache: { write: 0.20, read: 0.04 }
381
+ },
382
+
383
+ "qwen3.6-flash" => {
384
+ input: { default: 0.15, over_200k: 0.15 },
385
+ output: { default: 0.90, over_200k: 0.90 },
386
+ cache: { write: 0.15, read: 0.03 }
387
+ },
388
+
389
+ "qwen-plus-latest" => {
390
+ input: { default: 0.40, over_200k: 0.40 },
391
+ output: { default: 1.20, over_200k: 1.20 },
392
+ cache: { write: 0.40, read: 0.08 }
393
+ },
394
+
395
+ "qwen-vl-plus" => {
396
+ input: { default: 0.14, over_200k: 0.14 },
397
+ output: { default: 0.41, over_200k: 0.41 },
398
+ cache: { write: 0.14, read: 0.028 }
399
+ },
400
+
401
+ "qwen-vl-max" => {
402
+ input: { default: 0.52, over_200k: 0.52 },
403
+ output: { default: 2.08, over_200k: 2.08 },
404
+ cache: { write: 0.52, read: 0.104 }
405
+ },
406
+
355
407
  }.freeze
356
408
 
357
409
  # Threshold for tiered pricing (200K tokens)
@@ -361,7 +413,7 @@ module Clacky
361
413
 
362
414
  class << self
363
415
  # Calculate cost for the given model and usage
364
- #
416
+ #
365
417
  # @param model [String] Model identifier
366
418
  # @param usage [Hash] Usage statistics containing:
367
419
  # - prompt_tokens: number of input tokens
@@ -384,24 +436,24 @@ module Clacky
384
436
  completion_tokens = usage[:completion_tokens] || 0
385
437
  cache_write_tokens = usage[:cache_creation_input_tokens] || 0
386
438
  cache_read_tokens = usage[:cache_read_input_tokens] || 0
387
-
439
+
388
440
  # Determine if we're in the over_200k tier
389
441
  # Note: prompt_tokens includes cache_read_tokens but NOT cache_write_tokens
390
442
  # cache_write_tokens are additional tokens that were written to cache
391
443
  total_input_tokens = prompt_tokens + cache_write_tokens
392
444
  over_threshold = total_input_tokens > TIERED_PRICING_THRESHOLD
393
-
445
+
394
446
  # Calculate regular input cost (non-cached tokens)
395
447
  # prompt_tokens already includes cache_read_tokens, so we need to subtract them
396
448
  # cache_write_tokens are not part of prompt_tokens, so they're handled separately in cache_cost
397
449
  regular_input_tokens = prompt_tokens - cache_read_tokens
398
450
  input_rate = over_threshold ? pricing[:input][:over_200k] : pricing[:input][:default]
399
451
  input_cost = (regular_input_tokens / 1_000_000.0) * input_rate
400
-
452
+
401
453
  # Calculate output cost
402
454
  output_rate = over_threshold ? pricing[:output][:over_200k] : pricing[:output][:default]
403
455
  output_cost = (completion_tokens / 1_000_000.0) * output_rate
404
-
456
+
405
457
  # Calculate cache costs
406
458
  cache_cost = calculate_cache_cost(
407
459
  pricing: pricing,
@@ -409,24 +461,24 @@ module Clacky
409
461
  cache_read_tokens: cache_read_tokens,
410
462
  over_threshold: over_threshold
411
463
  )
412
-
464
+
413
465
  {
414
466
  cost: input_cost + output_cost + cache_cost,
415
467
  source: source
416
468
  }
417
469
  end
418
-
470
+
419
471
  # Get pricing for a specific model
420
472
  # Falls back to default pricing if model not found
421
- #
473
+ #
422
474
  # @param model [String] Model identifier
423
475
  # @return [Hash] Pricing structure for the model
424
476
  def get_pricing(model)
425
477
  get_pricing_with_source(model)[:pricing]
426
478
  end
427
-
479
+
428
480
  # Get pricing with source information
429
- #
481
+ #
430
482
  # @param model [String] Model identifier
431
483
  # @return [Hash] Hash containing:
432
484
  # - pricing: Pricing structure or nil if model is unknown
@@ -446,18 +498,18 @@ module Clacky
446
498
  { pricing: nil, source: nil }
447
499
  end
448
500
  end
449
-
450
-
501
+
502
+
451
503
  # Normalize model name to match pricing table keys.
452
504
  # Returns the canonical key on match, or nil when no pricing is available.
453
505
  def normalize_model_name(model)
454
506
  return nil if model.nil? || model.empty?
455
-
507
+
456
508
  model = model.downcase.strip
457
-
509
+
458
510
  # Direct match
459
511
  return model if PRICING_TABLE.key?(model)
460
-
512
+
461
513
  # Check for Claude model variations
462
514
  # Support both dot and dash separators (e.g., "4.5", "4-5", "4-6")
463
515
  # Also handles Bedrock cross-region prefixes (e.g. "jp.anthropic.claude-sonnet-4-6")
@@ -514,6 +566,25 @@ module Clacky
514
566
  when /^minimax-m2\.7$/i
515
567
  "minimax-m2.7"
516
568
 
569
+ # Qwen (Alibaba DashScope) — strict anchored match per registered
570
+ # model id in providers.rb. qwen3.6-* are the new flagship line;
571
+ # qwen-plus-latest is the rolling alias for the latest Qwen-Plus
572
+ # release; qwen-vl-* are the multimodal SKUs.
573
+ when /^qwen3\.6-plus$/i
574
+ "qwen3.6-plus"
575
+ when /^qwen3\.6-max$/i
576
+ "qwen3.6-max"
577
+ when /^qwen3\.6-27b$/i
578
+ "qwen3.6-27b"
579
+ when /^qwen3\.6-flash$/i
580
+ "qwen3.6-flash"
581
+ when /^qwen-plus-latest$/i
582
+ "qwen-plus-latest"
583
+ when /^qwen-vl-plus$/i
584
+ "qwen-vl-plus"
585
+ when /^qwen-vl-max$/i
586
+ "qwen-vl-max"
587
+
517
588
  # OpenAI GPT-5.x models — match various dashed/dotted/compact forms
518
589
  # (e.g. "gpt-5.5", "gpt-5-5", "gpt5.5", "gpt55")
519
590
  when /^gpt-?5\.?5$/i, /^gpt-?5[\.-]?5$/i
@@ -533,11 +604,11 @@ module Clacky
533
604
  nil # No pricing available for this model — cost will show as N/A
534
605
  end
535
606
  end
536
-
607
+
537
608
  # Calculate cache-related costs
538
609
  def calculate_cache_cost(pricing:, cache_write_tokens:, cache_read_tokens:, over_threshold:)
539
610
  cache_cost = 0.0
540
-
611
+
541
612
  # Cache write cost
542
613
  if cache_write_tokens > 0
543
614
  write_rate = if pricing[:cache].key?(:write)
@@ -549,10 +620,10 @@ module Clacky
549
620
  else
550
621
  pricing[:cache][:write_default]
551
622
  end
552
-
623
+
553
624
  cache_cost += (cache_write_tokens / 1_000_000.0) * write_rate
554
625
  end
555
-
626
+
556
627
  # Cache read cost
557
628
  if cache_read_tokens > 0
558
629
  read_rate = if pricing[:cache].key?(:read)
@@ -564,10 +635,10 @@ module Clacky
564
635
  else
565
636
  pricing[:cache][:read_default]
566
637
  end
567
-
638
+
568
639
  cache_cost += (cache_read_tokens / 1_000_000.0) * read_rate
569
640
  end
570
-
641
+
571
642
  cache_cost
572
643
  end
573
644
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "1.1.2"
4
+ VERSION = "1.1.3"
5
5
  end
@@ -3289,6 +3289,14 @@ body {
3289
3289
  .custom-select-trigger:hover {
3290
3290
  border-color: var(--color-text-muted);
3291
3291
  }
3292
+ /* Focus: same accent border as `.open` so users see "this is where to start"
3293
+ without the dropdown auto-expanding. Used by onboard step 2 and the
3294
+ settings "add model" flow to nudge users to pick a provider first. */
3295
+ .custom-select-trigger:focus,
3296
+ .custom-select-trigger:focus-visible {
3297
+ outline: none;
3298
+ border-color: var(--color-accent-primary);
3299
+ }
3292
3300
  .custom-select-trigger.open {
3293
3301
  border-color: var(--color-accent-primary);
3294
3302
  }
@@ -834,7 +834,7 @@
834
834
  <div class="setup-field">
835
835
  <label class="setup-label" data-i18n="onboard.key.provider">Provider</label>
836
836
  <div class="custom-select-wrapper" id="setup-provider-wrapper">
837
- <div class="custom-select-trigger">
837
+ <div class="custom-select-trigger" tabindex="0">
838
838
  <span class="custom-select-value placeholder" data-i18n="onboard.key.provider.placeholder">— Choose provider —</span>
839
839
  <svg class="custom-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
840
840
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -86,6 +86,12 @@ const Onboard = (() => {
86
86
  _showSetupStep("key");
87
87
  await _loadProviders();
88
88
  _bindKeyStep();
89
+ // Nudge: focus the provider trigger so the user knows step 1 is "pick
90
+ // a provider". `tabindex="0"` on the trigger makes it focusable; the
91
+ // .open class is NOT toggled, so the dropdown stays closed — we just
92
+ // get the accent-color border via `.custom-select-trigger:focus`.
93
+ const trigger = $("setup-provider-wrapper")?.querySelector(".custom-select-trigger");
94
+ if (trigger) trigger.focus({ preventScroll: true });
89
95
  });
90
96
  }
91
97
 
@@ -81,7 +81,7 @@ const Settings = (() => {
81
81
  <label class="model-field quick-setup-field" ${(model.model || model.base_url) ? 'style="display:none"' : ''}>
82
82
  <span class="field-label">${I18n.t("settings.models.field.quicksetup")}</span>
83
83
  <div class="custom-select-wrapper" data-index="${index}">
84
- <div class="custom-select-trigger">
84
+ <div class="custom-select-trigger" tabindex="0">
85
85
  <span class="custom-select-value placeholder">${I18n.t("settings.models.placeholder.provider")}</span>
86
86
  <svg class="custom-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
87
87
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -709,10 +709,22 @@ const Settings = (() => {
709
709
  behavior: "smooth"
710
710
  });
711
711
 
712
- // Put focus on the new card's first input so the user can start
713
- // typing immediately.
714
- const firstInput = last.querySelector("input, select, textarea");
715
- if (firstInput) firstInput.focus({ preventScroll: true });
712
+ // Put focus on the new card so the user can start configuring.
713
+ // Priority order:
714
+ // 1. The provider `.custom-select-trigger` — nudges the user to
715
+ // pick a provider first (step 1 of the 3-field form). It's a
716
+ // div with tabindex="0", so it gets the accent border via
717
+ // `:focus` without expanding the dropdown.
718
+ // 2. Fall back to the first form input (used when the quick-setup
719
+ // field is hidden, e.g. on a model card that already has values).
720
+ const providerTrigger = last.querySelector(".custom-select-wrapper .custom-select-trigger");
721
+ const isVisible = el => el && el.offsetParent !== null;
722
+ if (isVisible(providerTrigger)) {
723
+ providerTrigger.focus({ preventScroll: true });
724
+ } else {
725
+ const firstInput = last.querySelector("input, select, textarea");
726
+ if (firstInput) firstInput.focus({ preventScroll: true });
727
+ }
716
728
  });
717
729
  });
718
730
  }
@@ -1,6 +1,6 @@
1
1
  # apt.sh — apt package manager helpers (Ubuntu/Debian)
2
- # Depends-On: colors.sh
3
- # Requires-Vars: $DISTRO $USE_CN_MIRRORS
2
+ # Depends-On: colors.sh network.sh
3
+ # Requires-Vars: $DISTRO $USE_CN_MIRRORS $CN_ALIYUN_MIRROR
4
4
  # Sets-Vars: (none)
5
5
  # Include via: @include lib/apt.sh
6
6
 
@@ -12,20 +12,40 @@ setup_apt_mirror() {
12
12
 
13
13
  if [ "$USE_CN_MIRRORS" = true ]; then
14
14
  print_info "Region: China — configuring Aliyun apt mirror"
15
- local codename="${VERSION_CODENAME:-jammy}"
16
- local components="main restricted universe multiverse"
17
- local arch; arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
18
- if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
19
- local mirror="https://mirrors.aliyun.com/ubuntu-ports/"
20
- else
21
- local mirror="https://mirrors.aliyun.com/ubuntu/"
15
+
16
+ if [ -f /etc/apt/sources.list ]; then
17
+ sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
18
+ print_info "Backed up /etc/apt/sources.list to sources.list.bak"
22
19
  fi
23
- sudo tee /etc/apt/sources.list > /dev/null <<EOF
20
+
21
+ if [ "$DISTRO" = "debian" ]; then
22
+ local codename="${VERSION_CODENAME:-bookworm}"
23
+ local components="main contrib non-free non-free-firmware"
24
+ local mirror="${CN_ALIYUN_MIRROR}/debian/"
25
+ local security_mirror="${CN_ALIYUN_MIRROR}/debian-security/"
26
+ sudo tee /etc/apt/sources.list > /dev/null <<EOF
27
+ deb ${mirror} ${codename} ${components}
28
+ deb ${mirror} ${codename}-updates ${components}
29
+ deb ${mirror} ${codename}-backports ${components}
30
+ deb ${security_mirror} ${codename}-security ${components}
31
+ EOF
32
+ else
33
+ local codename="${VERSION_CODENAME:-jammy}"
34
+ local components="main restricted universe multiverse"
35
+ local arch; arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
36
+ if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
37
+ local mirror="${CN_ALIYUN_MIRROR}/ubuntu-ports/"
38
+ else
39
+ local mirror="${CN_ALIYUN_MIRROR}/ubuntu/"
40
+ fi
41
+ sudo tee /etc/apt/sources.list > /dev/null <<EOF
24
42
  deb ${mirror} ${codename} ${components}
25
43
  deb ${mirror} ${codename}-updates ${components}
26
44
  deb ${mirror} ${codename}-backports ${components}
27
45
  deb ${mirror} ${codename}-security ${components}
28
46
  EOF
47
+ fi
48
+
29
49
  print_success "apt mirror set to Aliyun"
30
50
  else
31
51
  print_info "Region: global — using default apt sources"
@@ -1,7 +1,7 @@
1
1
  # network.sh — network region detection, mirror variables, URL probing
2
2
  # Depends-On: colors.sh
3
3
  # Requires-Vars: (none)
4
- # Sets-Vars: $USE_CN_MIRRORS $NETWORK_REGION $CN_CDN_BASE_URL $CN_RUBYGEMS_URL $CN_NODE_MIRROR_URL $CN_NPM_REGISTRY $CN_MISE_INSTALL_URL $CN_RUBY_PRECOMPILED_URL $MISE_INSTALL_URL $NODE_MIRROR_URL $NPM_REGISTRY_URL $RUBY_VERSION_SPEC $DEFAULT_RUBYGEMS_URL $DEFAULT_MISE_INSTALL_URL $DEFAULT_NPM_REGISTRY
4
+ # Sets-Vars: $USE_CN_MIRRORS $NETWORK_REGION $CN_CDN_BASE_URL $CN_ALIYUN_MIRROR $CN_RUBYGEMS_URL $CN_NODE_MIRROR_URL $CN_NPM_REGISTRY $CN_MISE_INSTALL_URL $CN_RUBY_PRECOMPILED_URL $MISE_INSTALL_URL $NODE_MIRROR_URL $NPM_REGISTRY_URL $RUBY_VERSION_SPEC $DEFAULT_RUBYGEMS_URL $DEFAULT_MISE_INSTALL_URL $DEFAULT_NPM_REGISTRY
5
5
  # Include via: @include lib/network.sh
6
6
 
7
7
  # --------------------------------------------------------------------------
@@ -17,9 +17,10 @@ DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
17
17
  DEFAULT_MISE_INSTALL_URL="https://mise.run"
18
18
 
19
19
  CN_CDN_BASE_URL="https://oss.1024code.com"
20
+ CN_ALIYUN_MIRROR="https://mirrors.aliyun.com"
20
21
  CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
21
22
  CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
22
- CN_RUBYGEMS_URL="https://mirrors.aliyun.com/rubygems/"
23
+ CN_RUBYGEMS_URL="${CN_ALIYUN_MIRROR}/rubygems/"
23
24
  CN_NPM_REGISTRY="https://registry.npmmirror.com"
24
25
  CN_NODE_MIRROR_URL="https://cdn.npmmirror.com/binaries/node/"
25
26
  CN_GEM_BASE_URL="${CN_CDN_BASE_URL}/openclacky"
data/scripts/install.sh CHANGED
@@ -129,9 +129,10 @@ DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
129
129
  DEFAULT_MISE_INSTALL_URL="https://mise.run"
130
130
 
131
131
  CN_CDN_BASE_URL="https://oss.1024code.com"
132
+ CN_ALIYUN_MIRROR="https://mirrors.aliyun.com"
132
133
  CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
133
134
  CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
134
- CN_RUBYGEMS_URL="https://mirrors.aliyun.com/rubygems/"
135
+ CN_RUBYGEMS_URL="${CN_ALIYUN_MIRROR}/rubygems/"
135
136
  CN_NPM_REGISTRY="https://registry.npmmirror.com"
136
137
  CN_NODE_MIRROR_URL="https://cdn.npmmirror.com/binaries/node/"
137
138
  CN_GEM_BASE_URL="${CN_CDN_BASE_URL}/openclacky"
@@ -278,20 +279,40 @@ setup_apt_mirror() {
278
279
 
279
280
  if [ "$USE_CN_MIRRORS" = true ]; then
280
281
  print_info "Region: China — configuring Aliyun apt mirror"
281
- local codename="${VERSION_CODENAME:-jammy}"
282
- local components="main restricted universe multiverse"
283
- local arch; arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
284
- if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
285
- local mirror="https://mirrors.aliyun.com/ubuntu-ports/"
286
- else
287
- local mirror="https://mirrors.aliyun.com/ubuntu/"
282
+
283
+ if [ -f /etc/apt/sources.list ]; then
284
+ sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
285
+ print_info "Backed up /etc/apt/sources.list to sources.list.bak"
288
286
  fi
289
- sudo tee /etc/apt/sources.list > /dev/null <<EOF
287
+
288
+ if [ "$DISTRO" = "debian" ]; then
289
+ local codename="${VERSION_CODENAME:-bookworm}"
290
+ local components="main contrib non-free non-free-firmware"
291
+ local mirror="${CN_ALIYUN_MIRROR}/debian/"
292
+ local security_mirror="${CN_ALIYUN_MIRROR}/debian-security/"
293
+ sudo tee /etc/apt/sources.list > /dev/null <<EOF
294
+ deb ${mirror} ${codename} ${components}
295
+ deb ${mirror} ${codename}-updates ${components}
296
+ deb ${mirror} ${codename}-backports ${components}
297
+ deb ${security_mirror} ${codename}-security ${components}
298
+ EOF
299
+ else
300
+ local codename="${VERSION_CODENAME:-jammy}"
301
+ local components="main restricted universe multiverse"
302
+ local arch; arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
303
+ if [ "$arch" = "arm64" ] || [ "$arch" = "aarch64" ]; then
304
+ local mirror="${CN_ALIYUN_MIRROR}/ubuntu-ports/"
305
+ else
306
+ local mirror="${CN_ALIYUN_MIRROR}/ubuntu/"
307
+ fi
308
+ sudo tee /etc/apt/sources.list > /dev/null <<EOF
290
309
  deb ${mirror} ${codename} ${components}
291
310
  deb ${mirror} ${codename}-updates ${components}
292
311
  deb ${mirror} ${codename}-backports ${components}
293
312
  deb ${mirror} ${codename}-security ${components}
294
313
  EOF
314
+ fi
315
+
295
316
  print_success "apt mirror set to Aliyun"
296
317
  else
297
318
  print_info "Region: global — using default apt sources"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
@@ -281,6 +281,7 @@ files:
281
281
  - ".rubocop.yml"
282
282
  - CHANGELOG.md
283
283
  - CODE_OF_CONDUCT.md
284
+ - Dockerfile
284
285
  - LICENSE.txt
285
286
  - README.md
286
287
  - Rakefile
@@ -300,26 +301,12 @@ files:
300
301
  - bin/openclacky
301
302
  - clacky-legacy/clacky.gemspec
302
303
  - clacky-legacy/clarky.gemspec
303
- - docs/HOW-TO-USE-CN.md
304
- - docs/HOW-TO-USE.md
305
304
  - docs/agent-first-ui-design.md
306
- - docs/browser-cdp-native-design.md
307
- - docs/c-end-user-positioning.md
308
305
  - docs/channel-architecture.md
309
- - docs/config.example.yml
310
- - docs/deploy-architecture.md
311
- - docs/deploy_subagent_design.md
312
- - docs/install-script-simplification.md
313
- - docs/memory-architecture.md
314
- - docs/openclacky_cloud_api_reference.md
315
- - docs/security-design.md
316
- - docs/session-management-redesign.md
306
+ - docs/engineering-article.md
317
307
  - docs/session-skill-invocation.md
318
- - docs/system-skill-authoring-guide.md
319
308
  - docs/time_machine_design.md
320
309
  - docs/ui2-architecture.md
321
- - docs/why-developer.md
322
- - docs/why-openclacky.md
323
310
  - homebrew/README.md
324
311
  - homebrew/openclacky.rb
325
312
  - lib/clacky.rb
@@ -1,96 +0,0 @@
1
- # OpenClacky 使用指南
2
-
3
- ## 安装
4
-
5
- ```bash
6
- gem install openclacky
7
- ```
8
-
9
- **系统要求:** Ruby >= 3.1
10
-
11
- ## 快速开始
12
-
13
- ### 1. 启动 Clacky
14
-
15
- ```bash
16
- clacky
17
- ```
18
-
19
- ### 2. 配置 API Key(首次使用)
20
-
21
- 在聊天界面中输入:
22
-
23
- ```
24
- /config
25
- ```
26
-
27
- 然后按提示设置你的 API key:
28
- - **OpenAI**:从 https://platform.openai.com/api-keys 获取
29
- - **Anthropic**:从 https://console.anthropic.com/ 获取
30
- - **MiniMax**:国内推荐,https://platform.minimaxi.com/
31
- - **OpenRouter**:聚合多个 AI 模型,https://openrouter.ai/
32
-
33
- ### 3. 开始对话
34
-
35
- 直接在聊天框输入你的问题或需求:
36
-
37
- ```
38
- 帮我写一个解析 CSV 文件的 Ruby 脚本
39
- ```
40
-
41
- ```
42
- 创建一个网页爬虫提取文章标题
43
- ```
44
-
45
- ## 核心功能
46
-
47
- ### 🎯 自主代理模式
48
- Clacky 可以自动执行复杂任务,内置多种工具:
49
- - **文件操作**:读取、写入、编辑、搜索文件
50
- - **网页访问**:浏览网页、搜索信息
51
- - **代码执行**:运行 shell 命令、测试代码
52
- - **项目管理**:Git 操作、测试、部署
53
-
54
- ### 🔌 技能系统
55
- 使用简写命令调用强大的技能:
56
-
57
- ```
58
- /commit # 智能 Git 提交助手
59
- /gem-release # 自动化 gem 发布流程
60
- ```
61
-
62
- 你还可以在 `.clacky/skills/` 目录创建自己的技能!
63
-
64
- ### 💬 智能记忆管理
65
- - **自动压缩**长对话内容
66
- - **保留上下文**同时降低 token 成本
67
- - **智能总结**对话历史
68
-
69
- ### ⚙️ 简单配置
70
- - 交互式设置向导
71
- - 支持多个 API 提供商
72
- - 成本追踪和使用限制
73
- - 常用场景的智能默认值
74
-
75
- ## 聊天中的常用命令
76
-
77
- ```
78
- /config # 配置 API 设置
79
- /help # 显示可用命令
80
- /skills # 列出可用技能
81
- ```
82
-
83
- ## 为什么选择 OpenClacky?
84
-
85
- ✅ **安装简单** - 一条命令安装,立即开始对话
86
- ✅ **功能强大** - 自主执行复杂任务
87
- ✅ **可扩展** - 为你的工作流创建自定义技能
88
- ✅ **省钱高效** - 智能记忆压缩节省 token 费用
89
- ✅ **多平台** - 支持 OpenAI、Anthropic、MiniMax、OpenRouter 等
90
- ✅ **质量保证** - 367+ 测试用例确保可靠性
91
-
92
- ## 了解更多
93
-
94
- - GitHub:https://github.com/clacky-ai/openclacky
95
- - 问题反馈:https://github.com/clacky-ai/openclacky/issues
96
- - 当前版本:0.7.0