@moxxy/cli 0.0.11 → 0.1.0
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 +278 -112
- package/bin/moxxy +10 -0
- package/package.json +36 -53
- package/src/api-client.js +286 -0
- package/src/cli.js +341 -0
- package/src/commands/agent.js +413 -0
- package/src/commands/auth.js +326 -0
- package/src/commands/channel.js +285 -0
- package/src/commands/doctor.js +261 -0
- package/src/commands/events.js +80 -0
- package/src/commands/gateway.js +428 -0
- package/src/commands/heartbeat.js +145 -0
- package/src/commands/init.js +767 -0
- package/src/commands/mcp.js +278 -0
- package/src/commands/plugin.js +583 -0
- package/src/commands/provider.js +1934 -0
- package/src/commands/skill.js +125 -0
- package/src/commands/template.js +237 -0
- package/src/commands/uninstall.js +196 -0
- package/src/commands/update.js +406 -0
- package/src/commands/vault.js +219 -0
- package/src/help.js +368 -0
- package/src/lib/plugin-registry.js +98 -0
- package/src/platform.js +40 -0
- package/src/sse-client.js +79 -0
- package/src/tui/action-wizards.js +130 -0
- package/src/tui/app.jsx +859 -0
- package/src/tui/components/action-picker.jsx +86 -0
- package/src/tui/components/chat-panel.jsx +120 -0
- package/src/tui/components/footer.jsx +13 -0
- package/src/tui/components/header.jsx +45 -0
- package/src/tui/components/input-area.jsx +384 -0
- package/src/tui/components/messages/ask-message.jsx +13 -0
- package/src/tui/components/messages/assistant-message.jsx +165 -0
- package/src/tui/components/messages/channel-message.jsx +18 -0
- package/src/tui/components/messages/event-message.jsx +22 -0
- package/src/tui/components/messages/hive-status.jsx +34 -0
- package/src/tui/components/messages/skill-message.jsx +31 -0
- package/src/tui/components/messages/system-message.jsx +12 -0
- package/src/tui/components/messages/thinking.jsx +25 -0
- package/src/tui/components/messages/tool-group.jsx +62 -0
- package/src/tui/components/messages/tool-message.jsx +66 -0
- package/src/tui/components/messages/user-message.jsx +12 -0
- package/src/tui/components/model-picker.jsx +138 -0
- package/src/tui/components/multiline-input.jsx +72 -0
- package/src/tui/events-handler.js +730 -0
- package/src/tui/helpers.js +59 -0
- package/src/tui/hooks/use-command-handler.js +451 -0
- package/src/tui/index.jsx +55 -0
- package/src/tui/input-utils.js +26 -0
- package/src/tui/markdown-renderer.js +66 -0
- package/src/tui/mcp-wizard.js +136 -0
- package/src/tui/model-picker.js +174 -0
- package/src/tui/slash-commands.js +26 -0
- package/src/tui/store.js +12 -0
- package/src/tui/theme.js +17 -0
- package/src/ui.js +109 -0
- package/bin/moxxy.js +0 -2
- package/dist/chunk-23LZYKQ6.mjs +0 -1131
- package/dist/chunk-2FZEA3NG.mjs +0 -457
- package/dist/chunk-3KDPLS22.mjs +0 -1131
- package/dist/chunk-3QRJTRBT.mjs +0 -1102
- package/dist/chunk-6DZX6EAA.mjs +0 -37
- package/dist/chunk-A4WRDUNY.mjs +0 -1242
- package/dist/chunk-C46NSEKG.mjs +0 -211
- package/dist/chunk-CAUXONEF.mjs +0 -1131
- package/dist/chunk-CPL5V56X.mjs +0 -1131
- package/dist/chunk-CTBVTTBG.mjs +0 -440
- package/dist/chunk-FHHLXTEZ.mjs +0 -1121
- package/dist/chunk-FXY3GPVA.mjs +0 -1126
- package/dist/chunk-GSNMMI3H.mjs +0 -530
- package/dist/chunk-HHOAOGUS.mjs +0 -1242
- package/dist/chunk-N5JTPB6U.mjs +0 -820
- package/dist/chunk-NGVL4Q5C.mjs +0 -1102
- package/dist/chunk-Q2OCMNYI.mjs +0 -1131
- package/dist/chunk-QDVRLN6D.mjs +0 -1121
- package/dist/chunk-QO2JONHP.mjs +0 -1131
- package/dist/chunk-RVAPILHA.mjs +0 -1242
- package/dist/chunk-S7YBOV7E.mjs +0 -1131
- package/dist/chunk-SHIG6Y5L.mjs +0 -1074
- package/dist/chunk-SOFST2PV.mjs +0 -1242
- package/dist/chunk-TMZWETMH.mjs +0 -1242
- package/dist/chunk-TYD7NMMI.mjs +0 -581
- package/dist/chunk-TYQ3YS42.mjs +0 -1068
- package/dist/chunk-UALWCJ7F.mjs +0 -1131
- package/dist/chunk-UQZKODNW.mjs +0 -1124
- package/dist/chunk-USC6R2ON.mjs +0 -1242
- package/dist/chunk-W32EQCVC.mjs +0 -823
- package/dist/chunk-WMB5ENMC.mjs +0 -1242
- package/dist/chunk-WNHA5JAP.mjs +0 -1242
- package/dist/cli-2AIWTL6F.mjs +0 -8
- package/dist/cli-2QKJ5UUL.mjs +0 -8
- package/dist/cli-4RIS6DQX.mjs +0 -8
- package/dist/cli-5RH4VBBL.mjs +0 -7
- package/dist/cli-7MK4YGOP.mjs +0 -7
- package/dist/cli-B4KH6MZI.mjs +0 -8
- package/dist/cli-CGO2LZ6Z.mjs +0 -8
- package/dist/cli-CVP26EL2.mjs +0 -8
- package/dist/cli-DDRVVNAV.mjs +0 -8
- package/dist/cli-E7U56QVQ.mjs +0 -8
- package/dist/cli-EQNRMLL3.mjs +0 -8
- package/dist/cli-F5RUHHH4.mjs +0 -8
- package/dist/cli-LX6FFSEF.mjs +0 -8
- package/dist/cli-LY74GWKR.mjs +0 -6
- package/dist/cli-MAT3ZJHI.mjs +0 -8
- package/dist/cli-NJXXTQYF.mjs +0 -8
- package/dist/cli-O4ZGFAZG.mjs +0 -8
- package/dist/cli-ORVLI3UQ.mjs +0 -8
- package/dist/cli-PV43ZVKA.mjs +0 -8
- package/dist/cli-REVD6ISM.mjs +0 -8
- package/dist/cli-TBX76KQX.mjs +0 -8
- package/dist/cli-TLX5ENVM.mjs +0 -8
- package/dist/cli-TNJHCBQA.mjs +0 -6
- package/dist/cli-TUX22CZP.mjs +0 -8
- package/dist/cli-XJVH7EEP.mjs +0 -8
- package/dist/cli-XXOW4VXJ.mjs +0 -8
- package/dist/cli-XZ5RESNB.mjs +0 -6
- package/dist/cli-YCBYZ76Q.mjs +0 -8
- package/dist/cli-ZLMQCU7X.mjs +0 -8
- package/dist/dist-2VGKJRBH.mjs +0 -6820
- package/dist/dist-37BNX4QG.mjs +0 -7081
- package/dist/dist-7LTHRYKA.mjs +0 -11569
- package/dist/dist-7XJPQW5C.mjs +0 -6950
- package/dist/dist-AYMVOW7T.mjs +0 -7123
- package/dist/dist-FAXRJMEN.mjs +0 -6812
- package/dist/dist-HQGANM3P.mjs +0 -6976
- package/dist/dist-KATLOZQV.mjs +0 -7054
- package/dist/dist-KLSB6YHV.mjs +0 -6964
- package/dist/dist-LKIOZQ42.mjs +0 -17
- package/dist/dist-UYA4RJUH.mjs +0 -2792
- package/dist/dist-ZYHCBILM.mjs +0 -6993
- package/dist/index.d.mts +0 -23
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -25512
- package/dist/index.mjs +0 -18
- package/dist/src-APP5P3UD.mjs +0 -1386
- package/dist/src-D5HMDDVE.mjs +0 -1324
- package/dist/src-EK3WD4AU.mjs +0 -1327
- package/dist/src-LSZFLMFN.mjs +0 -1400
- package/dist/src-WIOCZRAC.mjs +0 -1397
- package/dist/src-YK6CHCMW.mjs +0 -1400
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export function createMcpAddWizard(transport) {
|
|
2
|
+
return {
|
|
3
|
+
transport,
|
|
4
|
+
step: transport === 'stdio' ? 'command' : 'url',
|
|
5
|
+
values: {},
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parseMcpCommandInput(rawValue) {
|
|
10
|
+
const input = String(rawValue || '').trim();
|
|
11
|
+
if (!input) {
|
|
12
|
+
return { command: '', args: [] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const parts = [];
|
|
16
|
+
let current = '';
|
|
17
|
+
let quote = null;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < input.length; i++) {
|
|
20
|
+
const char = input[i];
|
|
21
|
+
|
|
22
|
+
if (quote) {
|
|
23
|
+
if (char === quote) {
|
|
24
|
+
quote = null;
|
|
25
|
+
} else {
|
|
26
|
+
current += char;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (char === '"' || char === '\'') {
|
|
32
|
+
quote = char;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (/\s/.test(char)) {
|
|
37
|
+
if (current) {
|
|
38
|
+
parts.push(current);
|
|
39
|
+
current = '';
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
current += char;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (current) {
|
|
48
|
+
parts.push(current);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
command: parts[0] || '',
|
|
53
|
+
args: parts.slice(1),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getMcpAddWizardPrompt(wizard) {
|
|
58
|
+
switch (wizard.step) {
|
|
59
|
+
case 'command':
|
|
60
|
+
return {
|
|
61
|
+
title: 'Add MCP Server',
|
|
62
|
+
label: 'Command',
|
|
63
|
+
placeholder: 'npx -y @modelcontextprotocol/server-filesystem',
|
|
64
|
+
};
|
|
65
|
+
case 'url':
|
|
66
|
+
return {
|
|
67
|
+
title: 'Add MCP Server',
|
|
68
|
+
label: 'Server URL',
|
|
69
|
+
placeholder: wizard.transport === 'streamable_http'
|
|
70
|
+
? 'https://mcp.exa.ai/mcp'
|
|
71
|
+
: 'http://localhost:8080/sse',
|
|
72
|
+
};
|
|
73
|
+
case 'server_id':
|
|
74
|
+
return {
|
|
75
|
+
title: 'Add MCP Server',
|
|
76
|
+
label: 'Server ID',
|
|
77
|
+
placeholder: 'my-mcp-server',
|
|
78
|
+
};
|
|
79
|
+
default:
|
|
80
|
+
return {
|
|
81
|
+
title: 'Add MCP Server',
|
|
82
|
+
label: 'Value',
|
|
83
|
+
placeholder: '',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function submitMcpAddWizardValue(wizard, rawValue) {
|
|
89
|
+
const value = String(rawValue || '').trim();
|
|
90
|
+
if (!value) {
|
|
91
|
+
return {
|
|
92
|
+
done: false,
|
|
93
|
+
wizard,
|
|
94
|
+
error: 'Value cannot be empty.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (wizard.step === 'command') {
|
|
99
|
+
const parsed = parseMcpCommandInput(value);
|
|
100
|
+
return {
|
|
101
|
+
done: false,
|
|
102
|
+
wizard: {
|
|
103
|
+
...wizard,
|
|
104
|
+
step: 'server_id',
|
|
105
|
+
values: {
|
|
106
|
+
...wizard.values,
|
|
107
|
+
command: parsed.command,
|
|
108
|
+
...(parsed.args.length > 0 ? { args: parsed.args } : {}),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
error: null,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (wizard.step === 'url') {
|
|
116
|
+
return {
|
|
117
|
+
done: false,
|
|
118
|
+
wizard: {
|
|
119
|
+
...wizard,
|
|
120
|
+
step: 'server_id',
|
|
121
|
+
values: { ...wizard.values, url: value },
|
|
122
|
+
},
|
|
123
|
+
error: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
done: true,
|
|
129
|
+
payload: {
|
|
130
|
+
transport: wizard.transport,
|
|
131
|
+
...wizard.values,
|
|
132
|
+
id: value,
|
|
133
|
+
},
|
|
134
|
+
error: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
function normalizeQuery(query) {
|
|
2
|
+
return String(query || '').trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function inferProviderLabel(provider) {
|
|
6
|
+
return provider.display_name || provider.provider_name || provider.id || provider.provider_id || 'Provider';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function inferProviderId(provider) {
|
|
10
|
+
return provider.id || provider.provider_id || '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function inferDeployment(model) {
|
|
14
|
+
if (typeof model.deployment === 'string' && model.deployment.trim()) {
|
|
15
|
+
return model.deployment.trim().toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const metadataDeployment = model.metadata?.deployment;
|
|
19
|
+
if (typeof metadataDeployment === 'string' && metadataDeployment.trim()) {
|
|
20
|
+
return metadataDeployment.trim().toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (model.provider_id === 'ollama') {
|
|
24
|
+
const id = String(model.model_id || '').toLowerCase();
|
|
25
|
+
if (id.includes(':cloud') || id.includes('-cloud')) {
|
|
26
|
+
return 'cloud';
|
|
27
|
+
}
|
|
28
|
+
return 'local';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isModelMatch(model, query) {
|
|
35
|
+
if (!query) return true;
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
model.provider_name,
|
|
39
|
+
model.provider_id,
|
|
40
|
+
model.model_name,
|
|
41
|
+
model.model_id,
|
|
42
|
+
model.deployment,
|
|
43
|
+
]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.some(value => String(value).toLowerCase().includes(query));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isProviderMatch(provider, query) {
|
|
49
|
+
if (!query) return true;
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
provider.provider_name,
|
|
53
|
+
provider.provider_id,
|
|
54
|
+
'custom model',
|
|
55
|
+
]
|
|
56
|
+
.some(value => String(value).toLowerCase().includes(query));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function buildModelPickerEntries(providers, models, query = '', currentCustom = null) {
|
|
60
|
+
const normalizedQuery = normalizeQuery(query);
|
|
61
|
+
const grouped = new Map();
|
|
62
|
+
|
|
63
|
+
for (const provider of providers || []) {
|
|
64
|
+
const providerId = inferProviderId(provider);
|
|
65
|
+
if (!providerId) continue;
|
|
66
|
+
grouped.set(providerId, {
|
|
67
|
+
provider_id: providerId,
|
|
68
|
+
provider_name: inferProviderLabel(provider),
|
|
69
|
+
models: [],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const model of models || []) {
|
|
74
|
+
const providerId = model.provider_id;
|
|
75
|
+
if (!providerId) continue;
|
|
76
|
+
if (!grouped.has(providerId)) {
|
|
77
|
+
grouped.set(providerId, {
|
|
78
|
+
provider_id: providerId,
|
|
79
|
+
provider_name: model.provider_name || providerId,
|
|
80
|
+
models: [],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
grouped.get(providerId).models.push({
|
|
84
|
+
...model,
|
|
85
|
+
provider_name: model.provider_name || grouped.get(providerId).provider_name,
|
|
86
|
+
deployment: inferDeployment(model),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const providerGroups = Array.from(grouped.values()).sort((left, right) =>
|
|
91
|
+
left.provider_name.toLowerCase().localeCompare(right.provider_name.toLowerCase())
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const entries = [];
|
|
95
|
+
for (const provider of providerGroups) {
|
|
96
|
+
const visibleModels = provider.models
|
|
97
|
+
.filter(model => isModelMatch(model, normalizedQuery))
|
|
98
|
+
.sort((left, right) => {
|
|
99
|
+
const leftRank = left.deployment === 'local' ? 0 : left.deployment === 'cloud' ? 1 : 2;
|
|
100
|
+
const rightRank = right.deployment === 'local' ? 0 : right.deployment === 'cloud' ? 1 : 2;
|
|
101
|
+
return leftRank - rightRank
|
|
102
|
+
|| left.model_name.toLowerCase().localeCompare(right.model_name.toLowerCase());
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const providerMatches = isProviderMatch(provider, normalizedQuery);
|
|
106
|
+
if (visibleModels.length === 0 && !providerMatches) continue;
|
|
107
|
+
|
|
108
|
+
const currentCustomModelId = currentCustom?.provider_id === provider.provider_id
|
|
109
|
+
? currentCustom.model_id
|
|
110
|
+
: null;
|
|
111
|
+
|
|
112
|
+
entries.push({
|
|
113
|
+
type: 'section',
|
|
114
|
+
label: provider.provider_name,
|
|
115
|
+
provider_id: provider.provider_id,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
for (const model of visibleModels) {
|
|
119
|
+
entries.push({
|
|
120
|
+
type: 'model',
|
|
121
|
+
...model,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
entries.push({
|
|
126
|
+
type: 'custom',
|
|
127
|
+
provider_id: provider.provider_id,
|
|
128
|
+
provider_name: provider.provider_name,
|
|
129
|
+
is_current: Boolean(currentCustomModelId),
|
|
130
|
+
current_model_id: currentCustomModelId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return entries;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isSelectable(entry) {
|
|
138
|
+
return entry && entry.type !== 'section';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function movePickerSelection(entries, currentIndex, direction) {
|
|
142
|
+
if (!Array.isArray(entries) || entries.length === 0) return 0;
|
|
143
|
+
|
|
144
|
+
let index = Number.isInteger(currentIndex) ? currentIndex : 0;
|
|
145
|
+
while (true) {
|
|
146
|
+
const nextIndex = index + direction;
|
|
147
|
+
if (nextIndex < 0 || nextIndex >= entries.length) {
|
|
148
|
+
return index;
|
|
149
|
+
}
|
|
150
|
+
index = nextIndex;
|
|
151
|
+
if (isSelectable(entries[index])) {
|
|
152
|
+
return index;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function findFirstSelectableIndex(entries) {
|
|
158
|
+
if (!Array.isArray(entries)) return 0;
|
|
159
|
+
const index = entries.findIndex(isSelectable);
|
|
160
|
+
return index >= 0 ? index : 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function clampPickerScroll(selectedIndex, scrollOffset, visibleRows) {
|
|
164
|
+
if (!visibleRows || visibleRows <= 0) return 0;
|
|
165
|
+
|
|
166
|
+
const selected = Math.max(0, Number(selectedIndex) || 0);
|
|
167
|
+
const scroll = Math.max(0, Number(scrollOffset) || 0);
|
|
168
|
+
const viewport = Math.max(1, Number(visibleRows) || 1);
|
|
169
|
+
const end = scroll + viewport - 1;
|
|
170
|
+
|
|
171
|
+
if (selected < scroll) return selected;
|
|
172
|
+
if (selected > end) return selected - viewport + 1;
|
|
173
|
+
return scroll;
|
|
174
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const SLASH_COMMANDS = [
|
|
2
|
+
{ name: '/exit', description: 'Exit the TUI', aliases: [] },
|
|
3
|
+
{ name: '/stop', description: 'Stop the running agent', aliases: [] },
|
|
4
|
+
{ name: '/new', description: 'Reset session and start fresh', aliases: ['/reset'] },
|
|
5
|
+
{ name: '/clear', description: 'Clear chat history', aliases: [] },
|
|
6
|
+
{ name: '/help', description: 'Show available commands', aliases: [] },
|
|
7
|
+
{ name: '/status', description: 'Show agent status', aliases: [] },
|
|
8
|
+
{ name: '/model', description: 'Open model picker', aliases: [] },
|
|
9
|
+
{ name: '/vault', description: 'Open vault actions', aliases: ['/vault delete'] },
|
|
10
|
+
{ name: '/mcp', description: 'Open MCP actions', aliases: [] },
|
|
11
|
+
{ name: '/template', description: 'Open template actions', aliases: [] },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function matchCommands(input) {
|
|
15
|
+
if (!input.startsWith('/')) return [];
|
|
16
|
+
const lower = input.toLowerCase();
|
|
17
|
+
return SLASH_COMMANDS.filter(cmd =>
|
|
18
|
+
cmd.name.startsWith(lower) ||
|
|
19
|
+
lower.startsWith(cmd.name) ||
|
|
20
|
+
cmd.aliases.some(a => a.startsWith(lower) || lower.startsWith(a))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isSlashCommand(input) {
|
|
25
|
+
return input.startsWith('/');
|
|
26
|
+
}
|
package/src/tui/store.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useEventsStore(handler) {
|
|
4
|
+
const snapshot = useSyncExternalStore(
|
|
5
|
+
(callback) => {
|
|
6
|
+
handler.onChange(callback);
|
|
7
|
+
return () => handler.onChange(null);
|
|
8
|
+
},
|
|
9
|
+
() => handler.getSnapshot(),
|
|
10
|
+
);
|
|
11
|
+
return snapshot;
|
|
12
|
+
}
|
package/src/tui/theme.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const THEME = {
|
|
2
|
+
primary: 'cyan',
|
|
3
|
+
accent: '#FF9500',
|
|
4
|
+
text: 'white',
|
|
5
|
+
dim: 'gray',
|
|
6
|
+
border: 'gray',
|
|
7
|
+
error: 'red',
|
|
8
|
+
warning: 'yellow',
|
|
9
|
+
success: 'green',
|
|
10
|
+
user: '#FF9500',
|
|
11
|
+
assistant: 'cyan',
|
|
12
|
+
tool: 'gray',
|
|
13
|
+
toolError: 'red',
|
|
14
|
+
contextLow: 'green',
|
|
15
|
+
contextMed: '#FF9500',
|
|
16
|
+
contextHigh: 'red',
|
|
17
|
+
};
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
|
|
3
|
+
export function isInteractive() {
|
|
4
|
+
return Boolean(process.stdout.isTTY) && !process.env.CI;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class CancelledError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super('cancelled');
|
|
10
|
+
this.name = 'CancelledError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function handleCancel(value) {
|
|
15
|
+
if (p.isCancel(value)) {
|
|
16
|
+
throw new CancelledError();
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function withSpinner(startMsg, fn, stopMsg) {
|
|
22
|
+
const s = p.spinner();
|
|
23
|
+
s.start(startMsg);
|
|
24
|
+
try {
|
|
25
|
+
const result = await fn();
|
|
26
|
+
s.stop(stopMsg || 'Done.');
|
|
27
|
+
return result;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
s.stop(`Failed: ${err.message}`);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function pickAgent(client, message = 'Select an agent') {
|
|
35
|
+
const agents = await withSpinner('Fetching agents...', () => client.listAgents(), 'Agents loaded.');
|
|
36
|
+
if (!agents || agents.length === 0) {
|
|
37
|
+
p.log.warn('No agents found. Create one first with: moxxy agent create');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const selected = await p.select({
|
|
41
|
+
message,
|
|
42
|
+
options: agents.map(a => ({
|
|
43
|
+
value: a.name,
|
|
44
|
+
label: a.name,
|
|
45
|
+
hint: `${a.provider_id}/${a.model_id} [${a.status}]`,
|
|
46
|
+
})),
|
|
47
|
+
});
|
|
48
|
+
return handleCancel(selected);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function pickProvider(client) {
|
|
52
|
+
const providers = await withSpinner('Fetching providers...', () => client.listProviders(), 'Providers loaded.');
|
|
53
|
+
if (!providers || providers.length === 0) {
|
|
54
|
+
p.log.warn('No providers available. Install one first.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const selected = await p.select({
|
|
58
|
+
message: 'Select a provider',
|
|
59
|
+
options: providers.map(pr => ({
|
|
60
|
+
value: pr.id,
|
|
61
|
+
label: pr.display_name || pr.id,
|
|
62
|
+
})),
|
|
63
|
+
});
|
|
64
|
+
return handleCancel(selected);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function pickModel(client, providerId) {
|
|
68
|
+
const models = await withSpinner('Fetching models...', () => client.listModels(providerId), 'Models loaded.');
|
|
69
|
+
if (!models || models.length === 0) {
|
|
70
|
+
p.log.warn(`No models available for provider ${providerId}.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const selected = await p.select({
|
|
74
|
+
message: 'Select a model',
|
|
75
|
+
options: models.map(m => ({
|
|
76
|
+
value: m.model_id,
|
|
77
|
+
label: m.display_name || m.model_id,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
return handleCancel(selected);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function pickSkill(client, agentId, message = 'Select a skill') {
|
|
84
|
+
const skills = await withSpinner('Fetching skills...', () => client.listSkills(agentId), 'Skills loaded.');
|
|
85
|
+
if (!skills || skills.length === 0) {
|
|
86
|
+
p.log.warn('No skills found for this agent.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const statusIcon = (s) => s === 'approved' ? '\u2705' : s === 'quarantined' ? '\u23f3' : '\u274c';
|
|
90
|
+
const selected = await p.select({
|
|
91
|
+
message,
|
|
92
|
+
options: skills.map(s => ({
|
|
93
|
+
value: s.id,
|
|
94
|
+
label: `${statusIcon(s.status)} ${s.name} v${s.version}`,
|
|
95
|
+
hint: `[${s.status}] ${s.id.slice(0, 12)}`,
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
return handleCancel(selected);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function showResult(title, data) {
|
|
102
|
+
const lines = Object.entries(data)
|
|
103
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
104
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
105
|
+
.join('\n');
|
|
106
|
+
p.note(lines, title);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { p };
|
package/bin/moxxy.js
DELETED