openclacky 0.9.7 → 0.9.9
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 +4 -4
- data/.clacky/skills/gem-release/SKILL.md +16 -1
- data/CHANGELOG.md +51 -0
- data/docs/browser-cdp-native-design.md +195 -0
- data/docs/session-management-redesign.md +202 -0
- data/docs/system-skill-authoring-guide.md +47 -0
- data/lib/clacky/agent/cost_tracker.rb +2 -1
- data/lib/clacky/agent/message_compressor_helper.rb +2 -2
- data/lib/clacky/agent/session_serializer.rb +36 -3
- data/lib/clacky/agent/skill_manager.rb +68 -5
- data/lib/clacky/agent/system_prompt_builder.rb +30 -13
- data/lib/clacky/agent/tool_executor.rb +8 -12
- data/lib/clacky/agent.rb +172 -30
- data/lib/clacky/brand_config.rb +90 -0
- data/lib/clacky/cli.rb +37 -8
- data/lib/clacky/default_parsers/doc_parser.rb +69 -0
- data/lib/clacky/default_parsers/docx_parser.rb +172 -0
- data/lib/clacky/default_parsers/pdf_parser.rb +79 -0
- data/lib/clacky/default_parsers/pptx_parser.rb +140 -0
- data/lib/clacky/default_parsers/xlsx_parser.rb +121 -0
- data/lib/clacky/default_skills/browser-setup/SKILL.md +158 -0
- data/lib/clacky/default_skills/channel-setup/SKILL.md +139 -42
- data/lib/clacky/default_skills/channel-setup/feishu_setup.rb +582 -0
- data/lib/clacky/default_skills/channel-setup/weixin_setup.rb +274 -0
- data/lib/clacky/default_skills/onboard/SKILL.md +132 -6
- data/lib/clacky/default_skills/personal-website/SKILL.md +113 -0
- data/lib/clacky/default_skills/personal-website/publish.rb +214 -0
- data/lib/clacky/default_skills/skill-add/SKILL.md +6 -7
- data/lib/clacky/default_skills/skill-add/scripts/install_from_zip.rb +1 -1
- data/lib/clacky/default_skills/skill-creator/SKILL.md +3 -3
- data/lib/clacky/json_ui_controller.rb +5 -0
- data/lib/clacky/message_format/open_ai.rb +7 -1
- data/lib/clacky/plain_ui_controller.rb +5 -0
- data/lib/clacky/server/browser_manager.rb +308 -0
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +80 -15
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +216 -7
- data/lib/clacky/server/channel/adapters/feishu/message_parser.rb +12 -0
- data/lib/clacky/server/channel/adapters/wecom/adapter.rb +2 -8
- data/lib/clacky/server/channel/adapters/weixin/adapter.rb +391 -0
- data/lib/clacky/server/channel/adapters/weixin/api_client.rb +374 -0
- data/lib/clacky/server/channel/channel_config.rb +6 -0
- data/lib/clacky/server/channel/channel_manager.rb +50 -15
- data/lib/clacky/server/channel/channel_ui_controller.rb +18 -0
- data/lib/clacky/server/channel.rb +1 -0
- data/lib/clacky/server/http_server.rb +210 -45
- data/lib/clacky/server/scheduler.rb +1 -1
- data/lib/clacky/server/session_registry.rb +131 -40
- data/lib/clacky/server/web_ui_controller.rb +21 -2
- data/lib/clacky/session_manager.rb +43 -73
- data/lib/clacky/skill.rb +64 -42
- data/lib/clacky/tools/browser.rb +728 -183
- data/lib/clacky/tools/invoke_skill.rb +18 -11
- data/lib/clacky/tools/shell.rb +16 -7
- data/lib/clacky/tools/web_fetch.rb +3 -1
- data/lib/clacky/ui2/README.md +1 -1
- data/lib/clacky/ui2/components/input_area.rb +5 -2
- data/lib/clacky/ui2/components/welcome_banner.rb +23 -0
- data/lib/clacky/ui2/ui_controller.rb +16 -2
- data/lib/clacky/ui_interface.rb +7 -0
- data/lib/clacky/utils/file_processor.rb +116 -139
- data/lib/clacky/utils/model_pricing.rb +5 -4
- data/lib/clacky/utils/parser_manager.rb +93 -0
- data/lib/clacky/utils/workspace_rules.rb +46 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +696 -62
- data/lib/clacky/web/app.js +181 -51
- data/lib/clacky/web/brand.js +14 -0
- data/lib/clacky/web/channels.js +10 -1
- data/lib/clacky/web/i18n.js +49 -7
- data/lib/clacky/web/index.html +67 -10
- data/lib/clacky/web/onboard.js +2 -1
- data/lib/clacky/web/sessions.js +255 -69
- data/lib/clacky/web/settings.js +113 -1
- data/lib/clacky/web/skills.js +16 -3
- data/lib/clacky/web/tasks.js +11 -6
- data/lib/clacky/web/weixin-qr.html +104 -0
- data/lib/clacky.rb +2 -0
- data/scripts/install.sh +41 -11
- metadata +48 -7
- data/lib/clacky/default_skills/pdf-reader/SKILL.md +0 -90
- data/lib/clacky/utils/file_parser/docx_parser.rb +0 -156
- data/lib/clacky/utils/file_parser/pptx_parser.rb +0 -116
- data/lib/clacky/utils/file_parser/xlsx_parser.rb +0 -95
- data/lib/clacky/utils/file_parser/zip_parser.rb +0 -60
- data/scripts/install_agent_browser.sh +0 -67
data/lib/clacky/web/settings.js
CHANGED
|
@@ -12,6 +12,7 @@ const Settings = (() => {
|
|
|
12
12
|
function open() {
|
|
13
13
|
_load();
|
|
14
14
|
_loadBrand();
|
|
15
|
+
_loadBrowserStatus();
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// ── Data Loading ────────────────────────────────────────────────────────────
|
|
@@ -446,6 +447,80 @@ const Settings = (() => {
|
|
|
446
447
|
}
|
|
447
448
|
}
|
|
448
449
|
|
|
450
|
+
// ── Browser Setup ─────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
async function _loadBrowserStatus() {
|
|
453
|
+
try {
|
|
454
|
+
const res = await fetch("/api/browser/status");
|
|
455
|
+
const data = await res.json();
|
|
456
|
+
const desc = document.getElementById("browser-status-desc");
|
|
457
|
+
const btn = document.getElementById("btn-browser-setup");
|
|
458
|
+
const toggleWrap = document.getElementById("browser-toggle-wrap");
|
|
459
|
+
const toggleInput = document.getElementById("browser-toggle-input");
|
|
460
|
+
|
|
461
|
+
// File doesn't exist → not set up yet
|
|
462
|
+
if (data.enabled === undefined || data.enabled === null) {
|
|
463
|
+
desc.textContent = I18n.t("settings.browser.desc");
|
|
464
|
+
btn.textContent = I18n.t("settings.browser.btn");
|
|
465
|
+
toggleWrap.style.display = "none";
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Configured — show toggle + reconfigure button
|
|
470
|
+
const version = data.chrome_version ? ` (Chrome v${data.chrome_version})` : "";
|
|
471
|
+
desc.textContent = I18n.t(data.enabled ? "settings.browser.configured" : "settings.browser.disabled") + version;
|
|
472
|
+
btn.textContent = I18n.t("settings.browser.btn.reconfigure");
|
|
473
|
+
toggleWrap.style.display = "inline-block";
|
|
474
|
+
toggleInput.checked = data.enabled;
|
|
475
|
+
|
|
476
|
+
// Only bind once
|
|
477
|
+
if (!toggleInput.dataset.bound) {
|
|
478
|
+
toggleInput.dataset.bound = "1";
|
|
479
|
+
toggleInput.addEventListener("change", _toggleBrowser);
|
|
480
|
+
}
|
|
481
|
+
} catch (_) { /* non-critical */ }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async function _toggleBrowser() {
|
|
485
|
+
const toggleInput = document.getElementById("browser-toggle-input");
|
|
486
|
+
toggleInput.disabled = true;
|
|
487
|
+
try {
|
|
488
|
+
const res = await fetch("/api/browser/toggle", { method: "POST" });
|
|
489
|
+
const data = await res.json();
|
|
490
|
+
if (!data.ok) throw new Error(data.error || "toggle failed");
|
|
491
|
+
await _loadBrowserStatus();
|
|
492
|
+
} catch (_) {
|
|
493
|
+
// Revert on failure
|
|
494
|
+
toggleInput.checked = !toggleInput.checked;
|
|
495
|
+
} finally {
|
|
496
|
+
toggleInput.disabled = false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function _setupBrowser() {
|
|
501
|
+
const btn = document.getElementById("btn-browser-setup");
|
|
502
|
+
btn.disabled = true;
|
|
503
|
+
btn.textContent = I18n.t("settings.browser.btn.starting");
|
|
504
|
+
try {
|
|
505
|
+
const res = await fetch("/api/sessions", {
|
|
506
|
+
method: "POST",
|
|
507
|
+
headers: { "Content-Type": "application/json" },
|
|
508
|
+
body: JSON.stringify({ name: "🌐 Browser Setup", source: "setup" })
|
|
509
|
+
});
|
|
510
|
+
const data = await res.json();
|
|
511
|
+
const session = data.session;
|
|
512
|
+
if (!session) throw new Error("No session returned");
|
|
513
|
+
Sessions.add(session);
|
|
514
|
+
Sessions.setTab("setup");
|
|
515
|
+
Sessions.renderList();
|
|
516
|
+
Sessions.setPendingMessage(session.id, "/browser-setup");
|
|
517
|
+
Sessions.select(session.id);
|
|
518
|
+
} catch (e) {
|
|
519
|
+
btn.disabled = false;
|
|
520
|
+
btn.textContent = I18n.t("settings.browser.btn");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
449
524
|
// ── Brand & License ───────────────────────────────────────────────────────────
|
|
450
525
|
|
|
451
526
|
// Whether the server was started with --brand-test (relaxed key validation).
|
|
@@ -494,6 +569,37 @@ const Settings = (() => {
|
|
|
494
569
|
expiresEl.textContent = "—";
|
|
495
570
|
}
|
|
496
571
|
|
|
572
|
+
// Show homepage link if available
|
|
573
|
+
const homepageRow = document.getElementById("brand-status-homepage-row");
|
|
574
|
+
const homepageLink = document.getElementById("brand-status-homepage");
|
|
575
|
+
if (info.homepage_url && homepageRow && homepageLink) {
|
|
576
|
+
homepageLink.href = info.homepage_url;
|
|
577
|
+
homepageLink.textContent = info.homepage_url;
|
|
578
|
+
homepageRow.style.display = "";
|
|
579
|
+
} else if (homepageRow) {
|
|
580
|
+
homepageRow.style.display = "none";
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Show support contact if available
|
|
584
|
+
const contactWrap = document.getElementById("brand-support-contact");
|
|
585
|
+
const contactLink = document.getElementById("brand-support-contact-link");
|
|
586
|
+
if (info.support_contact && contactWrap && contactLink) {
|
|
587
|
+
const contact = info.support_contact;
|
|
588
|
+
contactLink.textContent = contact;
|
|
589
|
+
// Auto-detect mailto / http link
|
|
590
|
+
if (contact.startsWith("http://") || contact.startsWith("https://")) {
|
|
591
|
+
contactLink.href = contact;
|
|
592
|
+
} else if (contact.includes("@")) {
|
|
593
|
+
contactLink.href = "mailto:" + contact;
|
|
594
|
+
} else {
|
|
595
|
+
contactLink.href = "#";
|
|
596
|
+
contactLink.style.cursor = "default";
|
|
597
|
+
}
|
|
598
|
+
contactWrap.style.display = "";
|
|
599
|
+
} else if (contactWrap) {
|
|
600
|
+
contactWrap.style.display = "none";
|
|
601
|
+
}
|
|
602
|
+
|
|
497
603
|
// Show support QR code if available
|
|
498
604
|
const qrContainer = document.getElementById("brand-support-qr");
|
|
499
605
|
const qrImg = document.getElementById("brand-support-qr-img");
|
|
@@ -603,12 +709,15 @@ const Settings = (() => {
|
|
|
603
709
|
function init() {
|
|
604
710
|
document.getElementById("btn-add-model").addEventListener("click", _addModel);
|
|
605
711
|
document.getElementById("btn-rerun-onboard").addEventListener("click", _rerunOnboard);
|
|
712
|
+
document.getElementById("btn-browser-setup").addEventListener("click", _setupBrowser);
|
|
606
713
|
|
|
607
714
|
document.getElementById("btn-settings-activate").addEventListener("click", _activateLicense);
|
|
608
715
|
document.getElementById("settings-license-key").addEventListener("keydown", e => {
|
|
609
716
|
if (e.key === "Enter") _activateLicense();
|
|
610
717
|
});
|
|
611
|
-
document.getElementById("btn-rebind-license").addEventListener("click", () => {
|
|
718
|
+
document.getElementById("btn-rebind-license").addEventListener("click", async () => {
|
|
719
|
+
const confirmed = await Modal.confirm(I18n.t("settings.brand.confirmRebind"));
|
|
720
|
+
if (!confirmed) return;
|
|
612
721
|
// Show the form again so user can enter a new key
|
|
613
722
|
document.getElementById("brand-status-card").style.display = "none";
|
|
614
723
|
document.getElementById("brand-activate-form").style.display = "";
|
|
@@ -617,6 +726,9 @@ const Settings = (() => {
|
|
|
617
726
|
});
|
|
618
727
|
|
|
619
728
|
_initLangBtns();
|
|
729
|
+
|
|
730
|
+
// Re-render model cards when language changes (dynamic HTML, not data-i18n)
|
|
731
|
+
document.addEventListener("langchange", () => _renderCards());
|
|
620
732
|
}
|
|
621
733
|
|
|
622
734
|
// ── QR Code Lightbox ───────────────────────────────────────────────────
|
data/lib/clacky/web/skills.js
CHANGED
|
@@ -241,7 +241,7 @@ const Skills = (() => {
|
|
|
241
241
|
const res = await fetch("/api/sessions", {
|
|
242
242
|
method: "POST",
|
|
243
243
|
headers: { "Content-Type": "application/json" },
|
|
244
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1) })
|
|
244
|
+
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "setup" })
|
|
245
245
|
});
|
|
246
246
|
const data = await res.json();
|
|
247
247
|
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return; }
|
|
@@ -255,6 +255,7 @@ const Skills = (() => {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
Sessions.add(session);
|
|
258
|
+
Sessions.setTab("setup");
|
|
258
259
|
Sessions.renderList();
|
|
259
260
|
Sessions.setPendingMessage(session.id, "/" + name);
|
|
260
261
|
Sessions.select(session.id);
|
|
@@ -559,6 +560,9 @@ const Skills = (() => {
|
|
|
559
560
|
fetch("/api/brand/status")
|
|
560
561
|
.then(res => res.json())
|
|
561
562
|
.then(data => {
|
|
563
|
+
const prevLicensed = _userLicensed;
|
|
564
|
+
const prevActivated = _brandActivated;
|
|
565
|
+
|
|
562
566
|
_brandActivated = data.branded && !data.needs_activation;
|
|
563
567
|
_userLicensed = !!data.user_licensed;
|
|
564
568
|
|
|
@@ -566,6 +570,13 @@ const Skills = (() => {
|
|
|
566
570
|
// license — the tab itself will show an activation prompt in that case.
|
|
567
571
|
const brandTab = $("tab-brand-skills");
|
|
568
572
|
if (brandTab) brandTab.style.display = data.branded ? "" : "none";
|
|
573
|
+
|
|
574
|
+
// Re-render my-skills tab if user_licensed status changed so that the
|
|
575
|
+
// upload buttons appear immediately on first load (they were hidden during
|
|
576
|
+
// the initial synchronous render when _userLicensed was still false).
|
|
577
|
+
if (prevLicensed !== _userLicensed || prevActivated !== _brandActivated) {
|
|
578
|
+
_renderMySkills();
|
|
579
|
+
}
|
|
569
580
|
})
|
|
570
581
|
.catch(() => {
|
|
571
582
|
// On network error, keep whatever is currently shown
|
|
@@ -737,7 +748,7 @@ const Skills = (() => {
|
|
|
737
748
|
const res = await fetch("/api/sessions", {
|
|
738
749
|
method: "POST",
|
|
739
750
|
headers: { "Content-Type": "application/json" },
|
|
740
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1) })
|
|
751
|
+
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "setup" })
|
|
741
752
|
});
|
|
742
753
|
const data = await res.json();
|
|
743
754
|
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return; }
|
|
@@ -748,6 +759,7 @@ const Skills = (() => {
|
|
|
748
759
|
if (!WS.ready) { WS.connect(); Tasks.load(); }
|
|
749
760
|
|
|
750
761
|
Sessions.add(session);
|
|
762
|
+
Sessions.setTab("setup");
|
|
751
763
|
Sessions.renderList();
|
|
752
764
|
Sessions.setPendingMessage(session.id, `/skill-add ${url}`);
|
|
753
765
|
Sessions.select(session.id);
|
|
@@ -766,7 +778,7 @@ const Skills = (() => {
|
|
|
766
778
|
const res = await fetch("/api/sessions", {
|
|
767
779
|
method: "POST",
|
|
768
780
|
headers: { "Content-Type": "application/json" },
|
|
769
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1) })
|
|
781
|
+
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "setup" })
|
|
770
782
|
});
|
|
771
783
|
const data = await res.json();
|
|
772
784
|
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return; }
|
|
@@ -783,6 +795,7 @@ const Skills = (() => {
|
|
|
783
795
|
}
|
|
784
796
|
|
|
785
797
|
Sessions.add(session);
|
|
798
|
+
Sessions.setTab("setup");
|
|
786
799
|
Sessions.renderList();
|
|
787
800
|
Sessions.setPendingMessage(session.id, "/skill-creator");
|
|
788
801
|
Sessions.select(session.id);
|
data/lib/clacky/web/tasks.js
CHANGED
|
@@ -47,11 +47,14 @@ const Tasks = (() => {
|
|
|
47
47
|
|
|
48
48
|
row.innerHTML = `
|
|
49
49
|
<div class="task-col task-col-name">
|
|
50
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
|
|
50
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon task-name-icon">
|
|
51
51
|
<circle cx="12" cy="12" r="10"/>
|
|
52
52
|
<polyline points="12 6 12 12 16 14"/>
|
|
53
53
|
</svg>
|
|
54
|
-
<
|
|
54
|
+
<div class="task-name-info">
|
|
55
|
+
<span class="task-name-text">${escapeHtml(t.name)}</span>
|
|
56
|
+
<span class="task-name-sched">${schedLabel}</span>
|
|
57
|
+
</div>
|
|
55
58
|
</div>
|
|
56
59
|
<div class="task-col task-col-schedule">${schedLabel}</div>
|
|
57
60
|
<div class="task-col task-col-content">${previewText}</div>
|
|
@@ -59,13 +62,13 @@ const Tasks = (() => {
|
|
|
59
62
|
<button class="task-btn task-btn-run" title="${I18n.t("tasks.btn.run")}">
|
|
60
63
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
61
64
|
<polygon points="6 3 20 12 6 21 6 3"/>
|
|
62
|
-
</svg> ${I18n.t("tasks.btn.run")}
|
|
65
|
+
</svg><span class="btn-label"> ${I18n.t("tasks.btn.run")}</span>
|
|
63
66
|
</button>
|
|
64
67
|
<button class="task-btn task-btn-edit" title="${I18n.t("tasks.btn.edit")}">
|
|
65
68
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
66
69
|
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
|
|
67
70
|
<path d="m15 5 4 4"/>
|
|
68
|
-
</svg> ${I18n.t("tasks.btn.edit")}
|
|
71
|
+
</svg><span class="btn-label"> ${I18n.t("tasks.btn.edit")}</span>
|
|
69
72
|
</button>
|
|
70
73
|
<button class="task-btn task-btn-del" title="Delete">
|
|
71
74
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
@@ -198,7 +201,7 @@ const Tasks = (() => {
|
|
|
198
201
|
const res = await fetch("/api/sessions", {
|
|
199
202
|
method: "POST",
|
|
200
203
|
headers: { "Content-Type": "application/json" },
|
|
201
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1) })
|
|
204
|
+
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "setup" })
|
|
202
205
|
});
|
|
203
206
|
const data = await res.json();
|
|
204
207
|
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return; }
|
|
@@ -215,6 +218,7 @@ const Tasks = (() => {
|
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
Sessions.add(session);
|
|
221
|
+
Sessions.setTab("setup");
|
|
218
222
|
Sessions.renderList();
|
|
219
223
|
Sessions.setPendingMessage(session.id, "/cron-task-creator");
|
|
220
224
|
Sessions.select(session.id);
|
|
@@ -229,7 +233,7 @@ const Tasks = (() => {
|
|
|
229
233
|
const res = await fetch("/api/sessions", {
|
|
230
234
|
method: "POST",
|
|
231
235
|
headers: { "Content-Type": "application/json" },
|
|
232
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1) })
|
|
236
|
+
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "setup" })
|
|
233
237
|
});
|
|
234
238
|
const data = await res.json();
|
|
235
239
|
if (!res.ok) { alert("Error creating session: " + (data.error || "unknown")); return; }
|
|
@@ -243,6 +247,7 @@ const Tasks = (() => {
|
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
Sessions.add(session);
|
|
250
|
+
Sessions.setTab("setup");
|
|
246
251
|
Sessions.renderList();
|
|
247
252
|
Sessions.setPendingMessage(session.id, `/cron-task-creator I'm editing ${name} task`);
|
|
248
253
|
Sessions.select(session.id);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>微信扫码登录</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
11
|
+
background: #f5f5f5;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
.card {
|
|
18
|
+
background: #fff;
|
|
19
|
+
border-radius: 16px;
|
|
20
|
+
padding: 40px 48px;
|
|
21
|
+
text-align: center;
|
|
22
|
+
box-shadow: 0 4px 24px rgba(0,0,0,.08);
|
|
23
|
+
max-width: 360px;
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
.logo {
|
|
27
|
+
width: 48px;
|
|
28
|
+
height: 48px;
|
|
29
|
+
background: linear-gradient(135deg, #2dc100, #1aad19);
|
|
30
|
+
border-radius: 12px;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
font-size: 24px;
|
|
35
|
+
margin: 0 auto 16px;
|
|
36
|
+
}
|
|
37
|
+
h1 {
|
|
38
|
+
font-size: 18px;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
color: #111;
|
|
41
|
+
margin-bottom: 6px;
|
|
42
|
+
}
|
|
43
|
+
p {
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
color: #888;
|
|
46
|
+
margin-bottom: 24px;
|
|
47
|
+
line-height: 1.5;
|
|
48
|
+
}
|
|
49
|
+
#qrcode {
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
margin-bottom: 20px;
|
|
53
|
+
}
|
|
54
|
+
#qrcode canvas, #qrcode img {
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
border: 1px solid #eee;
|
|
57
|
+
}
|
|
58
|
+
.hint {
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
color: #bbb;
|
|
61
|
+
}
|
|
62
|
+
.error {
|
|
63
|
+
color: #e53e3e;
|
|
64
|
+
font-size: 13px;
|
|
65
|
+
padding: 12px;
|
|
66
|
+
background: #fff5f5;
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div class="card">
|
|
73
|
+
<div class="logo">微</div>
|
|
74
|
+
<h1>微信扫码登录</h1>
|
|
75
|
+
<p>使用微信扫描下方二维码<br>在手机上点击「确认登录」</p>
|
|
76
|
+
<div id="qrcode"></div>
|
|
77
|
+
<div class="hint">二维码有效期 5 分钟</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
|
|
81
|
+
<script>
|
|
82
|
+
const params = new URLSearchParams(location.search);
|
|
83
|
+
const url = params.get("url");
|
|
84
|
+
const el = document.getElementById("qrcode");
|
|
85
|
+
|
|
86
|
+
if (!url) {
|
|
87
|
+
el.innerHTML = '<div class="error">缺少 url 参数</div>';
|
|
88
|
+
} else {
|
|
89
|
+
try {
|
|
90
|
+
new QRCode(el, {
|
|
91
|
+
text: url,
|
|
92
|
+
width: 220,
|
|
93
|
+
height: 220,
|
|
94
|
+
colorDark: "#111111",
|
|
95
|
+
colorLight: "#ffffff",
|
|
96
|
+
correctLevel: QRCode.CorrectLevel.M
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
el.innerHTML = '<div class="error">二维码生成失败: ' + e.message + '</div>';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
data/lib/clacky.rb
CHANGED
|
@@ -59,6 +59,7 @@ require_relative "clacky/agent"
|
|
|
59
59
|
|
|
60
60
|
require_relative "clacky/server/session_registry"
|
|
61
61
|
require_relative "clacky/server/web_ui_controller"
|
|
62
|
+
require_relative "clacky/server/browser_manager"
|
|
62
63
|
require_relative "clacky/cli"
|
|
63
64
|
|
|
64
65
|
module Clacky
|
|
@@ -66,4 +67,5 @@ module Clacky
|
|
|
66
67
|
class AgentError < StandardError; end
|
|
67
68
|
class RetryableError < StandardError; end # Transient errors that should be retried (5xx, HTML response, rate limit)
|
|
68
69
|
class ToolCallError < AgentError; end # Raised when tool call fails due to invalid parameters
|
|
70
|
+
# BrowserManager singleton: Clacky::BrowserManager.instance
|
|
69
71
|
end
|
data/scripts/install.sh
CHANGED
|
@@ -251,7 +251,7 @@ install_via_gem() {
|
|
|
251
251
|
|
|
252
252
|
if [ $? -eq 0 ]; then
|
|
253
253
|
print_success "${DISPLAY_NAME} installed successfully!"
|
|
254
|
-
|
|
254
|
+
install_chrome_devtools_mcp
|
|
255
255
|
return 0
|
|
256
256
|
else
|
|
257
257
|
print_error "Gem installation failed"
|
|
@@ -305,7 +305,8 @@ install_macos_dependencies() {
|
|
|
305
305
|
print_info "Added mise activation to $SHELL_RC"
|
|
306
306
|
|
|
307
307
|
export PATH="$HOME/.local/bin:$PATH"
|
|
308
|
-
|
|
308
|
+
# Always activate using bash syntax here (this script runs under bash)
|
|
309
|
+
eval "$(~/.local/bin/mise activate bash)"
|
|
309
310
|
|
|
310
311
|
print_success "mise installed successfully"
|
|
311
312
|
else
|
|
@@ -319,8 +320,8 @@ install_macos_dependencies() {
|
|
|
319
320
|
# Install Ruby 3 via mise
|
|
320
321
|
print_info "Installing Ruby 3 via mise..."
|
|
321
322
|
if ~/.local/bin/mise use -g ruby@3; then
|
|
322
|
-
# Reload mise using
|
|
323
|
-
eval "$(~/.local/bin/mise activate
|
|
323
|
+
# Reload mise using bash syntax (this script runs under bash)
|
|
324
|
+
eval "$(~/.local/bin/mise activate bash)"
|
|
324
325
|
print_success "Ruby 3 installed successfully"
|
|
325
326
|
else
|
|
326
327
|
print_error "Failed to install Ruby 3"
|
|
@@ -376,7 +377,8 @@ install_ubuntu_dependencies() {
|
|
|
376
377
|
print_info "Added mise activation to $SHELL_RC"
|
|
377
378
|
|
|
378
379
|
export PATH="$HOME/.local/bin:$PATH"
|
|
379
|
-
|
|
380
|
+
# Always activate using bash syntax here (this script runs under bash)
|
|
381
|
+
eval "$(~/.local/bin/mise activate bash)"
|
|
380
382
|
|
|
381
383
|
print_success "mise installed successfully"
|
|
382
384
|
else
|
|
@@ -390,8 +392,8 @@ install_ubuntu_dependencies() {
|
|
|
390
392
|
# Install Ruby 3 via mise
|
|
391
393
|
print_info "Installing Ruby 3 via mise..."
|
|
392
394
|
if ~/.local/bin/mise use -g ruby@3; then
|
|
393
|
-
# Reload mise using
|
|
394
|
-
eval "$(~/.local/bin/mise activate
|
|
395
|
+
# Reload mise using bash syntax (this script runs under bash)
|
|
396
|
+
eval "$(~/.local/bin/mise activate bash)"
|
|
395
397
|
print_success "Ruby 3 installed successfully"
|
|
396
398
|
else
|
|
397
399
|
print_error "Failed to install Ruby 3"
|
|
@@ -634,10 +636,38 @@ main() {
|
|
|
634
636
|
|
|
635
637
|
# Install agent-browser (browser automation tool)
|
|
636
638
|
# This step is optional — failures are silently skipped with a hint.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
639
|
+
install_chrome_devtools_mcp() {
|
|
640
|
+
print_step "Installing chrome-devtools-mcp..."
|
|
641
|
+
|
|
642
|
+
# 尝试找到 npm;如果没有,通过 mise 安装 Node.js
|
|
643
|
+
if ! command_exists npm; then
|
|
644
|
+
local mise_bin=""
|
|
645
|
+
if command_exists mise; then
|
|
646
|
+
mise_bin="mise"
|
|
647
|
+
elif [ -x "$HOME/.local/bin/mise" ]; then
|
|
648
|
+
mise_bin="$HOME/.local/bin/mise"
|
|
649
|
+
fi
|
|
650
|
+
|
|
651
|
+
if [ -n "$mise_bin" ]; then
|
|
652
|
+
print_info "Installing Node.js via mise..."
|
|
653
|
+
"$mise_bin" install node@22 > /dev/null 2>&1 || true
|
|
654
|
+
"$mise_bin" use -g node@22 > /dev/null 2>&1 || true
|
|
655
|
+
eval "$("$mise_bin" activate bash 2>/dev/null)" 2>/dev/null || true
|
|
656
|
+
fi
|
|
657
|
+
fi
|
|
658
|
+
|
|
659
|
+
if ! command_exists npm; then
|
|
660
|
+
print_warning "Node.js/npm not found. Browser automation will not be available."
|
|
661
|
+
print_info "To enable browser support, install Node.js and run: npm install -g chrome-devtools-mcp"
|
|
662
|
+
return 0
|
|
663
|
+
fi
|
|
664
|
+
|
|
665
|
+
if npm install -g chrome-devtools-mcp > /dev/null 2>&1; then
|
|
666
|
+
print_success "chrome-devtools-mcp installed"
|
|
667
|
+
else
|
|
668
|
+
print_warning "chrome-devtools-mcp installation failed. Browser automation may not work."
|
|
669
|
+
print_info "To install manually: npm install -g chrome-devtools-mcp"
|
|
670
|
+
fi
|
|
641
671
|
}
|
|
642
672
|
|
|
643
673
|
# Post-installation information
|
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: 0.9.
|
|
4
|
+
version: 0.9.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-multipart
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: thor
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -191,6 +205,20 @@ dependencies:
|
|
|
191
205
|
- - "~>"
|
|
192
206
|
- !ruby/object:Gem::Version
|
|
193
207
|
version: '3.0'
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: chunky_png
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - "~>"
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: '1.4'
|
|
215
|
+
type: :runtime
|
|
216
|
+
prerelease: false
|
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - "~>"
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: '1.4'
|
|
194
222
|
description: OpenClacky is a Ruby CLI tool for interacting with AI models via OpenAI-compatible
|
|
195
223
|
APIs. It provides chat functionality and autonomous AI agent capabilities with tool
|
|
196
224
|
use.
|
|
@@ -221,13 +249,16 @@ files:
|
|
|
221
249
|
- docs/HOW-TO-USE-CN.md
|
|
222
250
|
- docs/HOW-TO-USE.md
|
|
223
251
|
- docs/agent-first-ui-design.md
|
|
252
|
+
- docs/browser-cdp-native-design.md
|
|
224
253
|
- docs/channel-architecture.md
|
|
225
254
|
- docs/config.example.yml
|
|
226
255
|
- docs/deploy_subagent_design.md
|
|
227
256
|
- docs/memory-architecture.md
|
|
228
257
|
- docs/openclacky_cloud_api_reference.md
|
|
229
258
|
- docs/security-design.md
|
|
259
|
+
- docs/session-management-redesign.md
|
|
230
260
|
- docs/session-skill-invocation.md
|
|
261
|
+
- docs/system-skill-authoring-guide.md
|
|
231
262
|
- docs/time_machine_design.md
|
|
232
263
|
- docs/ui2-architecture.md
|
|
233
264
|
- docs/why-developer.md
|
|
@@ -262,7 +293,15 @@ files:
|
|
|
262
293
|
- lib/clacky/default_agents/coding/system_prompt.md
|
|
263
294
|
- lib/clacky/default_agents/general/profile.yml
|
|
264
295
|
- lib/clacky/default_agents/general/system_prompt.md
|
|
296
|
+
- lib/clacky/default_parsers/doc_parser.rb
|
|
297
|
+
- lib/clacky/default_parsers/docx_parser.rb
|
|
298
|
+
- lib/clacky/default_parsers/pdf_parser.rb
|
|
299
|
+
- lib/clacky/default_parsers/pptx_parser.rb
|
|
300
|
+
- lib/clacky/default_parsers/xlsx_parser.rb
|
|
301
|
+
- lib/clacky/default_skills/browser-setup/SKILL.md
|
|
265
302
|
- lib/clacky/default_skills/channel-setup/SKILL.md
|
|
303
|
+
- lib/clacky/default_skills/channel-setup/feishu_setup.rb
|
|
304
|
+
- lib/clacky/default_skills/channel-setup/weixin_setup.rb
|
|
266
305
|
- lib/clacky/default_skills/code-explorer/SKILL.md
|
|
267
306
|
- lib/clacky/default_skills/cron-task-creator/SKILL.md
|
|
268
307
|
- lib/clacky/default_skills/cron-task-creator/evals/evals.json
|
|
@@ -282,7 +321,8 @@ files:
|
|
|
282
321
|
- lib/clacky/default_skills/new/scripts/create_rails_project.sh
|
|
283
322
|
- lib/clacky/default_skills/new/scripts/rails_env_checker.sh
|
|
284
323
|
- lib/clacky/default_skills/onboard/SKILL.md
|
|
285
|
-
- lib/clacky/default_skills/
|
|
324
|
+
- lib/clacky/default_skills/personal-website/SKILL.md
|
|
325
|
+
- lib/clacky/default_skills/personal-website/publish.rb
|
|
286
326
|
- lib/clacky/default_skills/product-help/SKILL.md
|
|
287
327
|
- lib/clacky/default_skills/recall-memory/SKILL.md
|
|
288
328
|
- lib/clacky/default_skills/skill-add/SKILL.md
|
|
@@ -310,6 +350,7 @@ files:
|
|
|
310
350
|
- lib/clacky/message_history.rb
|
|
311
351
|
- lib/clacky/plain_ui_controller.rb
|
|
312
352
|
- lib/clacky/providers.rb
|
|
353
|
+
- lib/clacky/server/browser_manager.rb
|
|
313
354
|
- lib/clacky/server/channel.rb
|
|
314
355
|
- lib/clacky/server/channel/adapters/base.rb
|
|
315
356
|
- lib/clacky/server/channel/adapters/feishu/adapter.rb
|
|
@@ -320,6 +361,8 @@ files:
|
|
|
320
361
|
- lib/clacky/server/channel/adapters/wecom/adapter.rb
|
|
321
362
|
- lib/clacky/server/channel/adapters/wecom/media_downloader.rb
|
|
322
363
|
- lib/clacky/server/channel/adapters/wecom/ws_client.rb
|
|
364
|
+
- lib/clacky/server/channel/adapters/weixin/adapter.rb
|
|
365
|
+
- lib/clacky/server/channel/adapters/weixin/api_client.rb
|
|
323
366
|
- lib/clacky/server/channel/channel_config.rb
|
|
324
367
|
- lib/clacky/server/channel/channel_manager.rb
|
|
325
368
|
- lib/clacky/server/channel/channel_ui_controller.rb
|
|
@@ -380,18 +423,16 @@ files:
|
|
|
380
423
|
- lib/clacky/utils/encoding.rb
|
|
381
424
|
- lib/clacky/utils/environment_detector.rb
|
|
382
425
|
- lib/clacky/utils/file_ignore_helper.rb
|
|
383
|
-
- lib/clacky/utils/file_parser/docx_parser.rb
|
|
384
|
-
- lib/clacky/utils/file_parser/pptx_parser.rb
|
|
385
|
-
- lib/clacky/utils/file_parser/xlsx_parser.rb
|
|
386
|
-
- lib/clacky/utils/file_parser/zip_parser.rb
|
|
387
426
|
- lib/clacky/utils/file_processor.rb
|
|
388
427
|
- lib/clacky/utils/gitignore_parser.rb
|
|
389
428
|
- lib/clacky/utils/limit_stack.rb
|
|
390
429
|
- lib/clacky/utils/logger.rb
|
|
391
430
|
- lib/clacky/utils/model_pricing.rb
|
|
431
|
+
- lib/clacky/utils/parser_manager.rb
|
|
392
432
|
- lib/clacky/utils/path_helper.rb
|
|
393
433
|
- lib/clacky/utils/string_matcher.rb
|
|
394
434
|
- lib/clacky/utils/trash_directory.rb
|
|
435
|
+
- lib/clacky/utils/workspace_rules.rb
|
|
395
436
|
- lib/clacky/version.rb
|
|
396
437
|
- lib/clacky/web/app.css
|
|
397
438
|
- lib/clacky/web/app.js
|
|
@@ -409,9 +450,9 @@ files:
|
|
|
409
450
|
- lib/clacky/web/tasks.js
|
|
410
451
|
- lib/clacky/web/theme.js
|
|
411
452
|
- lib/clacky/web/version.js
|
|
453
|
+
- lib/clacky/web/weixin-qr.html
|
|
412
454
|
- lib/clacky/web/ws.js
|
|
413
455
|
- scripts/install.sh
|
|
414
|
-
- scripts/install_agent_browser.sh
|
|
415
456
|
- scripts/uninstall.sh
|
|
416
457
|
- sig/clacky.rbs
|
|
417
458
|
homepage: https://github.com/yafeilee/clacky
|