shadok-ai 0.17.36 → 0.17.37
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.
- package/README.md +11 -0
- package/package.json +1 -1
- package/public/index.html +93 -7
package/README.md
CHANGED
|
@@ -254,6 +254,9 @@ Enter to skip; you can add it later from the web UI).
|
|
|
254
254
|
with a command goes to the agent bare, without shadok's context line or
|
|
255
255
|
ledger update ahead of it, since Claude Code reads a command only when `/`
|
|
256
256
|
comes first.
|
|
257
|
+
The ✦ button beside the paperclip opens the same menu without typing, like
|
|
258
|
+
an emoji picker: the chosen skill goes in **front** of what you already
|
|
259
|
+
wrote (which becomes its argument), replacing a command already there.
|
|
257
260
|
- **Files you hand an agent** — paste or drop a file into the composer, or pick
|
|
258
261
|
one with the 📎 button beside it (an image, a PDF, a CSV…), and it becomes a **chip** above the text box: a
|
|
259
262
|
thumbnail for an image, a named tag for anything else, each with a ✕ to
|
|
@@ -301,6 +304,14 @@ Enter to skip; you can add it later from the web UI).
|
|
|
301
304
|
- **The detail panel.** Its numbers, where it runs (group by group: each
|
|
302
305
|
agent, its schedule, its runs), the secrets agents running it get, and its
|
|
303
306
|
feedback.
|
|
307
|
+
- **Creating.** The new-skill form asks for a **name** (what people see) and
|
|
308
|
+
a **technical name** (what agents call it, `/name:name`), proposed from the
|
|
309
|
+
name until you type your own. Choose it then: it is the skill's repository
|
|
310
|
+
and plugin name, so it cannot be changed afterwards. A name already taken
|
|
311
|
+
gets a number (`-2`, `-3`…), and the form says so before you create it.
|
|
312
|
+
- **Renaming.** Click a skill's name at the top of its editor to rename it
|
|
313
|
+
(Enter saves, Escape cancels). Only the displayed name changes: agents keep
|
|
314
|
+
calling it by its technical name, so attached agents are not disturbed.
|
|
304
315
|
- **Sharing.** A skill is shared with **only me**, with **one or more
|
|
305
316
|
groups**, or with **everyone**. Sharing keeps lists short; it is not a
|
|
306
317
|
lock. "Only me" hides the skill from everyone else. A group skill is offered
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -1771,6 +1771,7 @@
|
|
|
1771
1771
|
.slash-menu[hidden] { display: none; }
|
|
1772
1772
|
.slash-item { display: flex; gap: 10px; align-items: baseline; width: 100%; padding: 7px 10px; border: 0; border-radius: 6px; background: transparent; color: var(--text); text-align: left; cursor: pointer; font: inherit; }
|
|
1773
1773
|
.slash-item[aria-selected="true"] { background: var(--amber-soft); }
|
|
1774
|
+
.slash-empty { padding: 8px 10px; color: var(--text-dim); font-size: 13px; }
|
|
1774
1775
|
.slash-name { font-family: var(--mono); font-size: 13px; flex: none; }
|
|
1775
1776
|
.slash-desc { color: var(--text-dim); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
1776
1777
|
.cmp-attach {
|
|
@@ -2489,6 +2490,11 @@
|
|
|
2489
2490
|
.fp-col.fp-doc { padding: 22px 26px; background: var(--bg); }
|
|
2490
2491
|
.fp-doc-head { display: flex; gap: 14px; align-items: center; margin-bottom: 6px; }
|
|
2491
2492
|
.fp-doc-head h1 { font-size: 20px; font-weight: 650; }
|
|
2493
|
+
/* The skill's name, renamed in place: reads as a title until hovered/focused. */
|
|
2494
|
+
.fp-main .fp-doc-head input.fp-title-in { width: 100%; box-sizing: border-box; font: inherit; font-size: 20px; font-weight: 650; color: var(--text);
|
|
2495
|
+
background: transparent; border: 1px solid transparent; border-radius: 6px; padding: 2px 6px; margin-left: -7px; }
|
|
2496
|
+
.fp-main .fp-doc-head input.fp-title-in:hover { border-color: var(--line); }
|
|
2497
|
+
.fp-main .fp-doc-head input.fp-title-in:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }
|
|
2492
2498
|
.fp-doc-head .fp-sub { margin-top: 4px; }
|
|
2493
2499
|
.fp-doc textarea { border-radius: 10px; padding: 10px 12px; background: var(--bg-raised); border: 1px solid var(--line); color: var(--text); font-family: var(--sans); }
|
|
2494
2500
|
.fp-doc textarea:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }
|
|
@@ -3752,6 +3758,11 @@
|
|
|
3752
3758
|
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M21 11.5l-8.6 8.6a5.5 5.5 0 0 1-7.8-7.8l8.6-8.6a3.7 3.7 0 0 1 5.2 5.2l-8.6 8.6a1.8 1.8 0 0 1-2.6-2.6l7.9-7.9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
3753
3759
|
</button>
|
|
3754
3760
|
<input type="file" id="attachInput" multiple hidden>
|
|
3761
|
+
<!-- Pick a skill like an emoji: opens the `/` menu with every skill attached
|
|
3762
|
+
to this agent; the choice goes in at the head of the prompt. -->
|
|
3763
|
+
<button type="button" id="skillBtn" class="cmp-attach" title="Add a skill to this prompt" aria-label="Add a skill" aria-haspopup="listbox">
|
|
3764
|
+
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z M18.5 15.5l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
|
3765
|
+
</button>
|
|
3755
3766
|
<div id="slashMenu" class="slash-menu" role="listbox" aria-label="Commands" hidden></div>
|
|
3756
3767
|
<textarea id="promptInput" rows="1"
|
|
3757
3768
|
placeholder="Write your prompt — Enter to send, Shift+Enter for a new line"></textarea>
|
|
@@ -9952,9 +9963,19 @@
|
|
|
9952
9963
|
slash.cache.set(sessionId, { at: Date.now(), list });
|
|
9953
9964
|
return list;
|
|
9954
9965
|
}
|
|
9955
|
-
function closeSlash() { slashMenu.hidden = true; slash.list = []; }
|
|
9966
|
+
function closeSlash() { slashMenu.hidden = true; slash.list = []; slash.picker = false; }
|
|
9956
9967
|
function paintSlash() {
|
|
9957
9968
|
slashMenu.textContent = "";
|
|
9969
|
+
if (slash.picker && !slash.list.length) {
|
|
9970
|
+
// Only ATTACHED skills run (they load as a plugin at spawn), so an empty
|
|
9971
|
+
// menu says where to attach one rather than staying silently shut.
|
|
9972
|
+
const d = document.createElement("div");
|
|
9973
|
+
d.className = "slash-empty";
|
|
9974
|
+
d.textContent = "No skill attached to this agent yet. Attach one from the Skills page, then reload the agent.";
|
|
9975
|
+
slashMenu.appendChild(d);
|
|
9976
|
+
slashMenu.hidden = false;
|
|
9977
|
+
return;
|
|
9978
|
+
}
|
|
9958
9979
|
slash.list.forEach((c, i) => {
|
|
9959
9980
|
const b = document.createElement("button");
|
|
9960
9981
|
b.type = "button";
|
|
@@ -9985,16 +10006,34 @@
|
|
|
9985
10006
|
function pickSlash(i) {
|
|
9986
10007
|
const c = slash.list[i];
|
|
9987
10008
|
if (!c) return;
|
|
9988
|
-
|
|
10009
|
+
// From the picker the prompt may already hold text: a `/command` only runs at
|
|
10010
|
+
// the very start (isSlashCommand), so the skill goes in FRONT and the text
|
|
10011
|
+
// becomes its argument; a command already there is replaced, not stacked.
|
|
10012
|
+
const rest = slash.picker ? promptInput.value.replace(/^\/[A-Za-z][\w:.-]*\s*/, "") : "";
|
|
10013
|
+
promptInput.value = "/" + c.name + " " + rest;
|
|
9989
10014
|
promptInput.setSelectionRange(promptInput.value.length, promptInput.value.length);
|
|
9990
10015
|
closeSlash();
|
|
9991
10016
|
promptInput.dispatchEvent(new Event("input"));
|
|
9992
10017
|
promptInput.focus();
|
|
9993
10018
|
}
|
|
9994
10019
|
promptInput.addEventListener("input", updateSlash);
|
|
10020
|
+
const skillBtn = $("skillBtn");
|
|
10021
|
+
// mousedown would blur the textarea, whose blur handler closes the menu.
|
|
10022
|
+
skillBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
|
10023
|
+
skillBtn.addEventListener("click", async () => {
|
|
10024
|
+
if (!active) return;
|
|
10025
|
+
if (!slashMenu.hidden && slash.picker) { closeSlash(); return; }
|
|
10026
|
+
const all = await slashCommands(active.sessionId);
|
|
10027
|
+
slash.list = all.slice(0, 40);
|
|
10028
|
+
slash.at = 0;
|
|
10029
|
+
slash.picker = true;
|
|
10030
|
+
paintSlash();
|
|
10031
|
+
promptInput.focus(); // arrows / Enter / Escape go through its keydown
|
|
10032
|
+
});
|
|
9995
10033
|
promptInput.addEventListener("blur", () => setTimeout(closeSlash, 100));
|
|
9996
10034
|
|
|
9997
10035
|
promptInput.addEventListener("keydown", (e) => {
|
|
10036
|
+
if (!slashMenu.hidden && slash.picker && !slash.list.length && e.key === "Escape") { e.preventDefault(); closeSlash(); return; }
|
|
9998
10037
|
if (!slashMenu.hidden && slash.list.length) {
|
|
9999
10038
|
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
10000
10039
|
e.preventDefault();
|
|
@@ -11685,8 +11724,31 @@
|
|
|
11685
11724
|
const scroll = facEl("div", { class: "fp-scroll" });
|
|
11686
11725
|
m.appendChild(scroll);
|
|
11687
11726
|
const title = facEl("input", { type: "text", id: "fpTitle", placeholder: "Daily marketing summary", autocomplete: "off" });
|
|
11688
|
-
|
|
11689
|
-
|
|
11727
|
+
// The technical name agents call it by (`/<name>:<name>`): proposed from the
|
|
11728
|
+
// name above until the person types their own, then left alone. Unlike the
|
|
11729
|
+
// displayed name it can NOT be changed later (it is the repo, the plugin and
|
|
11730
|
+
// every attachment's key), so it is chosen here or never.
|
|
11731
|
+
const slug = facEl("input", { type: "text", id: "fpSlug", placeholder: "daily-marketing-summary", autocomplete: "off", spellcheck: "false", maxlength: "40", style: "font-family:var(--mono)" });
|
|
11732
|
+
let slugTouched = false;
|
|
11733
|
+
const slugHint = facEl("div", { class: "fp-hint" });
|
|
11734
|
+
const paintSlug = () => {
|
|
11735
|
+
const sl = facSlug(slug.value);
|
|
11736
|
+
const taken = sl && fac.data && fac.data.skills.some((x) => x.name === sl);
|
|
11737
|
+
slugHint.textContent = !sl ? "Lowercase letters, digits and dashes. It cannot be changed later."
|
|
11738
|
+
: sl.length < 2 ? "At least 2 characters."
|
|
11739
|
+
: taken ? `${sl} is already used: it will be saved as ${sl}-2 (or the next free number).`
|
|
11740
|
+
: `Agents will call it /${sl}:${sl}. It cannot be changed later.`;
|
|
11741
|
+
};
|
|
11742
|
+
title.addEventListener("input", () => { if (!slugTouched) slug.value = facSlug(title.value); paintSlug(); });
|
|
11743
|
+
slug.addEventListener("input", () => {
|
|
11744
|
+
// Light clean-up while typing (a trailing dash must survive, "my-" is on its
|
|
11745
|
+
// way to "my-skill"); the full facSlug runs when it is sent.
|
|
11746
|
+
const v = slug.value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+/, "").replace(/--+/g, "-");
|
|
11747
|
+
if (v !== slug.value) slug.value = v;
|
|
11748
|
+
slugTouched = !!v; // emptied → follows the name again (at its next edit, or on Create)
|
|
11749
|
+
paintSlug();
|
|
11750
|
+
});
|
|
11751
|
+
paintSlug();
|
|
11690
11752
|
const desc = facEl("textarea", { rows: "4", id: "fpDescIn",
|
|
11691
11753
|
placeholder: "Writes a short summary of yesterday's marketing numbers (traffic, signups, campaigns) and posts it in #marketing. Use it every morning, or when someone asks for the marketing summary." });
|
|
11692
11754
|
const tags = facEl("input", { type: "text", placeholder: "reporting, slack" });
|
|
@@ -11702,7 +11764,7 @@
|
|
|
11702
11764
|
const v = (scroll.querySelector('input[name="fpScope"]:checked') || {}).value || "me";
|
|
11703
11765
|
const scope = v === "groups" ? { groups: [...ids] } : v;
|
|
11704
11766
|
try {
|
|
11705
|
-
const out = await facCall("POST", "/factory/skills", { title: title.value.trim(), description: desc.value.trim(), scope, tags: tags.value });
|
|
11767
|
+
const out = await facCall("POST", "/factory/skills", { title: title.value.trim(), name: facSlug(slug.value) || facSlug(title.value) || undefined, description: desc.value.trim(), scope, tags: tags.value });
|
|
11706
11768
|
await facLoad();
|
|
11707
11769
|
facEdit(out.skill.id, true);
|
|
11708
11770
|
} catch (e) { facMsg(e.message, true); }
|
|
@@ -11711,7 +11773,9 @@
|
|
|
11711
11773
|
facEl("div", { class: "fp-bar" }, [facEl("button", { class: "fp-link", text: "← Skills", onclick: () => facGo("list") })]),
|
|
11712
11774
|
facEl("h1", { text: "New skill" }),
|
|
11713
11775
|
facEl("div", { class: "fp-sub", text: "A short set of instructions an agent follows to do one job." }),
|
|
11714
|
-
facEl("div", { class: "fp-field" }, [facEl("label", { for: "fpTitle", text: "Name" }), title,
|
|
11776
|
+
facEl("div", { class: "fp-field" }, [facEl("label", { for: "fpTitle", text: "Name" }), title,
|
|
11777
|
+
facEl("div", { class: "fp-hint", text: "What people see. You can rename it any time." })]),
|
|
11778
|
+
facEl("div", { class: "fp-field" }, [facEl("label", { for: "fpSlug", text: "Technical name" }), slug, slugHint]),
|
|
11715
11779
|
facEl("div", { class: "fp-field" }, [facEl("label", { for: "fpDescIn", text: "What does it do, and when should it be used?" }), desc,
|
|
11716
11780
|
facEl("div", { class: "fp-hint", text: "An agent reads this to decide to use the skill. You can refine it later." })]),
|
|
11717
11781
|
facEl("div", { class: "fp-field" }, [facEl("span", { class: "fp-label", text: "Shared with" }),
|
|
@@ -11830,7 +11894,29 @@
|
|
|
11830
11894
|
const ed = fac.ed;
|
|
11831
11895
|
const dirty = () => ed.desc !== ed.saved.desc || ed.body !== ed.saved.body;
|
|
11832
11896
|
// Center content per tab
|
|
11833
|
-
|
|
11897
|
+
// The name is edited in place: it is only the DISPLAY title (PUT …/meta), the
|
|
11898
|
+
// technical name agents call it by (`<name>:<name>`) stays put, so attached
|
|
11899
|
+
// agents and typed `/` commands keep working after a rename.
|
|
11900
|
+
const nameIn = facEl("input", { type: "text", class: "fp-title-in", value: facTitle(s), maxlength: "80",
|
|
11901
|
+
"aria-label": "Skill name", title: `Click to rename. Agents still call it ${s.name}:${s.name}.` });
|
|
11902
|
+
const saveName = async () => {
|
|
11903
|
+
const v = nameIn.value.trim();
|
|
11904
|
+
if (!v) { nameIn.value = facTitle(s); return; }
|
|
11905
|
+
if (v === facTitle(s)) return;
|
|
11906
|
+
try {
|
|
11907
|
+
await facCall("PUT", `/factory/skills/${s.id}/meta`, { title: v });
|
|
11908
|
+
await facLoad();
|
|
11909
|
+
// A blur caused by clicking another skill in the list: that click already
|
|
11910
|
+
// rendered the other one — repainting here would race it.
|
|
11911
|
+
if (fac.view === "edit" && fac.sel && fac.sel.id === s.id) facRender();
|
|
11912
|
+
} catch (e) { nameIn.value = facTitle(s); facMsg(e.message, true); }
|
|
11913
|
+
};
|
|
11914
|
+
nameIn.addEventListener("keydown", (e) => {
|
|
11915
|
+
if (e.key === "Enter") { e.preventDefault(); nameIn.blur(); }
|
|
11916
|
+
if (e.key === "Escape") { nameIn.value = facTitle(s); nameIn.blur(); }
|
|
11917
|
+
});
|
|
11918
|
+
nameIn.addEventListener("blur", saveName);
|
|
11919
|
+
center.appendChild(facEl("div", { class: "fp-doc-head" }, [facAvatar(s, "lg"), facEl("div", { style: "flex:1;min-width:0" }, [nameIn,
|
|
11834
11920
|
facEl("div", { class: "fp-sub" }, [
|
|
11835
11921
|
facStatus(s).includes("draft") ? facEl("span", { class: "fp-ver draft", text: "draft changes" }) : facEl("span", { class: "fp-ver live", text: "saved" }),
|
|
11836
11922
|
" ", ...facSharedLabels(s.owner).map((x) => facEl("span", { class: "fp-gp", text: x }))])])]));
|