@asobacloud/nehanda 0.6.1 → 0.8.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.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Single source of truth for nehanda-ui slash commands + help (#9).
3
+ * Autocomplete uses top-level entries (or nested when the user has typed a parent prefix).
4
+ */
5
+
6
+ /** @typedef {{ name: string, desc: string, autocomplete?: boolean }} SlashEntry */
7
+
8
+ /** @type {SlashEntry[]} */
9
+ export const TUI_SLASH_COMMANDS = [
10
+ { name: '/model', desc: 'Configure provider → key → host → discover → model' },
11
+ { name: '/key', desc: 'Set Nehanda API key, then continue configure' },
12
+ { name: '/key openrouter [VALUE]', desc: 'OpenRouter key → continue configure', autocomplete: false },
13
+ { name: '/key openai [VALUE]', desc: 'OpenAI-compat key → continue configure', autocomplete: false },
14
+ { name: '/key zhipu [VALUE]', desc: 'Zhipu (z.ai) key → continue configure', autocomplete: false },
15
+ { name: '/key nehanda [VALUE]', desc: 'Nehanda key → continue configure', autocomplete: false },
16
+ { name: '/config', desc: 'Show provider, model, endpoint, key status' },
17
+ { name: '/mcp', desc: 'MCP server & tool management' },
18
+ { name: '/mcp list', desc: 'List tools from configured MCP servers', autocomplete: false },
19
+ { name: '/mcp status', desc: 'Show configured servers', autocomplete: false },
20
+ { name: '/mcp reload [server]', desc: 'Reload mcp.json into this session', autocomplete: false },
21
+ { name: '/mcp add <name> <command> [args…]', desc: 'Register a server (~/.config/nehanda/mcp.json)', autocomplete: false },
22
+ { name: '/mcp env', desc: 'Show env vars for every server (values masked)', autocomplete: false },
23
+ { name: '/mcp env <server>', desc: 'Show env vars for one server', autocomplete: false },
24
+ { name: '/mcp env <server> <NAME> <VALUE>', desc: 'Set env var e.g. /mcp env jupyter JUPYTER_TOKEN sk-…', autocomplete: false },
25
+ { name: '/mcp env unset <server> <NAME>', desc: 'Remove an env var from a server', autocomplete: false },
26
+ { name: '/clear', desc: 'New conversation' },
27
+ { name: '/retry', desc: 'Resend the last failed message' },
28
+ { name: '/help', desc: 'Show this help' },
29
+ { name: '/exit', desc: 'Quit (/quit)' },
30
+ ]
31
+
32
+ /** Top-level commands only (slash menu when typing `/`). */
33
+ export function topLevelSlashCommands() {
34
+ return TUI_SLASH_COMMANDS.filter(c => c.autocomplete !== false && !c.name.slice(1).includes(' '))
35
+ }
36
+
37
+ /**
38
+ * Filter for Ink slash autocomplete.
39
+ * - Bare `/foo` → top-level matches
40
+ * - `/mcp…` → MCP entries (including subcommands)
41
+ * - `/key…` → /key variants
42
+ */
43
+ export function filterSlashAutocomplete(filter) {
44
+ const f = String(filter || '')
45
+ const lower = f.toLowerCase()
46
+ if (lower.startsWith('/mcp')) {
47
+ const needle = lower.trimEnd()
48
+ return TUI_SLASH_COMMANDS.filter(c => {
49
+ if (!c.name.startsWith('/mcp')) return false
50
+ if (needle === '/mcp') return true
51
+ return c.name.toLowerCase().startsWith(needle)
52
+ })
53
+ }
54
+ if (lower.startsWith('/key')) {
55
+ const needle = lower.trimEnd()
56
+ return TUI_SLASH_COMMANDS.filter(c => {
57
+ if (!c.name.startsWith('/key')) return false
58
+ if (needle === '/key') return true
59
+ return c.name.toLowerCase().startsWith(needle)
60
+ })
61
+ }
62
+ return topLevelSlashCommands().filter(c =>
63
+ c.name.startsWith(f) || c.name.includes(f.slice(1)))
64
+ }
65
+
66
+ /** Full help text for `/help` (no chalk — caller may colorize). */
67
+ export function formatTuiHelpText() {
68
+ const pad = Math.max(...TUI_SLASH_COMMANDS.map(c => c.name.length), 12) + 2
69
+ const lines = ['', 'Commands:', '']
70
+ let lastGroup = ''
71
+ for (const c of TUI_SLASH_COMMANDS) {
72
+ const group = c.name.split(/\s+/)[0]
73
+ if (lastGroup && group !== lastGroup && (group === '/mcp' || lastGroup === '/mcp' || group === '/clear')) {
74
+ lines.push('')
75
+ }
76
+ lastGroup = group
77
+ lines.push(` ${c.name.padEnd(pad)}${c.desc}`)
78
+ }
79
+ lines.push('')
80
+ return lines.join('\n')
81
+ }
82
+
83
+ /**
84
+ * Parse `/mcp env …` argument tokens (after `/mcp`).
85
+ * @param {string[]} parts - e.g. ['env'] | ['env','jupyter'] | ['env','unset','jupyter','TOKEN'] | ['env','jupyter','TOKEN','sk-…']
86
+ * @returns {{ action: string, server?: string, name?: string, value?: string, error?: string }}
87
+ */
88
+ export function parseMcpEnvParts(parts) {
89
+ // parts[0] should be 'env'
90
+ const rest = parts.slice(1)
91
+ if (rest.length === 0) return { action: 'list_all' }
92
+
93
+ const head = (rest[0] || '').toLowerCase()
94
+ if (head === 'unset' || head === 'clear') {
95
+ const server = rest[1]
96
+ const name = rest[2]
97
+ if (!server || !name) {
98
+ return {
99
+ action: 'usage_error',
100
+ error: 'Usage: /mcp env unset <server> <NAME>\n Example: /mcp env unset jupyter JUPYTER_TOKEN',
101
+ }
102
+ }
103
+ if (rest.length > 3) {
104
+ return {
105
+ action: 'usage_error',
106
+ error: 'Usage: /mcp env unset <server> <NAME>\n Do not pass a value when unsetting.',
107
+ }
108
+ }
109
+ return { action: 'unset', server, name }
110
+ }
111
+
112
+ const server = rest[0]
113
+ if (rest.length === 1) return { action: 'list_server', server }
114
+
115
+ const name = rest[1]
116
+ const valueParts = rest.slice(2)
117
+ if (valueParts.length === 0) {
118
+ return {
119
+ action: 'usage_error',
120
+ error: [
121
+ `Missing VALUE for ${name}.`,
122
+ ` Set: /mcp env ${server} ${name} <VALUE>`,
123
+ ` Unset: /mcp env unset ${server} ${name}`,
124
+ ].join('\n'),
125
+ }
126
+ }
127
+ return { action: 'set', server, name, value: valueParts.join(' ') }
128
+ }
package/lib/ui.mjs CHANGED
@@ -120,16 +120,18 @@ export function formatHelp(provider, dbPath) {
120
120
  cmd('/compact', 'Compact conversation'),
121
121
  cmd('/team', 'Manage teams'),
122
122
  '',
123
- cmd('/model [name]', 'Change model'),
123
+ cmd('/model', 'Configure provider, key, host, model'),
124
+ cmd('/key [provider] [value]', 'Save API key then continue configure'),
124
125
  cmd('/login', 'Store credentials'),
125
126
  cmd('/logout', 'Clear credentials'),
126
127
  cmd('/status', 'Auth status'),
127
- cmd('/config', 'Show / set settings'),
128
- cmd('/config set <key> <value>', 'Set any config value'),
128
+ cmd('/config', 'Show provider / model / key status'),
129
129
  cmd('/mcp', 'MCP server management & guide'),
130
130
  cmd('/mcp list', 'List all discovered tools & descriptions'),
131
131
  cmd('/mcp status', 'Show configured MCP servers'),
132
- cmd('/mcp env [server] [KEY] [val]', 'Set MCP env vars (API keys)'),
132
+ cmd('/mcp env', 'Show env vars (masked)'),
133
+ cmd('/mcp env <server> <NAME> <VALUE>', 'Set an env var'),
134
+ cmd('/mcp env unset <server> <NAME>', 'Remove an env var'),
133
135
  cmd('/clear', 'New conversation'),
134
136
  cmd('/exit', 'Quit (/quit)'),
135
137
  '',
@@ -141,15 +143,18 @@ export function formatHelp(provider, dbPath) {
141
143
  }
142
144
 
143
145
  export function formatMcpHelp() {
144
- const cmd = (name, desc) => ` ${colors.command(name.padEnd(28))} ${colors.dim(desc)}`
146
+ const cmd = (name, desc) => ` ${colors.command(name.padEnd(40))} ${colors.dim(desc)}`
145
147
  const lines = [
146
148
  '',
147
149
  colors.header('MCP Server & Tool Commands'),
148
- cmd('/mcp list', 'Discover and list all available MCP tools'),
149
- cmd('/mcp status', 'Show configured servers and command paths'),
150
- cmd('/mcp reload [server]', 'Reload mcp.json configs into session'),
151
- cmd('/mcp add <name> <cmd> [args]', 'Register a new MCP server'),
152
- cmd('/mcp env [server] [KEY] [val]', 'View or set server environment variables'),
150
+ cmd('/mcp list', 'List tools from configured MCP servers'),
151
+ cmd('/mcp status', 'Show configured servers'),
152
+ cmd('/mcp reload [server]', 'Reload mcp.json into this session'),
153
+ cmd('/mcp add <name> <command> [args…]', 'Register a server'),
154
+ cmd('/mcp env', 'Show env vars for every server (values masked)'),
155
+ cmd('/mcp env <server>', 'Show env vars for one server'),
156
+ cmd('/mcp env <server> <NAME> <VALUE>', 'Set env var e.g. /mcp env jupyter JUPYTER_TOKEN sk-…'),
157
+ cmd('/mcp env unset <server> <NAME>', 'Remove an env var'),
153
158
  '',
154
159
  colors.dim(' Tip: Run /mcp list to inspect tools injected into the assistant.'),
155
160
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asobacloud/nehanda",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Agentic terminal REPL and single-process engine for governed AI deep research and software development. Connects to Nehanda v3, OpenRouter, Ollama, LM Studio, ZhipuAI, or any OpenAI-compatible API. Includes Laya System-1 control plane for memory management, pre-execution safety gating, and intent-driven instruction shaping.",
6
6
  "keywords": [