@alisio/alisio-code 0.1.0-alpha.18 → 0.1.0-alpha.19

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/dist/builtin.d.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  * Built-in plugin registry wired by the CLI. Built-ins load through the trusted path of the
3
3
  * plugin host (unprefixed names, `internal` effect) and can be disabled by configuration
4
4
  * (`builtinPlugins.<id>.enabled: false`) or `--disable-plugin <id>`. Add a built-in by
5
- * appending an entry here.
5
+ * appending an entry here. Dedicated model providers (DeepSeek, OpenCode Console, OpenCode Go)
6
+ * are no longer built-ins: users install them independently via
7
+ * `alisio install npm:@alisio/plugin-{deepseek,opencode,opencode-go}`.
6
8
  */
7
9
  import type { BuiltinPlugin } from "@alisio/core";
8
10
  export declare const BUILTIN_PLUGINS: BuiltinPlugin[];
package/dist/builtin.js CHANGED
@@ -1,31 +1,7 @@
1
- import { createDeepSeekPlugin } from "@alisio/plugin-deepseek";
2
1
  import { createMemoryPlugin } from "@alisio/plugin-memory";
3
2
  import { createOpenAICompatiblePlugin } from "@alisio/plugin-openai-compatible";
4
- import { createOpenCodePlugin } from "@alisio/plugin-opencode";
5
- import { createOpenCodeGoPlugin } from "@alisio/plugin-opencode-go";
6
3
  import { createSubagentsPlugin } from "@alisio/plugin-subagents";
7
4
  export const BUILTIN_PLUGINS = [
8
- {
9
- id: "deepseek",
10
- name: "DeepSeek",
11
- description: "Dedicated DeepSeek Chat Completions and Responses provider",
12
- categories: ["model-provider"],
13
- create: createDeepSeekPlugin,
14
- },
15
- {
16
- id: "opencode",
17
- name: "OpenCode Console (Zen)",
18
- description: "Dedicated OpenCode Console (Zen) multi-protocol provider",
19
- categories: ["model-provider"],
20
- create: createOpenCodePlugin,
21
- },
22
- {
23
- id: "opencode-go",
24
- name: "OpenCode Go",
25
- description: "Dedicated OpenCode Go multi-protocol provider",
26
- categories: ["model-provider"],
27
- create: createOpenCodeGoPlugin,
28
- },
29
5
  {
30
6
  id: "openai-compatible",
31
7
  name: "OpenAI compatible",
package/dist/tui/app.d.ts CHANGED
@@ -1,4 +1,41 @@
1
1
  import type { AppOptions } from "@alisio/core";
2
+ import { type Component, type SelectItem } from "@earendil-works/pi-tui";
3
+ /**
4
+ * Inline selection list with type-to-filter, rendered above the editor.
5
+ *
6
+ * Grouped item builders (pluginCatalogItems) interleave their output with non-selectable
7
+ * `__group:` header items. Headers stay in the SelectList so they render, scroll and truncate
8
+ * like rows, but selection is owned here: navigation jumps over headers via skipGroupHeaders
9
+ * (wrapping both directions), Enter never fires onSelect for a header, and the whole display
10
+ * sequence is re-derived on every filter keystroke so headers of fully-filtered groups vanish.
11
+ */
12
+ export declare class Picker implements Component {
13
+ private title;
14
+ private readonly onSelectItem;
15
+ private readonly onCancelPick;
16
+ private filterable;
17
+ private detail?;
18
+ private list;
19
+ private filter;
20
+ /** Full item sequence (group headers + rows) before any filtering. */
21
+ private readonly allItems;
22
+ /** Display sequence: filtered rows plus the headers of groups that still match. */
23
+ private items;
24
+ /** Selection index within `items`; always a real row, never a group header. */
25
+ private index;
26
+ /** Picker height target: counted on real rows only, so headers never stretch the list. */
27
+ private readonly maxVisible;
28
+ constructor(title: string, items: SelectItem[], onSelectItem: (item: SelectItem) => void, onCancelPick: () => void, filterable?: boolean, detail?: string | undefined);
29
+ /** Copies items, dimming group-header labels (style helpers stay plain under NO_COLOR). */
30
+ private static displayItems;
31
+ /** (Re)builds the SelectList over the filtered display sequence; selection starts on a row. */
32
+ private resetList;
33
+ invalidate(): void;
34
+ /** The currently selected item — always a real row, never a group header. */
35
+ getSelectedItem(): SelectItem | null;
36
+ handleInput(data: string): void;
37
+ render(width: number): string[];
38
+ }
2
39
  export interface TuiOptions extends AppOptions {
3
40
  session?: string;
4
41
  quiet?: boolean;
package/dist/tui/app.js CHANGED
@@ -14,38 +14,94 @@ import { InteractiveQueue } from "./queue.js";
14
14
  import { settingsMenuRows } from "./settings.js";
15
15
  import { SettingsMenu } from "./settings-menu.js";
16
16
  import { SkillsManager } from "./skills-manager.js";
17
- import { addItem, COMMANDS, configuredProviderModelItems, editorCopyKey, effectiveEffort, effortPickerItems, foldCandidates, foldToggleKey, formatContext, formatDuration, formatTokens, hostOf, initialViewState, itemsFromHistory, lastAssistantText, mcpServerItems, mcpToolItems, parseCommand, pluginCatalogItems, pluginToggleNeedsConfirmation, providerModelItems, reduceEvent, reservedCommandNames, resolveCommand, richPartsOf, shortenPath, shortId, slashCompletionCommands, summarizeToolArgs, validateEffortLevel, } from "./state.js";
17
+ import { addItem, COMMANDS, configuredProviderModelItems, editorCopyKey, effectiveEffort, effortPickerItems, foldCandidates, foldToggleKey, formatContext, formatDuration, formatTokens, hostOf, initialViewState, isGroupHeader, itemsFromHistory, lastAssistantText, mcpServerItems, mcpToolItems, parseCommand, pluginCatalogItems, pluginToggleNeedsConfirmation, providerModelItems, reduceEvent, reservedCommandNames, resolveCommand, richPartsOf, shortenPath, shortId, skipGroupHeaders, slashCompletionCommands, summarizeToolArgs, validateEffortLevel, visibleGroupedItems, } from "./state.js";
18
18
  import { editorTheme, selectListTheme, style } from "./theme.js";
19
19
  const VERSION = loadVersion(import.meta.url);
20
- /** Inline selection list with type-to-filter, rendered above the editor. */
21
- class Picker {
20
+ /**
21
+ * Inline selection list with type-to-filter, rendered above the editor.
22
+ *
23
+ * Grouped item builders (pluginCatalogItems) interleave their output with non-selectable
24
+ * `__group:` header items. Headers stay in the SelectList so they render, scroll and truncate
25
+ * like rows, but selection is owned here: navigation jumps over headers via skipGroupHeaders
26
+ * (wrapping both directions), Enter never fires onSelect for a header, and the whole display
27
+ * sequence is re-derived on every filter keystroke so headers of fully-filtered groups vanish.
28
+ */
29
+ export class Picker {
22
30
  title;
31
+ onSelectItem;
32
+ onCancelPick;
23
33
  filterable;
24
34
  detail;
25
- list;
35
+ list = new SelectList([], 1, selectListTheme);
26
36
  filter = "";
27
- constructor(title, items, onSelect, onCancel, filterable = false, detail) {
37
+ /** Full item sequence (group headers + rows) before any filtering. */
38
+ allItems;
39
+ /** Display sequence: filtered rows plus the headers of groups that still match. */
40
+ items = [];
41
+ /** Selection index within `items`; always a real row, never a group header. */
42
+ index = 0;
43
+ /** Picker height target: counted on real rows only, so headers never stretch the list. */
44
+ maxVisible;
45
+ constructor(title, items, onSelectItem, onCancelPick, filterable = false, detail) {
28
46
  this.title = title;
47
+ this.onSelectItem = onSelectItem;
48
+ this.onCancelPick = onCancelPick;
29
49
  this.filterable = filterable;
30
50
  this.detail = detail;
31
- this.list = new SelectList(items, Math.min(10, Math.max(1, items.length)), selectListTheme);
32
- this.list.onSelect = onSelect;
33
- this.list.onCancel = onCancel;
51
+ this.allItems = items;
52
+ this.maxVisible = Math.min(10, Math.max(1, items.filter((item) => !isGroupHeader(item)).length));
53
+ this.resetList();
54
+ }
55
+ /** Copies items, dimming group-header labels (style helpers stay plain under NO_COLOR). */
56
+ static displayItems(items) {
57
+ return items.map((item) => isGroupHeader(item) ? { ...item, label: style.gray(item.label) } : item);
58
+ }
59
+ /** (Re)builds the SelectList over the filtered display sequence; selection starts on a row. */
60
+ resetList() {
61
+ this.items = Picker.displayItems(this.filter ? visibleGroupedItems(this.allItems, this.filter) : this.allItems);
62
+ this.list = new SelectList(this.items, this.maxVisible, selectListTheme);
63
+ this.list.onSelect = (item) => {
64
+ if (!isGroupHeader(item))
65
+ this.onSelectItem(item);
66
+ };
67
+ this.list.onCancel = this.onCancelPick;
68
+ this.index = this.items.length ? skipGroupHeaders(this.items, -1, 1) : 0;
69
+ if (this.index < 0)
70
+ this.index = 0;
71
+ this.list.setSelectedIndex(this.index);
34
72
  }
35
73
  invalidate() {
36
74
  this.list.invalidate();
37
75
  }
76
+ /** The currently selected item — always a real row, never a group header. */
77
+ getSelectedItem() {
78
+ return this.items[this.index] ?? null;
79
+ }
38
80
  handleInput(data) {
39
81
  if (this.filterable && matchesKey(data, Key.backspace)) {
40
82
  this.filter = this.filter.slice(0, -1);
41
- this.list.setFilter(this.filter);
83
+ this.resetList();
42
84
  }
43
85
  else if (this.filterable && data.length === 1 && data >= " " && data <= "~") {
44
86
  this.filter += data;
45
- this.list.setFilter(this.filter);
87
+ this.resetList();
46
88
  }
47
- else
89
+ else if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
90
+ const dir = matchesKey(data, Key.down) ? 1 : -1;
91
+ this.index = this.items.length ? skipGroupHeaders(this.items, this.index, dir) : 0;
92
+ this.list.setSelectedIndex(this.index);
93
+ }
94
+ else if (matchesKey(data, Key.enter)) {
95
+ const item = this.items[this.index];
96
+ if (item && !isGroupHeader(item))
97
+ this.onSelectItem(item);
98
+ }
99
+ else if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
100
+ this.onCancelPick();
101
+ }
102
+ else {
48
103
  this.list.handleInput(data);
104
+ }
49
105
  }
50
106
  render(width) {
51
107
  const hint = this.filterable
@@ -39,11 +39,46 @@ export interface PluginCatalogView {
39
39
  }
40
40
  /** Text markers remain meaningful without color: [x] active, [ ] inactive, [!] failed, [*] pending. */
41
41
  /** Group headings are derived from the primary category (or "General") of each plugin. */
42
- export declare function pluginCatalogItems(entries: PluginCatalogView[]): {
42
+ /**
43
+ * Reserved value prefix marking a non-selectable group-header row in picker item lists. It can
44
+ * never collide with real entry ids (plugin ids, server names), which never start with "_".
45
+ */
46
+ export declare const GROUP_HEADER_PREFIX = "__group:";
47
+ /** True when the item is a group header emitted under GROUP_HEADER_PREFIX (never selectable). */
48
+ export declare const isGroupHeader: (item: {
49
+ value: string;
50
+ }) => boolean;
51
+ /**
52
+ * Nearest non-header index from `index` stepping `dir` (1 = down, -1 = up), wrapping at the
53
+ * edges exactly like a plain list would, so navigation skips header rows in both directions and
54
+ * never stops on one. Returns -1 for an empty list and `index` unchanged when every item is a
55
+ * header (callers then treat it as "no move").
56
+ */
57
+ export declare function skipGroupHeaders(items: readonly {
58
+ value: string;
59
+ }[], index: number, dir: 1 | -1): number;
60
+ /**
61
+ * The display sequence a picker should render for a filter: rows matching SelectList's own
62
+ * prefix semantics, plus the group header of every group that still has at least one visible
63
+ * row, in original order. Groups with no match emit no header.
64
+ */
65
+ export declare function visibleGroupedItems(items: ReadonlyArray<{
66
+ value: string;
67
+ label: string;
68
+ description?: string;
69
+ }>, filter: string): Array<{
70
+ value: string;
71
+ label: string;
72
+ description?: string;
73
+ }>;
74
+ export declare function pluginCatalogItems(entries: PluginCatalogView[]): ({
75
+ value: string;
76
+ label: string;
77
+ } | {
43
78
  value: string;
44
79
  label: string;
45
80
  description: string;
46
- }[];
81
+ })[];
47
82
  export declare const pluginToggleNeedsConfirmation: (entry: PluginCatalogView) => boolean;
48
83
  export interface McpServerView {
49
84
  name: string;
package/dist/tui/state.js CHANGED
@@ -24,6 +24,55 @@ export function providerModelItems(provider, models, current) {
24
24
  }
25
25
  /** Text markers remain meaningful without color: [x] active, [ ] inactive, [!] failed, [*] pending. */
26
26
  /** Group headings are derived from the primary category (or "General") of each plugin. */
27
+ /**
28
+ * Reserved value prefix marking a non-selectable group-header row in picker item lists. It can
29
+ * never collide with real entry ids (plugin ids, server names), which never start with "_".
30
+ */
31
+ export const GROUP_HEADER_PREFIX = "__group:";
32
+ /** True when the item is a group header emitted under GROUP_HEADER_PREFIX (never selectable). */
33
+ export const isGroupHeader = (item) => item.value.startsWith(GROUP_HEADER_PREFIX);
34
+ /**
35
+ * Nearest non-header index from `index` stepping `dir` (1 = down, -1 = up), wrapping at the
36
+ * edges exactly like a plain list would, so navigation skips header rows in both directions and
37
+ * never stops on one. Returns -1 for an empty list and `index` unchanged when every item is a
38
+ * header (callers then treat it as "no move").
39
+ */
40
+ export function skipGroupHeaders(items, index, dir) {
41
+ const count = items.length;
42
+ if (!count)
43
+ return -1;
44
+ let next = (index + dir + count) % count;
45
+ for (let steps = 0; steps < count && next !== index; steps++) {
46
+ if (!isGroupHeader(items[next]))
47
+ return next;
48
+ next = (next + dir + count) % count;
49
+ }
50
+ return index;
51
+ }
52
+ /**
53
+ * The display sequence a picker should render for a filter: rows matching SelectList's own
54
+ * prefix semantics, plus the group header of every group that still has at least one visible
55
+ * row, in original order. Groups with no match emit no header.
56
+ */
57
+ export function visibleGroupedItems(items, filter) {
58
+ const query = filter.toLowerCase();
59
+ const visible = [];
60
+ let header;
61
+ for (const item of items) {
62
+ if (isGroupHeader(item)) {
63
+ header = item;
64
+ continue;
65
+ }
66
+ if (item.value.toLowerCase().startsWith(query)) {
67
+ if (header) {
68
+ visible.push(header);
69
+ header = undefined;
70
+ }
71
+ visible.push(item);
72
+ }
73
+ }
74
+ return visible;
75
+ }
27
76
  export function pluginCatalogItems(entries) {
28
77
  const marker = (entry) => entry.status === "active"
29
78
  ? "[x]"
@@ -37,11 +86,15 @@ export function pluginCatalogItems(entries) {
37
86
  const primary = entry.categories[0] ?? "General";
38
87
  grouped.set(primary, [...(grouped.get(primary) ?? []), entry]);
39
88
  }
40
- return [...grouped].flatMap(([title, group]) => group.map((entry, index) => ({
41
- value: entry.id,
42
- label: `${index === 0 ? `${title} · ` : ""}${marker(entry)} ${entry.name} · ${entry.builtin ? "built-in" : entry.source}`,
43
- description: `${entry.status}${entry.categories.length ? ` · ${entry.categories.join(", ")}` : ""} · ${entry.description}`,
44
- })));
89
+ // Every group opens with its own non-selectable header row; plugin rows are indented under it.
90
+ return [...grouped].flatMap(([title, group]) => [
91
+ { value: `${GROUP_HEADER_PREFIX}${title}`, label: `${title} ›` },
92
+ ...group.map((entry) => ({
93
+ value: entry.id,
94
+ label: ` ${marker(entry)} ${entry.name} · ${entry.builtin ? "built-in" : entry.source}`,
95
+ description: `${entry.status}${entry.categories.length ? ` · ${entry.categories.join(", ")}` : ""} · ${entry.description}`,
96
+ })),
97
+ ]);
45
98
  }
46
99
  export const pluginToggleNeedsConfirmation = (entry) => !entry.builtin;
47
100
  const mcpSourceTitle = (kind) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/alisio-code",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "Alisio: an extensible, provider-agnostic coding-agent harness for your terminal. TUI, OpenAI-compatible providers, permissioned local tools, context compaction, persistent memory and a typed plugin SDK.",
5
5
  "author": "Gustavo Gutiérrez",
6
6
  "license": "MIT",
@@ -46,11 +46,8 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@alisio/core": "0.1.0-alpha.16",
49
- "@alisio/plugin-deepseek": "0.1.0-alpha.10",
50
49
  "@alisio/plugin-memory": "0.1.0-alpha.9",
51
50
  "@alisio/plugin-openai-compatible": "0.1.0-alpha.9",
52
- "@alisio/plugin-opencode": "0.1.0-alpha.8",
53
- "@alisio/plugin-opencode-go": "0.1.0-alpha.8",
54
51
  "@alisio/plugin-subagents": "0.1.0-alpha.10",
55
52
  "@alisio/sdk": "0.1.0-alpha.11",
56
53
  "@earendil-works/pi-tui": "0.87.1",