@xmoon76/dsh-pi-tui 0.1.7 → 0.1.8

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 (2) hide show
  1. package/dist/index.mjs +114 -63
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -29,7 +29,7 @@ import { eastAsianWidth } from "get-east-asian-width";
29
29
  import { performance } from "node:perf_hooks";
30
30
  import { fileURLToPath } from "node:url";
31
31
  import { EventEmitter } from "events";
32
- import { isUserInvocable } from "@deepseek-ai/dsh-skill";
32
+ import { isUserInvocable, renderSkillContent } from "@deepseek-ai/dsh-skill";
33
33
  //#region src/present.ts
34
34
  /** Figma row titles per variant (design literals, not translatable copy). */
35
35
  const VARIANT_TITLES = {
@@ -12859,10 +12859,10 @@ var TaskBrowserPanel = class {
12859
12859
  this.searchInput.invalidate();
12860
12860
  }
12861
12861
  handleInput(data) {
12862
- const isNavUp = data === "\x1B[A" || !this.searchEnabled && data === "k";
12863
- const isNavDown = data === "\x1B[B" || !this.searchEnabled && data === "j";
12864
- const isPage = data === "\x1B[5~" || data === "\x1B[6~";
12865
- if (!isNavUp && !isNavDown && !isPage && data !== "\r" && data !== "\n" && data !== "\x1B" && this.searchEnabled) {
12862
+ const isNavUp = matchesKey(data, "up") || !this.searchEnabled && data === "k";
12863
+ const isNavDown = matchesKey(data, "down") || !this.searchEnabled && data === "j";
12864
+ const isPage = matchesKey(data, "pageUp") || matchesKey(data, "pageDown");
12865
+ if (!isNavUp && !isNavDown && !isPage && !matchesKey(data, "enter") && !matchesKey(data, "escape") && this.searchEnabled) {
12866
12866
  this.searchInput.handleInput(data);
12867
12867
  this.applyFilter(this.searchInput.getValue() ?? "");
12868
12868
  return;
@@ -12881,17 +12881,17 @@ var TaskBrowserPanel = class {
12881
12881
  }
12882
12882
  if (isPage) {
12883
12883
  if (this.filtered.length === 0) return;
12884
- if (data === "\x1B[5~") this.selected = Math.max(0, this.selected - this.maxVisible);
12884
+ if (matchesKey(data, "pageUp")) this.selected = Math.max(0, this.selected - this.maxVisible);
12885
12885
  else this.selected = Math.min(this.filtered.length - 1, this.selected + this.maxVisible);
12886
12886
  this.ensureVisible();
12887
12887
  return;
12888
12888
  }
12889
- if (data === "\r" || data === "\n") {
12889
+ if (matchesKey(data, "enter")) {
12890
12890
  const item = this.filtered[this.selected];
12891
12891
  if (item !== void 0) this.onSelect(item.value);
12892
12892
  return;
12893
12893
  }
12894
- if (data === "\x1B") this.onCancel();
12894
+ if (matchesKey(data, "escape")) this.onCancel();
12895
12895
  }
12896
12896
  ensureVisible() {
12897
12897
  if (this.selected < this.scroll) this.scroll = this.selected;
@@ -13493,8 +13493,9 @@ var QuestionFlow = class {
13493
13493
  if (draft === void 0) return;
13494
13494
  const text = value.trim();
13495
13495
  draft.custom = text;
13496
- if (text === "") draft.skipped = true;
13497
- else {
13496
+ if (text === "") {
13497
+ if (draft.selected.size === 0) draft.skipped = true;
13498
+ } else {
13498
13499
  if (this.questions[this.tab]?.multiSelect !== true) draft.selected.clear();
13499
13500
  draft.skipped = false;
13500
13501
  }
@@ -13526,44 +13527,34 @@ var QuestionFlow = class {
13526
13527
  handleInput(data) {
13527
13528
  if (data === "\0") return;
13528
13529
  if (this.editingOther) {
13529
- if (data === "\r" || data === "\n") this.commitOther(this.otherInput.getValue());
13530
- else if (data === "\x1B") this.exitOther();
13531
- else if (data === "\x1B[5~" || data === "\x1B[6~") this.scrollBody(data === "\x1B[5~" ? -1 : 1);
13532
- else if (data === "\x1B[D") {
13530
+ if (matchesKey(data, "enter")) this.commitOther(this.otherInput.getValue());
13531
+ else if (matchesKey(data, "escape")) this.exitOther();
13532
+ else if (matchesKey(data, "pageUp")) this.scrollBody(-1);
13533
+ else if (matchesKey(data, "pageDown")) this.scrollBody(1);
13534
+ else if (matchesKey(data, "left")) {
13533
13535
  if (this.tab > 0) {
13534
13536
  this.tab -= 1;
13535
13537
  this.cursor = 0;
13536
13538
  this.submitIdx = 0;
13537
13539
  this.syncEditMode();
13538
13540
  }
13539
- } else if (data === "\x1B[C") {
13540
- if (this.tab < this.questions.length - 1) {
13541
- this.tab += 1;
13542
- this.cursor = 0;
13543
- this.submitIdx = 0;
13544
- this.syncEditMode();
13545
- } else {
13546
- this.tab = this.questions.length;
13547
- this.submitIdx = 0;
13548
- this.syncEditMode();
13549
- }
13550
- } else this.otherInput.handleInput(data);
13541
+ } else if (matchesKey(data, "right")) this.commitOther(this.otherInput.getValue());
13542
+ else this.otherInput.handleInput(data);
13551
13543
  return;
13552
13544
  }
13553
13545
  const rows = this.rows();
13554
13546
  if (this.tab >= this.questions.length) {
13555
- if (data === "\r" || data === "\n") {
13547
+ if (matchesKey(data, "enter")) {
13556
13548
  if (this.submitIdx === 0) this.submit();
13557
13549
  else this.onCancel();
13558
- } else if (data === "\x1B") this.onCancel();
13559
- else if (data === "\x1B[D" || data === "h") this.submitIdx = 0;
13560
- else if (data === "\x1B[C" || data === "l") this.submitIdx = 1;
13561
- else if (data === "b") {
13550
+ } else if (matchesKey(data, "escape")) this.onCancel();
13551
+ else if (matchesKey(data, "left") || data === "h") {
13562
13552
  this.tab = Math.max(0, this.questions.length - 1);
13563
13553
  this.cursor = 0;
13564
13554
  this.submitIdx = 0;
13565
13555
  this.syncEditMode();
13566
- }
13556
+ } else if (matchesKey(data, "up") || data === "k") this.submitIdx = 0;
13557
+ else if (matchesKey(data, "down") || data === "j") this.submitIdx = 1;
13567
13558
  return;
13568
13559
  }
13569
13560
  const digit = /^[1-9]$/.exec(data);
@@ -13576,7 +13567,7 @@ var QuestionFlow = class {
13576
13567
  }
13577
13568
  return;
13578
13569
  }
13579
- if (data === "\x1B[A" || data === "k") {
13570
+ if (matchesKey(data, "up") || data === "k") {
13580
13571
  if (rows.length === 0) return;
13581
13572
  if (this.cursor === 0 && this.bodyScroll > 0) {
13582
13573
  this.scrollBody(-1);
@@ -13586,7 +13577,7 @@ var QuestionFlow = class {
13586
13577
  this.pendingCursorScroll = true;
13587
13578
  return;
13588
13579
  }
13589
- if (data === "\x1B[B" || data === "j") {
13580
+ if (matchesKey(data, "down") || data === "j") {
13590
13581
  if (rows.length === 0) return;
13591
13582
  const atLastRow = this.cursor === rows.length - 1;
13592
13583
  const scrolled = this.lastExpandable && this.lastContentRows > this.bodyScroll + this.lastVisibleRows;
@@ -13598,19 +13589,19 @@ var QuestionFlow = class {
13598
13589
  this.pendingCursorScroll = true;
13599
13590
  return;
13600
13591
  }
13601
- if (data === "\x1B[5~" || data === "\x1B[6~") {
13602
- this.scrollBody(data === "\x1B[5~" ? -1 : 1);
13592
+ if (matchesKey(data, "pageUp") || matchesKey(data, "pageDown")) {
13593
+ this.scrollBody(matchesKey(data, "pageUp") ? -1 : 1);
13603
13594
  return;
13604
13595
  }
13605
13596
  if (data === "e") {
13606
13597
  this.toggleExpanded();
13607
13598
  return;
13608
13599
  }
13609
- if (data === "\r" || data === "\n") {
13600
+ if (matchesKey(data, "enter")) {
13610
13601
  this.confirm();
13611
13602
  return;
13612
13603
  }
13613
- if (data === "\x1B[D" || data === "h") {
13604
+ if (matchesKey(data, "left") || data === "h") {
13614
13605
  if (this.tab > 0) {
13615
13606
  this.tab -= 1;
13616
13607
  this.cursor = 0;
@@ -13619,7 +13610,11 @@ var QuestionFlow = class {
13619
13610
  }
13620
13611
  return;
13621
13612
  }
13622
- if (data === "\x1B[C" || data === "l") {
13613
+ if (matchesKey(data, "right") || data === "l") {
13614
+ if (!this.answered()) {
13615
+ this.skip();
13616
+ return;
13617
+ }
13623
13618
  if (this.tab < this.questions.length - 1) {
13624
13619
  this.tab += 1;
13625
13620
  this.cursor = 0;
@@ -13633,11 +13628,7 @@ var QuestionFlow = class {
13633
13628
  }
13634
13629
  return;
13635
13630
  }
13636
- if (data === "s") {
13637
- this.skip();
13638
- return;
13639
- }
13640
- if (data === "\x1B") this.onCancel();
13631
+ if (matchesKey(data, "escape")) this.onCancel();
13641
13632
  }
13642
13633
  invalidate() {
13643
13634
  this.otherInput.invalidate();
@@ -13680,7 +13671,7 @@ var QuestionFlow = class {
13680
13671
  const submit = this.submitIdx === 0 ? color.textStrong("Submit") : color.textDim("Submit");
13681
13672
  const cancel = this.submitIdx === 1 ? color.textStrong("Cancel") : color.textDim("Cancel");
13682
13673
  lines.push(`${this.submitIdx === 0 ? color.primary("→ ") : " "}${submit} ${cancel}`);
13683
- lines.push(color.textDim("← → choose · b back · ↵ confirm · esc cancel"));
13674
+ lines.push(color.textDim("↑↓ choose · ← back · ↵ confirm · esc cancel"));
13684
13675
  return lines;
13685
13676
  }
13686
13677
  const question = this.questions[this.tab];
@@ -13736,14 +13727,14 @@ var QuestionFlow = class {
13736
13727
  lines.push("");
13737
13728
  const optionCount = Math.min(rows.length, 9);
13738
13729
  const scrollable = this.lastExpandable;
13730
+ const arrowVerb = this.editingOther ? "→ next" : "→ skip";
13739
13731
  const hintParts = [
13740
13732
  "↑↓ select",
13741
13733
  optionCount > 0 ? `1-${optionCount} choose` : "",
13742
13734
  multi ? "↵ toggle" : "↵ confirm",
13743
13735
  scrollable ? "pgup/pgdn scroll" : "",
13744
13736
  !this.editingOther ? this.bodyExpanded ? "e collapse" : scrollable ? "e expand" : "" : "",
13745
- this.questions.length > 1 ? "←→ switch" : "",
13746
- "s skip",
13737
+ this.questions.length > 1 ? `← back · ${arrowVerb}` : arrowVerb,
13747
13738
  "esc cancel"
13748
13739
  ].filter((part) => part !== "");
13749
13740
  const cancel = "esc cancel";
@@ -17699,6 +17690,48 @@ function registerTuiCommands(runner, initial) {
17699
17690
  const sessionCwd = (agent) => agent.session.header.cwd ?? cwd;
17700
17691
  const skillTarget = (agent) => resolveLiveSkillTarget(ctx, agent, sessionCwd(agent));
17701
17692
  /**
17693
+ * Split a skill invocation's trailing input into the skill name and its
17694
+ * arguments: the first whitespace-bounded token is the name (the public
17695
+ * skill-name grammar), everything after it the arguments. Used by the
17696
+ * per-skill wrappers and `/skill`, so `/name args` never looks up a name
17697
+ * containing the whole rest of the line.
17698
+ * @param raw - the invocation's rawInput ('' for a bare command).
17699
+ * @returns the name token ('' when the input is blank) plus the args.
17700
+ */
17701
+ const splitSkillLine = (raw) => {
17702
+ const trimmed = raw.trimStart();
17703
+ const space = trimmed.search(/\s/);
17704
+ if (space === -1) return [trimmed.trim(), ...[]];
17705
+ return [trimmed.slice(0, space).trim(), trimmed.slice(space).trimStart()];
17706
+ };
17707
+ /**
17708
+ * Structurally validate a skill's optional resource base, so a malformed
17709
+ * provider-supplied value degrades to no hint instead of throwing inside
17710
+ * the fallback rendering (the adapter's conservative rule — hostile
17711
+ * entries are refused, never coerced or thrown on).
17712
+ * @param value - the loaded skill's `resourceBase` (opaque from the adapter).
17713
+ * @returns the validated resource base, or undefined when unreadable.
17714
+ */
17715
+ const readResourceBase = (value) => {
17716
+ if (typeof value !== "object" || value === null) return void 0;
17717
+ const record = value;
17718
+ switch (record.kind) {
17719
+ case "directory": return typeof record.path === "string" && record.path !== "" ? {
17720
+ kind: "directory",
17721
+ path: record.path
17722
+ } : void 0;
17723
+ case "url": return typeof record.url === "string" && record.url !== "" ? {
17724
+ kind: "url",
17725
+ url: record.url
17726
+ } : void 0;
17727
+ case "opaque": return typeof record.description === "string" && record.description !== "" ? {
17728
+ kind: "opaque",
17729
+ description: record.description
17730
+ } : void 0;
17731
+ default: return;
17732
+ }
17733
+ };
17734
+ /**
17702
17735
  * The execution boundary for loading one skill into the live session;
17703
17736
  * shared by /skill and the per-skill slash commands. The skill is fetched
17704
17737
  * from the CURRENT agent's registry and its invocation policy is RE-CHECKED
@@ -17706,7 +17739,7 @@ function registerTuiCommands(runner, initial) {
17706
17739
  * authorization. A model-only skill is refused with an explicit error and
17707
17740
  * never injected.
17708
17741
  */
17709
- const loadSkill = async (agent, name) => {
17742
+ const loadSkill = async (agent, name, args = "") => {
17710
17743
  const target = skillTarget(agent);
17711
17744
  if (target === void 0) return {
17712
17745
  kind: "error",
@@ -17728,19 +17761,37 @@ function registerTuiCommands(runner, initial) {
17728
17761
  kind: "error",
17729
17762
  text: `skill "${name}" returned a malformed definition`
17730
17763
  };
17731
- const body = typeof skill.content === "string" && skill.content !== "" ? skill.content : skill.description;
17732
- const message = createUserMessage({
17764
+ const line = args.trim() === "" ? "/" + skill.name : "/" + skill.name + " " + args.trimStart();
17765
+ const userMessage = createUserMessage({
17733
17766
  content: [{
17734
17767
  type: "text",
17735
- text: "Skill loaded by the user: **" + skill.name + "**\n\n" + body
17768
+ text: line
17736
17769
  }],
17737
- source: {
17738
- kind: "plugin",
17739
- plugin: "tui-skill"
17740
- }
17770
+ source: { kind: "user" }
17741
17771
  });
17742
- if (agent.status === "running") agent.steer(message);
17743
- else agent.followup(message);
17772
+ const hostSkillLoader = ctx.get("tools")?.get?.("skill", agent);
17773
+ const hostLoadsSkillBody = hostSkillLoader !== void 0 && typeof hostSkillLoader.execute === "function";
17774
+ agent.steer(userMessage);
17775
+ if (!hostLoadsSkillBody) {
17776
+ const body = typeof skill.content === "string" && skill.content !== "" ? skill.content : skill.description;
17777
+ const resourceBase = readResourceBase(skill.resourceBase);
17778
+ agent.inject(createUserMessage({
17779
+ content: [{
17780
+ type: "text",
17781
+ text: renderSkillContent({
17782
+ name: skill.name,
17783
+ provider: typeof skill.provider === "string" && skill.provider !== "" ? skill.provider : "tui",
17784
+ ...resourceBase === void 0 ? {} : { resourceBase },
17785
+ content: body
17786
+ })
17787
+ }],
17788
+ source: {
17789
+ kind: "skill-invocation",
17790
+ name: skill.name,
17791
+ form: "instructions"
17792
+ }
17793
+ }));
17794
+ }
17744
17795
  return {
17745
17796
  kind: "success",
17746
17797
  text: "skill " + name + " loaded"
@@ -17776,9 +17827,9 @@ function registerTuiCommands(runner, initial) {
17776
17827
  const dispose = commands.register({
17777
17828
  name: skill.name,
17778
17829
  description: "[skill] " + skill.description,
17779
- handler: async () => {
17830
+ handler: async (invocation) => {
17780
17831
  const agent = await requireAgent();
17781
- return loadSkill(agent, skill.name);
17832
+ return loadSkill(agent, skill.name, invocation?.rawInput ?? "");
17782
17833
  }
17783
17834
  });
17784
17835
  skillDisposers.set(skill.name, dispose);
@@ -17824,9 +17875,9 @@ function registerTuiCommands(runner, initial) {
17824
17875
  const dispose = commands.register({
17825
17876
  name,
17826
17877
  description: `[skill: revalidating] ${name}`,
17827
- handler: async () => {
17878
+ handler: async (invocation) => {
17828
17879
  const agent = await requireAgent();
17829
- return loadSkill(agent, name);
17880
+ return loadSkill(agent, name, invocation?.rawInput ?? "");
17830
17881
  }
17831
17882
  });
17832
17883
  skillDisposers.set(name, dispose);
@@ -17848,8 +17899,8 @@ function registerTuiCommands(runner, initial) {
17848
17899
  kind: "error",
17849
17900
  text: "skill service unavailable"
17850
17901
  };
17851
- const name = invocation.rawInput.trim();
17852
- if (name !== "") return loadSkill(liveAgent, name);
17902
+ const [name, ...args] = splitSkillLine(invocation.rawInput);
17903
+ if (name !== "") return loadSkill(liveAgent, name, args.join(" "));
17853
17904
  const catalog = await readHumanSkillCatalog(target.registry, {
17854
17905
  cwd: target.cwd,
17855
17906
  scope: target.scope
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoon76/dsh-pi-tui",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "The dsh-pi-tui bundle: a TUI surface for DeepSeek Harness profiles (dsh --profile pi-tui)",
5
5
  "license": "MIT",
6
6
  "author": "XMoon <xmoonox@gmail.com>",