@crystalplatforms/adm 0.2.3 → 0.3.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.
Files changed (89) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +154 -161
  3. package/bin/adm +18 -21
  4. package/package.json +9 -22
  5. package/scripts/completion/adm.bash +12 -55
  6. package/scripts/completion/adm.zsh +21 -58
  7. package/scripts/install.js +8 -8
  8. package/src/commands/config.js +66 -0
  9. package/src/commands/dotfiles.js +49 -32
  10. package/src/commands/feedback.js +12 -0
  11. package/src/commands/help.js +33 -0
  12. package/src/commands/uninstall.js +73 -54
  13. package/src/commands/version.js +7 -0
  14. package/src/commands.js +26 -0
  15. package/src/config/index.js +32 -32
  16. package/src/data/extensions.json +358 -358
  17. package/src/engines.js +38 -0
  18. package/src/install/executor.js +263 -0
  19. package/src/install/hints.js +94 -0
  20. package/src/install/npm-prefix.js +110 -0
  21. package/src/install/plan.js +75 -0
  22. package/src/install/report.js +46 -0
  23. package/src/install/run.js +88 -0
  24. package/src/install/shell-paths.js +129 -0
  25. package/src/legacy-cleanup.js +96 -0
  26. package/src/pkg/builders.js +220 -0
  27. package/src/pkg/managers.js +59 -0
  28. package/src/pkg/package-names.js +229 -0
  29. package/src/pkg/script-urls.js +100 -0
  30. package/src/platform/index.js +129 -0
  31. package/src/platform/os-release.js +27 -0
  32. package/src/platform/shell.js +111 -0
  33. package/src/reports.js +37 -0
  34. package/src/router.js +157 -0
  35. package/src/setup/catalog.js +13 -13
  36. package/src/setup/git-step.js +128 -0
  37. package/src/setup/loader.js +108 -0
  38. package/src/setup/prompts.js +259 -0
  39. package/src/setup/registry.js +48 -0
  40. package/src/setup/spinner.js +67 -0
  41. package/src/setup/wizard.js +319 -195
  42. package/src/upgrade/index.js +499 -0
  43. package/src/utils/closest-match.js +36 -0
  44. package/src/utils/dotfiles-sync.js +89 -89
  45. package/src/utils/file-conflict-handler.js +30 -30
  46. package/bin/adm-tui +0 -13
  47. package/src/ai/knowledge.js +0 -61
  48. package/src/commands/connect.js +0 -76
  49. package/src/commands/issue.js +0 -80
  50. package/src/commands/pr.js +0 -55
  51. package/src/commands/theme.js +0 -103
  52. package/src/commands/upgrade.js +0 -114
  53. package/src/installer/git-config.js +0 -34
  54. package/src/installer/node-installer.js +0 -50
  55. package/src/installer/package-manager-installer.js +0 -31
  56. package/src/installer/ssh-setup.js +0 -31
  57. package/src/installer/system-packages.js +0 -49
  58. package/src/installers/index.js +0 -66
  59. package/src/integrations/ai-backend.js +0 -60
  60. package/src/integrations/ai-providers/registry.js +0 -261
  61. package/src/integrations/github.js +0 -107
  62. package/src/integrations/gitlab.js +0 -121
  63. package/src/plugins/context.js +0 -64
  64. package/src/plugins/loader.js +0 -50
  65. package/src/plugins/runner.js +0 -25
  66. package/src/setup/installer.js +0 -192
  67. package/src/shell/detector.js +0 -10
  68. package/src/tui/app-state.js +0 -374
  69. package/src/tui/app.js +0 -442
  70. package/src/tui/autocomplete.js +0 -57
  71. package/src/tui/commands/registry.js +0 -823
  72. package/src/tui/components/SetupScreen.js +0 -292
  73. package/src/ui/animations.js +0 -24
  74. package/src/ui/spinner.js +0 -34
  75. package/src/ui/theme/cyberpunk.js +0 -14
  76. package/src/ui/theme/dark.js +0 -14
  77. package/src/ui/theme/forest.js +0 -14
  78. package/src/ui/theme/light.js +0 -14
  79. package/src/ui/theme/monokai.js +0 -14
  80. package/src/ui/theme/nord.js +0 -14
  81. package/src/ui/theme.js +0 -49
  82. package/src/utils/command-history.js +0 -43
  83. package/src/utils/error-handler.js +0 -69
  84. package/src/utils/fuzzy-search.js +0 -20
  85. package/src/utils/github-error-handler.js +0 -26
  86. package/src/utils/keychain.js +0 -97
  87. package/src/utils/open-url.js +0 -15
  88. package/src/utils/terminal-detection.js +0 -26
  89. package/src/utils/token-encryption.js +0 -96
@@ -1,192 +0,0 @@
1
- const { promisify } = require('util');
2
- const execAsync = promisify(require('child_process').exec);
3
- const execSync = require('child_process').execSync;
4
- const path = require('path');
5
- const os = require('os');
6
- const fs = require('fs');
7
-
8
- const SCRIPT_URLS = {
9
- rustup: 'https://sh.rustup.rs',
10
- bun: 'https://bun.sh/install',
11
- conda: 'https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh',
12
- gcloud: 'https://sdk.cloud.google.com',
13
- 'azure-cli': 'https://aka.ms/InstallAzureCli',
14
- composer: 'https://getcomposer.org/installer',
15
- vcpkg: 'https://github.com/microsoft/vcpkg',
16
- 'oh-my-zsh': 'https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh',
17
- };
18
-
19
- // Ensure npm uses a user-writable directory (fixes EACCES without sudo)
20
- let _npmFixed = false;
21
- function ensureNpmPrefix() {
22
- if (_npmFixed) return;
23
- _npmFixed = true;
24
- try {
25
- const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
26
- const userPrefix = path.join(os.homedir(), '.npm-global');
27
- const userBin = path.join(userPrefix, 'bin');
28
- const needsPrefixChange = prefix === '/usr/local' || prefix === '/usr';
29
- const needsPathFix = !process.env.PATH.split(':').includes(userBin);
30
-
31
- if (needsPrefixChange) {
32
- if (!fs.existsSync(userPrefix)) {
33
- fs.mkdirSync(userBin, { recursive: true });
34
- }
35
- execSync(`npm config set prefix '${userPrefix}'`, { stdio: 'pipe' });
36
- }
37
-
38
- // Always ensure PATH is set — even if prefix was already changed before
39
- if (needsPathFix || needsPrefixChange) {
40
- addToShellPath(userBin);
41
- }
42
- } catch {
43
- // Can't fix — will fail later with helpful message
44
- }
45
- }
46
-
47
- // Add directory to PATH in shell config (.zshrc or .bashrc)
48
- function addToShellPath(dir) {
49
- const marker = '# adm-cli npm path';
50
- const exportLine = `export PATH="${dir}:\$PATH" ${marker}`;
51
-
52
- const shellRc = process.env.SHELL && process.env.SHELL.includes('zsh')
53
- ? path.join(os.homedir(), '.zshrc')
54
- : path.join(os.homedir(), '.bashrc');
55
-
56
- try {
57
- let content = '';
58
- if (fs.existsSync(shellRc)) {
59
- content = fs.readFileSync(shellRc, 'utf8');
60
- }
61
- // Remove old adm path entries
62
- const lines = content.split('\n').filter(l => !l.includes(marker));
63
- lines.push(exportLine);
64
- fs.writeFileSync(shellRc, lines.join('\n'), 'utf8');
65
- } catch {
66
- // Can't write to shell rc — user will need to add manually
67
- }
68
-
69
- // Also set in current process so subsequent installs find tools
70
- process.env.PATH = `${dir}:${process.env.PATH}`;
71
- }
72
-
73
- function getNpmBinPath() {
74
- try {
75
- const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
76
- return path.join(prefix, 'bin');
77
- } catch {
78
- return '';
79
- }
80
- }
81
-
82
- function getInstallCommand(tool, platform) {
83
- const method = tool.installMethod;
84
- const id = tool.id;
85
-
86
- const commands = {
87
- npm: `npm install -g ${id}@latest`,
88
- brew: platform === 'darwin' ? `brew install ${id}` : `sudo apt-get install -y ${id}`,
89
- pip: `pip install --upgrade ${id}`,
90
- cargo: `cargo install ${id}`,
91
- go: `go install ${id}@latest`,
92
- gem: `gem install ${id}`,
93
- composer: `composer global require ${id}`,
94
- dotnet: `dotnet tool install -g ${id}`,
95
- script: `curl -fsSL ${SCRIPT_URLS[id] || 'https://example.com/install'} | sh`,
96
- nvm: `curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.4/install.sh | bash`,
97
- node: `npm install -g ${id}@latest`,
98
- python: `python3 -m pip install --upgrade ${id}`,
99
- rustup: `rustup component add ${id}`,
100
- apt: `sudo apt-get install -y ${id}`,
101
- manual: `# Manual install required for ${id}`,
102
- };
103
-
104
- if (method === 'link') {
105
- return `echo 'Visit: ${tool.url || tool.id}'`;
106
- }
107
-
108
- return commands[method] || `# Unknown install method: ${method}`;
109
- }
110
-
111
- function isToolInstalled(tool, checkFn) {
112
- const check = checkFn || execSync;
113
- const cmd = tool.command || tool.id;
114
- try {
115
- check(`command -v ${cmd} >/dev/null 2>&1`, { shell: '/bin/bash', stdio: 'pipe' });
116
- return true;
117
- } catch {
118
- return false;
119
- }
120
- }
121
-
122
- async function installToolAsync(tool, platform) {
123
- if (tool.installMethod === 'link') {
124
- return { id: tool.id, name: tool.name, status: 'link', message: `Visit marketplace: ${tool.url || 'N/A'}` };
125
- }
126
-
127
- if (isToolInstalled(tool)) {
128
- return { id: tool.id, name: tool.name, status: 'skipped', message: 'already installed' };
129
- }
130
-
131
- // For npm/node methods, fix permissions first
132
- if (tool.installMethod === 'npm' || tool.installMethod === 'node') {
133
- ensureNpmPrefix();
134
- }
135
-
136
- const cmd = getInstallCommand(tool, platform);
137
- try {
138
- await execAsync(cmd, { shell: '/bin/bash' });
139
- // Refresh PATH so subsequent tools can find this one
140
- refreshPath();
141
- return { id: tool.id, name: tool.name, status: 'installed', message: 'installed' };
142
- } catch (err) {
143
- const stderr = err.stderr || '';
144
- let hint = '';
145
- if (stderr.includes('EACCES')) {
146
- hint = 'Fix: npm config set prefix ~/.npm-global && add ~/.npm-global/bin to PATH';
147
- }
148
- return { id: tool.id, name: tool.name, status: 'failed', error: (stderr.split('\n')[0] || err.message), hint, command: cmd };
149
- }
150
- }
151
-
152
- // Refresh PATH by reading shell rc files (picks up new tool paths like ~/.bun/bin)
153
- function refreshPath() {
154
- const home = os.homedir();
155
- const extraDirs = [
156
- path.join(home, '.npm-global', 'bin'),
157
- path.join(home, '.bun', 'bin'),
158
- path.join(home, '.cargo', 'bin'),
159
- path.join(home, 'go', 'bin'),
160
- '/opt/homebrew/bin',
161
- '/usr/local/bin',
162
- ];
163
- for (const dir of extraDirs) {
164
- if (fs.existsSync(dir) && !process.env.PATH.split(':').includes(dir)) {
165
- process.env.PATH = `${dir}:${process.env.PATH}`;
166
- }
167
- }
168
- }
169
-
170
- async function installTools(tools, platform, checkFn) {
171
- const run = checkFn || execSync;
172
- const results = [];
173
-
174
- for (const tool of tools) {
175
- if (isToolInstalled(tool, run)) {
176
- results.push({ id: tool.id, name: tool.name, status: 'skipped', message: `${tool.name} is already installed` });
177
- continue;
178
- }
179
-
180
- const cmd = getInstallCommand(tool, platform);
181
- try {
182
- run(cmd, { shell: '/bin/bash', stdio: 'pipe' });
183
- results.push({ id: tool.id, name: tool.name, status: 'installed', message: `${tool.name} installed` });
184
- } catch (err) {
185
- results.push({ id: tool.id, name: tool.name, status: 'failed', error: err.message });
186
- }
187
- }
188
-
189
- return results;
190
- }
191
-
192
- module.exports = { getInstallCommand, isToolInstalled, installTools, installToolAsync };
@@ -1,10 +0,0 @@
1
- // Minimal shell detector stub for TDD
2
- function detectShell(env) {
3
- env = env || process.env;
4
- const shell = env.SHELL || '';
5
- if (shell.includes('zsh')) return 'zsh';
6
- if (shell.includes('bash')) return 'bash';
7
- if (env.TERM_PROGRAM && env.TERM_PROGRAM.includes('Apple_Terminal')) return 'bash';
8
- return 'unknown';
9
- }
10
- module.exports = { detectShell };
@@ -1,374 +0,0 @@
1
- const { createRegistry, getPlaceholderText } = require('./commands/registry');
2
- const { resolveTheme } = require('../ui/theme');
3
- const { readConfig, writeConfig } = require('../config');
4
- const ai = require('../integrations/ai-backend');
5
- const { getKnowledge } = require('../ai/knowledge');
6
- const { createAutocomplete, updateAutocomplete, moveUp, moveDown, selectActive, closeAutocomplete } = require('./autocomplete');
7
- const fs = require('fs');
8
- const path = require('path');
9
- const os = require('os');
10
-
11
- const VERSION = `Crystal ADM-CLI ${require('../../package.json').version}`;
12
- const WELCOME_TEXT = 'Welcome to ADM! Type /help for commands or /download to install developer tools.';
13
-
14
- function createAppState() {
15
- // Load saved config synchronously for correct initial state (no re-render needed)
16
- let savedTheme = 'dark';
17
- let savedProvider = 'glm';
18
- try {
19
- const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.adm', 'config.json'), 'utf8'));
20
- if (cfg.theme) savedTheme = cfg.theme;
21
- if (cfg.aiProvider) savedProvider = cfg.aiProvider;
22
- } catch {}
23
-
24
- const config = { theme: savedTheme };
25
- const themeState = { current: savedTheme };
26
- const theme = resolveTheme(config);
27
- const registry = createRegistry({ theme: themeState, execSync: null });
28
-
29
- const ac = createAutocomplete(
30
- (partial) => registry.autocomplete(partial),
31
- (name) => registry.getCommandInfo(name),
32
- );
33
-
34
- const state = {
35
- messages: [{ text: WELCOME_TEXT, type: 'system' }],
36
- theme,
37
- themeState,
38
- config,
39
- aiMode: false,
40
- showSetup: false,
41
- setupDryRun: false,
42
- connectStep: null,
43
- connectPlatform: null,
44
- connectCursor: 0,
45
- modelStep: null,
46
- modelProvider: null,
47
- aiLoading: false,
48
- githubStep: null,
49
- githubCursor: 0,
50
- confirmStep: null,
51
- upgradeLoading: false,
52
- aiHistory: [],
53
- activeProvider: savedProvider,
54
- };
55
-
56
- let knowledge = null;
57
- try {
58
- knowledge = getKnowledge(VERSION, process.cwd());
59
- } catch {
60
- // knowledge unavailable — continue without it
61
- }
62
-
63
- function disableAI() {
64
- state.aiMode = false;
65
- state.aiHistory = [];
66
- state.messages.push({ text: 'AI: off', type: 'system' });
67
- }
68
-
69
- async function processInput(input) {
70
- const trimmed = input.trim();
71
-
72
- // In AI mode, intercept /exit and /ai to leave/toggle AI mode
73
- const cmd = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
74
- if (state.aiMode && (cmd === 'exit' || cmd === 'ai')) {
75
- state.aiMode = false;
76
- state.aiHistory = [];
77
- state.messages.push({ text: 'Exited AI mode.', type: 'system' });
78
- return { output: null, shouldExit: false, shouldClear: false };
79
- }
80
-
81
- // In AI mode, send non-command input to the active AI provider
82
- if (state.aiMode) {
83
- state.aiLoading = true;
84
- try {
85
- const messages = [];
86
- if (knowledge) {
87
- messages.push({ role: 'system', content: knowledge });
88
- }
89
- messages.push(...state.aiHistory);
90
- messages.push({ role: 'user', content: input });
91
-
92
- const cfg = await readConfig();
93
- const providerId = cfg.aiProvider || 'glm';
94
- state.activeProvider = providerId;
95
- const apiKey = cfg[`ai.${providerId}Key`] || process.env.GLM_API_KEY;
96
-
97
- if (!apiKey && providerId !== 'ollama') {
98
- throw new Error(`AI not configured. No API key for ${providerId}. Use /model ${providerId} to set one.`);
99
- }
100
-
101
- const { queryWithProvider } = require('../integrations/ai-providers/registry');
102
- const response = await queryWithProvider(providerId, input, { apiKey, messages });
103
-
104
- state.aiHistory.push({ role: 'user', content: input });
105
- state.aiHistory.push({ role: 'assistant', content: response });
106
-
107
- const prefix = providerId === 'glm' ? 'GLM' : providerId;
108
- state.messages.push({ text: `${prefix}: ${response}`, type: 'ai' });
109
- } catch (err) {
110
- state.messages.push({ text: `AI error: ${err.message}`, type: 'ai-error' });
111
- } finally {
112
- state.aiLoading = false;
113
- }
114
- return { output: null, shouldExit: false, shouldClear: false };
115
- }
116
-
117
- // Set upgrade loading before async dispatch
118
- const cmdName = trimmed.startsWith('/') ? trimmed.slice(1).split(/\s+/)[0] : trimmed.split(/\s+/)[0];
119
- if (cmdName === 'upgrade') {
120
- state.upgradeLoading = true;
121
- }
122
-
123
- const result = await registry.dispatch(input);
124
- state.upgradeLoading = false;
125
-
126
- if (result.shouldClear) {
127
- state.messages.length = 0; // mutate in-place so external refs stay valid
128
- return result;
129
- }
130
-
131
- if (result.shouldToggleAI) {
132
- state.aiMode = !state.aiMode;
133
- const label = state.aiMode
134
- ? `AI: ON (${state.activeProvider}) — type a question, /exit to leave AI mode`
135
- : 'AI: off';
136
- state.messages.push({ text: label, type: 'system' });
137
- if (result.output) {
138
- state.messages.push({ text: result.output, type: 'ai' });
139
- }
140
- return result;
141
- }
142
-
143
- if (result.shouldShowSetup) {
144
- state.showSetup = true;
145
- state.setupDryRun = result.dryRun || false;
146
- return result;
147
- }
148
-
149
- if (result.shouldStartConnect) {
150
- startConnect();
151
- return result;
152
- }
153
-
154
- if (result.shouldStartGithub) {
155
- startGithub();
156
- return result;
157
- }
158
-
159
- if (result.shouldPromptModelToken) {
160
- state.modelStep = 'token';
161
- state.modelProvider = result.modelProvider;
162
- state.messages.push({ text: `Enter your ${result.modelProvider} API key:`, type: 'system' });
163
- return result;
164
- }
165
-
166
- if (result.needsConfirm) {
167
- state.confirmStep = {
168
- message: result.confirmMessage,
169
- onConfirm: result.onConfirm,
170
- onCancel: result.onCancel,
171
- };
172
- state.messages.push({ text: result.output, type: 'system' });
173
- return result;
174
- }
175
-
176
- if (result.output) {
177
- state.messages.push({ text: result.output, type: 'command' });
178
- }
179
-
180
- return result;
181
- }
182
-
183
- function exitAI() {
184
- if (state.aiMode) {
185
- disableAI();
186
- }
187
- }
188
-
189
- function exitSetup() {
190
- state.showSetup = false;
191
- state.setupDryRun = false;
192
- }
193
-
194
- function startConnect() {
195
- state.connectStep = 'select';
196
- state.connectCursor = 0;
197
- state.messages.push({ text: 'Select a platform:', type: 'system' });
198
- }
199
-
200
- function moveConnectCursor(direction) {
201
- const options = ['GitHub', 'GitLab'];
202
- state.connectCursor = (state.connectCursor + direction + options.length) % options.length;
203
- }
204
-
205
- function selectConnectPlatform() {
206
- const options = ['github', 'gitlab'];
207
- const names = ['GitHub', 'GitLab'];
208
- state.connectPlatform = options[state.connectCursor];
209
- state.connectStep = 'token';
210
- state.messages.push({ text: `Enter your ${names[state.connectCursor]} PAT:`, type: 'system' });
211
- }
212
-
213
- async function submitConnectToken(token) {
214
- const platform = state.connectPlatform;
215
- try {
216
- if (platform === 'github') {
217
- const gh = require('../integrations/github');
218
- const result = await gh.connect(token);
219
- state.messages.push({ text: `GitHub: connected as ${result.user.login}`, type: 'system' });
220
- } else {
221
- const gl = require('../integrations/gitlab');
222
- const result = await gl.connect(token);
223
- state.messages.push({ text: `GitLab: connected as ${result.user.username}`, type: 'system' });
224
- }
225
- } catch (err) {
226
- state.messages.push({ text: err.message, type: 'ai-error' });
227
- }
228
- state.connectStep = null;
229
- state.connectPlatform = null;
230
- }
231
-
232
- function cancelConnect() {
233
- state.messages.push({ text: 'Cancelled.', type: 'system' });
234
- state.connectStep = null;
235
- state.connectPlatform = null;
236
- }
237
-
238
- const GITHUB_OPTIONS = 4;
239
-
240
- function startGithub() {
241
- state.githubStep = 'select';
242
- state.githubCursor = 0;
243
- state.messages.push({ text: 'Select an action:', type: 'system' });
244
- }
245
-
246
- function moveGithubCursor(direction) {
247
- state.githubCursor = (state.githubCursor + direction + GITHUB_OPTIONS) % GITHUB_OPTIONS;
248
- }
249
-
250
- async function selectGithubItem() {
251
- const idx = state.githubCursor;
252
- state.githubStep = null;
253
-
254
- const subcommands = ['status', 'pr list', 'issue list', 'commit suggest'];
255
- await processInput(`github ${subcommands[idx]}`);
256
- }
257
-
258
- function cancelGithub() {
259
- state.messages.push({ text: 'Cancelled.', type: 'system' });
260
- state.githubStep = null;
261
- }
262
-
263
- async function submitModelToken(token) {
264
- const provider = state.modelProvider;
265
- const { getProvider } = require('../integrations/ai-providers/registry');
266
- const providerInfo = getProvider(provider);
267
- const config = await readConfig();
268
- config[`ai.${provider}Key`] = token;
269
- config.aiProvider = provider;
270
- await writeConfig(config);
271
- const name = providerInfo ? providerInfo.name : provider;
272
- state.messages.push({ text: `Provider switched to ${name} (${provider}).`, type: 'system' });
273
- state.modelStep = null;
274
- state.modelProvider = null;
275
- }
276
-
277
- function cancelModelToken() {
278
- state.messages.push({ text: 'Cancelled.', type: 'system' });
279
- state.modelStep = null;
280
- state.modelProvider = null;
281
- }
282
-
283
- async function confirmAction() {
284
- if (!state.confirmStep) return { output: null, shouldExit: false, shouldClear: false };
285
- const { onConfirm } = state.confirmStep;
286
- state.confirmStep = null;
287
- const result = await onConfirm();
288
- if (result.output) {
289
- state.messages.push({ text: result.output, type: 'system' });
290
- }
291
- return result;
292
- }
293
-
294
- function confirmCancel() {
295
- if (!state.confirmStep) return;
296
- const { onCancel } = state.confirmStep;
297
- state.confirmStep = null;
298
- const result = onCancel();
299
- if (result.output) {
300
- state.messages.push({ text: result.output, type: 'system' });
301
- }
302
- return result;
303
- }
304
-
305
- function markSetupDone() {
306
- state.setupInstalled = true;
307
- }
308
-
309
- function getStatusBar() {
310
- return {
311
- themeName: themeState.current,
312
- aiMode: state.aiMode,
313
- version: VERSION,
314
- activeProvider: state.activeProvider,
315
- };
316
- }
317
-
318
- function getSuggestions(input) {
319
- const trimmed = input.trim();
320
- const stripped = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed;
321
- return registry.autocomplete(stripped);
322
- }
323
-
324
- function getPlaceholder(input) {
325
- return getPlaceholderText(input, registry.getSubcommands);
326
- }
327
-
328
- return {
329
- get messages() { return state.messages; },
330
- theme,
331
- themeState,
332
- get aiMode() { return state.aiMode; },
333
- get showSetup() { return state.showSetup; },
334
- get setupDryRun() { return state.setupDryRun; },
335
- get connectStep() { return state.connectStep; },
336
- set connectStep(v) { state.connectStep = v; },
337
- get connectPlatform() { return state.connectPlatform; },
338
- get connectCursor() { return state.connectCursor; },
339
- processInput,
340
- getStatusBar,
341
- exitAI,
342
- exitSetup,
343
- moveConnectCursor,
344
- selectConnectPlatform,
345
- submitConnectToken,
346
- cancelConnect,
347
- get githubStep() { return state.githubStep; },
348
- get githubCursor() { return state.githubCursor; },
349
- moveGithubCursor,
350
- selectGithubItem,
351
- cancelGithub,
352
- submitModelToken,
353
- cancelModelToken,
354
- get modelStep() { return state.modelStep; },
355
- get modelProvider() { return state.modelProvider; },
356
- get aiLoading() { return state.aiLoading; },
357
- get upgradeLoading() { return state.upgradeLoading; },
358
- get confirmStep() { return state.confirmStep; },
359
- confirmAction,
360
- confirmCancel,
361
- markSetupDone,
362
- get setupInstalled() { return state.setupInstalled; },
363
- getSuggestions,
364
- getPlaceholder,
365
- autocomplete: ac,
366
- updateAutocomplete: (input) => updateAutocomplete(ac, input),
367
- moveAutocompleteUp: () => moveUp(ac),
368
- moveAutocompleteDown: () => moveDown(ac),
369
- selectAutocompleteActive: () => selectActive(ac),
370
- closeAutocomplete: () => closeAutocomplete(ac),
371
- };
372
- }
373
-
374
- module.exports = { createAppState, VERSION, WELCOME_TEXT };