opencode-agora 0.4.0 → 0.4.2
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 +89 -415
- package/dist/atomic-write.d.ts +10 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +23 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/auth/refresh.d.ts +17 -0
- package/dist/auth/refresh.d.ts.map +1 -0
- package/dist/auth/refresh.js +50 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/cli/app.d.ts +11 -19
- package/dist/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +159 -2028
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/browse.d.ts +4 -0
- package/dist/cli/commands/browse.d.ts.map +1 -0
- package/dist/cli/commands/browse.js +80 -0
- package/dist/cli/commands/browse.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +4 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +125 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/community.d.ts +12 -0
- package/dist/cli/commands/community.d.ts.map +1 -0
- package/dist/cli/commands/community.js +453 -0
- package/dist/cli/commands/community.js.map +1 -0
- package/dist/cli/commands/export.d.ts +3 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +108 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +299 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/learn.d.ts +4 -0
- package/dist/cli/commands/learn.d.ts.map +1 -0
- package/dist/cli/commands/learn.js +62 -0
- package/dist/cli/commands/learn.js.map +1 -0
- package/dist/cli/commands/marketplace.d.ts +9 -0
- package/dist/cli/commands/marketplace.d.ts.map +1 -0
- package/dist/cli/commands/marketplace.js +321 -0
- package/dist/cli/commands/marketplace.js.map +1 -0
- package/dist/cli/commands/notify.d.ts +3 -0
- package/dist/cli/commands/notify.d.ts.map +1 -0
- package/dist/cli/commands/notify.js +59 -0
- package/dist/cli/commands/notify.js.map +1 -0
- package/dist/cli/commands/operations.d.ts +16 -0
- package/dist/cli/commands/operations.d.ts.map +1 -0
- package/dist/cli/commands/operations.js +1041 -0
- package/dist/cli/commands/operations.js.map +1 -0
- package/dist/cli/commands/outdated.d.ts +3 -0
- package/dist/cli/commands/outdated.d.ts.map +1 -0
- package/dist/cli/commands/outdated.js +48 -0
- package/dist/cli/commands/outdated.js.map +1 -0
- package/dist/cli/commands/ping.d.ts +3 -0
- package/dist/cli/commands/ping.d.ts.map +1 -0
- package/dist/cli/commands/ping.js +56 -0
- package/dist/cli/commands/ping.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +3 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +35 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/today.d.ts +3 -0
- package/dist/cli/commands/today.d.ts.map +1 -0
- package/dist/cli/commands/today.js +142 -0
- package/dist/cli/commands/today.js.map +1 -0
- package/dist/cli/commands/types.d.ts +5 -0
- package/dist/cli/commands/types.d.ts.map +1 -0
- package/dist/cli/commands/types.js +2 -0
- package/dist/cli/commands/types.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +3 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +41 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/commands/welcome.d.ts +3 -0
- package/dist/cli/commands/welcome.d.ts.map +1 -0
- package/dist/cli/commands/welcome.js +97 -0
- package/dist/cli/commands/welcome.js.map +1 -0
- package/dist/cli/commands-meta.d.ts.map +1 -1
- package/dist/cli/commands-meta.js +286 -29
- package/dist/cli/commands-meta.js.map +1 -1
- package/dist/cli/completions-gen.d.ts +2 -0
- package/dist/cli/completions-gen.d.ts.map +1 -0
- package/dist/cli/completions-gen.js +195 -0
- package/dist/cli/completions-gen.js.map +1 -0
- package/dist/cli/completions.d.ts.map +1 -1
- package/dist/cli/completions.js +42 -5
- package/dist/cli/completions.js.map +1 -1
- package/dist/cli/flags.d.ts +19 -0
- package/dist/cli/flags.d.ts.map +1 -0
- package/dist/cli/flags.js +91 -0
- package/dist/cli/flags.js.map +1 -0
- package/dist/cli/format.d.ts +19 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/format.js +249 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/helpers.d.ts +95 -0
- package/dist/cli/helpers.d.ts.map +1 -0
- package/dist/cli/helpers.js +301 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/mcp-server.d.ts +7 -1
- package/dist/cli/mcp-server.d.ts.map +1 -1
- package/dist/cli/mcp-server.js +70 -2
- package/dist/cli/mcp-server.js.map +1 -1
- package/dist/cli/menu.d.ts.map +1 -1
- package/dist/cli/menu.js +11 -3
- package/dist/cli/menu.js.map +1 -1
- package/dist/cli/pages/community.d.ts +6 -0
- package/dist/cli/pages/community.d.ts.map +1 -1
- package/dist/cli/pages/community.js +882 -64
- package/dist/cli/pages/community.js.map +1 -1
- package/dist/cli/pages/helpers.d.ts +14 -9
- package/dist/cli/pages/helpers.d.ts.map +1 -1
- package/dist/cli/pages/helpers.js +37 -6
- package/dist/cli/pages/helpers.js.map +1 -1
- package/dist/cli/pages/home.d.ts +1 -0
- package/dist/cli/pages/home.d.ts.map +1 -1
- package/dist/cli/pages/home.js +203 -120
- package/dist/cli/pages/home.js.map +1 -1
- package/dist/cli/pages/marketplace.d.ts +2 -0
- package/dist/cli/pages/marketplace.d.ts.map +1 -1
- package/dist/cli/pages/marketplace.js +524 -62
- package/dist/cli/pages/marketplace.js.map +1 -1
- package/dist/cli/pages/news.d.ts +28 -0
- package/dist/cli/pages/news.d.ts.map +1 -1
- package/dist/cli/pages/news.js +209 -82
- package/dist/cli/pages/news.js.map +1 -1
- package/dist/cli/pages/settings.d.ts.map +1 -1
- package/dist/cli/pages/settings.js +163 -33
- package/dist/cli/pages/settings.js.map +1 -1
- package/dist/cli/pages/types.d.ts +1 -1
- package/dist/cli/pages/types.d.ts.map +1 -1
- package/dist/cli/prompter.d.ts.map +1 -1
- package/dist/cli/prompter.js +43 -8
- package/dist/cli/prompter.js.map +1 -1
- package/dist/cli/shell.d.ts +2 -2
- package/dist/cli/shell.d.ts.map +1 -1
- package/dist/cli/shell.js +321 -18
- package/dist/cli/shell.js.map +1 -1
- package/dist/cli/tui.d.ts +1 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +69 -23
- package/dist/cli/tui.js.map +1 -1
- package/dist/community/client.d.ts +45 -8
- package/dist/community/client.d.ts.map +1 -1
- package/dist/community/client.js +118 -23
- package/dist/community/client.js.map +1 -1
- package/dist/community/search.d.ts +25 -0
- package/dist/community/search.d.ts.map +1 -0
- package/dist/community/search.js +62 -0
- package/dist/community/search.js.map +1 -0
- package/dist/community/types.d.ts +21 -0
- package/dist/community/types.d.ts.map +1 -1
- package/dist/community/types.js +1 -1
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +1 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +142 -68
- package/dist/data.js.map +1 -1
- package/dist/format.d.ts +0 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +0 -2
- package/dist/format.js.map +1 -1
- package/dist/hubs/cache.d.ts +6 -0
- package/dist/hubs/cache.d.ts.map +1 -0
- package/dist/hubs/cache.js +46 -0
- package/dist/hubs/cache.js.map +1 -0
- package/dist/hubs/enrichment.d.ts +43 -0
- package/dist/hubs/enrichment.d.ts.map +1 -0
- package/dist/hubs/enrichment.js +239 -0
- package/dist/hubs/enrichment.js.map +1 -0
- package/dist/hubs/github.d.ts +12 -0
- package/dist/hubs/github.d.ts.map +1 -0
- package/dist/hubs/github.js +54 -0
- package/dist/hubs/github.js.map +1 -0
- package/dist/hubs/huggingface.d.ts +27 -0
- package/dist/hubs/huggingface.d.ts.map +1 -0
- package/dist/hubs/huggingface.js +88 -0
- package/dist/hubs/huggingface.js.map +1 -0
- package/dist/hubs/quality.d.ts +26 -0
- package/dist/hubs/quality.d.ts.map +1 -0
- package/dist/hubs/quality.js +57 -0
- package/dist/hubs/quality.js.map +1 -0
- package/dist/hubs/types.d.ts +30 -0
- package/dist/hubs/types.d.ts.map +1 -0
- package/dist/hubs/types.js +2 -0
- package/dist/hubs/types.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -1
- package/dist/init.js +2 -2
- package/dist/init.js.map +1 -1
- package/dist/live.d.ts +10 -0
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +31 -1
- package/dist/live.js.map +1 -1
- package/dist/marketplace.d.ts +16 -3
- package/dist/marketplace.d.ts.map +1 -1
- package/dist/marketplace.js +174 -7
- package/dist/marketplace.js.map +1 -1
- package/dist/news/cache.d.ts.map +1 -1
- package/dist/news/cache.js +4 -3
- package/dist/news/cache.js.map +1 -1
- package/dist/news/score.js +1 -1
- package/dist/news/sources/arxiv.d.ts.map +1 -1
- package/dist/news/sources/arxiv.js +10 -6
- package/dist/news/sources/arxiv.js.map +1 -1
- package/dist/news/sources/github-trending.d.ts.map +1 -1
- package/dist/news/sources/github-trending.js +9 -5
- package/dist/news/sources/github-trending.js.map +1 -1
- package/dist/news/sources/hn.d.ts.map +1 -1
- package/dist/news/sources/hn.js +8 -4
- package/dist/news/sources/hn.js.map +1 -1
- package/dist/news/sources/reddit.d.ts.map +1 -1
- package/dist/news/sources/reddit.js +5 -4
- package/dist/news/sources/reddit.js.map +1 -1
- package/dist/news/sources/rss.d.ts +10 -11
- package/dist/news/sources/rss.d.ts.map +1 -1
- package/dist/news/sources/rss.js +11 -99
- package/dist/news/sources/rss.js.map +1 -1
- package/dist/news/types.d.ts +3 -0
- package/dist/news/types.d.ts.map +1 -1
- package/dist/news/types.js +15 -6
- package/dist/news/types.js.map +1 -1
- package/dist/outdated.d.ts +24 -0
- package/dist/outdated.d.ts.map +1 -0
- package/dist/outdated.js +53 -0
- package/dist/outdated.js.map +1 -0
- package/dist/preferences.d.ts.map +1 -1
- package/dist/preferences.js +4 -4
- package/dist/preferences.js.map +1 -1
- package/dist/scan.d.ts +26 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +179 -0
- package/dist/scan.js.map +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +14 -20
- package/dist/settings.js.map +1 -1
- package/dist/state.d.ts +9 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +41 -19
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +1 -1
- package/package.json +4 -2
package/dist/cli/shell.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { execSync, spawn } from 'node:child_process';
|
|
2
|
-
import { existsSync, mkdtempSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { appendFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir, tmpdir } from 'node:os';
|
|
4
4
|
import { join, resolve, sep } from 'node:path';
|
|
5
5
|
import { COMMANDS } from './commands-meta.js';
|
|
6
6
|
import { runInteractiveMenu } from './menu.js';
|
|
7
7
|
import { runTui } from './tui.js';
|
|
8
|
-
import { AGORA_VERSION
|
|
8
|
+
import { AGORA_VERSION } from './app.js';
|
|
9
|
+
import { FREE_MODELS } from './commands/chat.js';
|
|
9
10
|
import { detectAgoraDataDir, loadAgoraState, resolveSavedItems } from '../state.js';
|
|
10
11
|
import { appendTranscript, loadSessionMeta, readTranscript, recentBashContext, writeSessionMeta } from '../transcript.js';
|
|
11
12
|
import { gradientText, renderBanner, supportsTrueColor } from '../ui.js';
|
|
@@ -15,6 +16,8 @@ import { completeShellLine, ghostFromHistory } from './completions.js';
|
|
|
15
16
|
import { getMarketplaceItems } from '../marketplace.js';
|
|
16
17
|
const SHELL_BUILTINS = new Set(['cd', 'export', 'alias', 'source', 'unset', 'umask', 'exec']);
|
|
17
18
|
const MAX_BASH_BUFFER = 16 * 1024;
|
|
19
|
+
const SHELL_HISTORY_FILE = 'shell-history.jsonl';
|
|
20
|
+
const SHELL_HISTORY_MAX = 2000;
|
|
18
21
|
/** Map slash aliases (with leading `/`) to TUI page ids. */
|
|
19
22
|
const TUI_SLASH_ALIASES = {
|
|
20
23
|
'/tui': 'default',
|
|
@@ -26,6 +29,34 @@ const TUI_SLASH_ALIASES = {
|
|
|
26
29
|
'/news': 'news',
|
|
27
30
|
'/settings': 'settings'
|
|
28
31
|
};
|
|
32
|
+
const LETTER_SHORTCUTS = {
|
|
33
|
+
'/a': { kind: 'meta', sub: 'again' },
|
|
34
|
+
'/b': { kind: 'bash', cmd: 'agora browse' },
|
|
35
|
+
'/c': { kind: 'tui', page: 'community' },
|
|
36
|
+
'/d': { kind: 'bash', cmd: 'agora config doctor' },
|
|
37
|
+
'/e': { kind: 'meta', sub: 'env' },
|
|
38
|
+
'/f': { kind: 'meta', sub: 'fg' },
|
|
39
|
+
'/g': { kind: 'bash', cmd: 'agora search' },
|
|
40
|
+
'/h': { kind: 'tui', page: 'home' },
|
|
41
|
+
'/i': { kind: 'bash', cmd: 'agora init' },
|
|
42
|
+
'/j': { kind: 'meta', sub: 'jobs' },
|
|
43
|
+
'/k': { kind: 'bash', cmd: 'agora search' },
|
|
44
|
+
'/l': { kind: 'meta', sub: 'last' },
|
|
45
|
+
'/m': { kind: 'tui', page: 'marketplace' },
|
|
46
|
+
'/n': { kind: 'tui', page: 'news' },
|
|
47
|
+
'/o': { kind: 'bash', cmd: 'agora browse' },
|
|
48
|
+
'/p': { kind: 'bash', cmd: 'agora preferences' },
|
|
49
|
+
'/q': { kind: 'meta', sub: 'quit' },
|
|
50
|
+
'/r': { kind: 'bash', cmd: 'agora reviews' },
|
|
51
|
+
'/s': { kind: 'tui', page: 'settings' },
|
|
52
|
+
'/t': { kind: 'meta', sub: 'terminal' },
|
|
53
|
+
'/u': { kind: 'bash', cmd: 'agora use' },
|
|
54
|
+
'/v': { kind: 'meta', sub: 'verbose' },
|
|
55
|
+
'/w': { kind: 'bash', cmd: 'agora watch' },
|
|
56
|
+
'/x': { kind: 'bash', cmd: 'agora export' },
|
|
57
|
+
'/y': { kind: 'bash', cmd: 'agora history' },
|
|
58
|
+
'/z': { kind: 'bash', cmd: 'agora config doctor --fix' }
|
|
59
|
+
};
|
|
29
60
|
const QUESTION_STARTERS = new Set([
|
|
30
61
|
'what',
|
|
31
62
|
'why',
|
|
@@ -58,12 +89,20 @@ const QUESTION_STARTERS = new Set([
|
|
|
58
89
|
'hey',
|
|
59
90
|
'thanks'
|
|
60
91
|
]);
|
|
92
|
+
const ENV_VAR_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
93
|
+
const BACKTICK_RE = /^`[^`]+`/;
|
|
61
94
|
export function looksLikeQuestion(line) {
|
|
62
95
|
const trimmed = line.trim();
|
|
63
96
|
if (!trimmed)
|
|
64
97
|
return false;
|
|
65
98
|
if (trimmed.endsWith('?'))
|
|
66
99
|
return true;
|
|
100
|
+
// Env-prefixed commands (FOO=bar cmd) are always bash, never questions
|
|
101
|
+
if (ENV_VAR_RE.test(trimmed))
|
|
102
|
+
return false;
|
|
103
|
+
// Backtick-prefixed commands are always bash
|
|
104
|
+
if (BACKTICK_RE.test(trimmed))
|
|
105
|
+
return false;
|
|
67
106
|
const words = trimmed.split(/\s+/);
|
|
68
107
|
const firstWord = words[0].toLowerCase();
|
|
69
108
|
if (QUESTION_STARTERS.has(firstWord))
|
|
@@ -79,6 +118,8 @@ export function classifyInput(line, isExecutable) {
|
|
|
79
118
|
const trimmed = line.trim();
|
|
80
119
|
if (!trimmed)
|
|
81
120
|
return { kind: 'noop' };
|
|
121
|
+
if (trimmed === '/abc')
|
|
122
|
+
return { kind: 'meta', sub: 'abc' };
|
|
82
123
|
if (trimmed === '/help')
|
|
83
124
|
return { kind: 'meta', sub: 'help' };
|
|
84
125
|
if (trimmed === '/quit')
|
|
@@ -103,6 +144,14 @@ export function classifyInput(line, isExecutable) {
|
|
|
103
144
|
return { kind: 'meta', sub: 'last' };
|
|
104
145
|
if (trimmed === '/again')
|
|
105
146
|
return { kind: 'meta', sub: 'again' };
|
|
147
|
+
if (trimmed === '/jobs')
|
|
148
|
+
return { kind: 'meta', sub: 'jobs' };
|
|
149
|
+
if (trimmed === '/fg' || trimmed.startsWith('/fg '))
|
|
150
|
+
return { kind: 'meta', sub: 'fg', args: trimmed.slice(4).trim() };
|
|
151
|
+
if (trimmed === '/bg' || trimmed.startsWith('/bg '))
|
|
152
|
+
return { kind: 'meta', sub: 'bg', args: trimmed.slice(4).trim() };
|
|
153
|
+
if (trimmed === '/env' || trimmed.startsWith('/env '))
|
|
154
|
+
return { kind: 'meta', sub: 'env', args: trimmed.slice(5).trim() };
|
|
106
155
|
if (trimmed.startsWith('/? '))
|
|
107
156
|
return { kind: 'meta', sub: 'dry-run', args: trimmed.slice(3).trim() };
|
|
108
157
|
if (trimmed.startsWith('!'))
|
|
@@ -116,7 +165,18 @@ export function classifyInput(line, isExecutable) {
|
|
|
116
165
|
const target = TUI_SLASH_ALIASES[trimmed];
|
|
117
166
|
return target === 'default' ? { kind: 'tui' } : { kind: 'tui', page: target };
|
|
118
167
|
}
|
|
119
|
-
//
|
|
168
|
+
// Single-letter shortcuts (exact match only, no args).
|
|
169
|
+
const letterDisp = LETTER_SHORTCUTS[trimmed];
|
|
170
|
+
if (letterDisp) {
|
|
171
|
+
if (letterDisp.kind === 'meta') {
|
|
172
|
+
return { kind: 'meta', sub: letterDisp.sub };
|
|
173
|
+
}
|
|
174
|
+
if (letterDisp.kind === 'tui') {
|
|
175
|
+
return { kind: 'tui', page: letterDisp.page };
|
|
176
|
+
}
|
|
177
|
+
return letterDisp;
|
|
178
|
+
}
|
|
179
|
+
// Slash-prefixed inputs that weren't an exact meta, TUI, or letter match are
|
|
120
180
|
// forwarded to the `agora` CLI: `/agora help`, `/agora search foo`,
|
|
121
181
|
// `/help tutorials`, `/foo` all become `agora <args>`. Never let
|
|
122
182
|
// `/anything` fall through to bash — PATH-joining an absolute name like
|
|
@@ -244,6 +304,44 @@ function copyToClipboard(text) {
|
|
|
244
304
|
// fall back silently
|
|
245
305
|
}
|
|
246
306
|
}
|
|
307
|
+
// ── Shell history persistence ────────────────────────────────────────────────
|
|
308
|
+
function getShellHistoryPath(dataDir) {
|
|
309
|
+
return join(dataDir, SHELL_HISTORY_FILE);
|
|
310
|
+
}
|
|
311
|
+
function loadShellHistory(dataDir) {
|
|
312
|
+
const path = getShellHistoryPath(dataDir);
|
|
313
|
+
if (!existsSync(path))
|
|
314
|
+
return [];
|
|
315
|
+
try {
|
|
316
|
+
const raw = readFileSync(path, 'utf8');
|
|
317
|
+
const entries = [];
|
|
318
|
+
for (const line of raw.split('\n').filter(Boolean).reverse()) {
|
|
319
|
+
try {
|
|
320
|
+
const parsed = JSON.parse(line);
|
|
321
|
+
if (parsed.line)
|
|
322
|
+
entries.push(parsed.line);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (entries.length >= SHELL_HISTORY_MAX)
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
return entries;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function appendShellHistory(dataDir, line) {
|
|
337
|
+
const path = getShellHistoryPath(dataDir);
|
|
338
|
+
try {
|
|
339
|
+
appendFileSync(path, JSON.stringify({ line, ts: new Date().toISOString() }) + '\n', 'utf8');
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// best-effort
|
|
343
|
+
}
|
|
344
|
+
}
|
|
247
345
|
// ── Main shell loop ─────────────────────────────────────────────────────────
|
|
248
346
|
export async function runShell(io, style) {
|
|
249
347
|
const env = io.env ?? {};
|
|
@@ -253,7 +351,7 @@ export async function runShell(io, style) {
|
|
|
253
351
|
const motto = "Developers' CLI marketplace and community hub - type a command, bash or chat:";
|
|
254
352
|
const mottoLine = gradientText(motto, { trueColor });
|
|
255
353
|
const model = FREE_MODELS[0];
|
|
256
|
-
const infoLine = style.dim(`v${AGORA_VERSION} · ${model} · /
|
|
354
|
+
const infoLine = style.dim(`v${AGORA_VERSION} · ${model} · /abc · /help · /menu · /search · /quit`);
|
|
257
355
|
const slashLine = style.orange('/home · /marketplace · /community · /news · /settings');
|
|
258
356
|
process.stdout.write(`\n${banner}\n\n${mottoLine}\n\n${infoLine}\n${slashLine}\n\n`);
|
|
259
357
|
}
|
|
@@ -286,6 +384,9 @@ export async function runShell(io, style) {
|
|
|
286
384
|
let verbosity = 'medium';
|
|
287
385
|
let childActive = false;
|
|
288
386
|
let totalCost = 0;
|
|
387
|
+
const trackedEnv = new Map();
|
|
388
|
+
let jobCounter = 0;
|
|
389
|
+
const jobs = new Map();
|
|
289
390
|
// Lazy caches for completion ids
|
|
290
391
|
let cachedMarketplaceIds = null;
|
|
291
392
|
let cachedSavedIds = null;
|
|
@@ -312,6 +413,7 @@ export async function runShell(io, style) {
|
|
|
312
413
|
'/comm',
|
|
313
414
|
'/news',
|
|
314
415
|
'/settings',
|
|
416
|
+
'/abc',
|
|
315
417
|
'/help',
|
|
316
418
|
'/menu',
|
|
317
419
|
'/transcript',
|
|
@@ -323,6 +425,10 @@ export async function runShell(io, style) {
|
|
|
323
425
|
'/exit',
|
|
324
426
|
'/last',
|
|
325
427
|
'/again',
|
|
428
|
+
'/env',
|
|
429
|
+
'/jobs',
|
|
430
|
+
'/fg',
|
|
431
|
+
'/bg',
|
|
326
432
|
...agoraSlashCommands
|
|
327
433
|
];
|
|
328
434
|
// Deduplicate: /news, /home etc may appear in both lists
|
|
@@ -348,9 +454,10 @@ export async function runShell(io, style) {
|
|
|
348
454
|
},
|
|
349
455
|
cwd: currentCwd
|
|
350
456
|
};
|
|
351
|
-
//
|
|
352
|
-
const history =
|
|
457
|
+
// Shell history — persisted to disk and loaded on startup
|
|
458
|
+
const history = loadShellHistory(dataDir);
|
|
353
459
|
const tips = [
|
|
460
|
+
'type /abc for a quick letter-shortcut reference (/a /b /c ...)',
|
|
354
461
|
'type /help to see all slash commands',
|
|
355
462
|
'type /menu to browse the command catalog',
|
|
356
463
|
'type ?<msg> to force AI chat',
|
|
@@ -358,20 +465,39 @@ export async function runShell(io, style) {
|
|
|
358
465
|
'type /clear to reset and see the home banner',
|
|
359
466
|
'type /transcript to see your last 20 commands',
|
|
360
467
|
'type /last to re-run the last bash command',
|
|
468
|
+
'type /again to re-send the last chat message',
|
|
469
|
+
'type /? <agora_cmd> to dry-run an agora command',
|
|
361
470
|
'type ?how do I use MCP? to ask about the marketplace',
|
|
362
471
|
'type /verbose for detailed AI responses',
|
|
363
472
|
'type /quiet for minimal AI responses',
|
|
473
|
+
'type /env to view tracked env vars, /env FOO=val to set one',
|
|
364
474
|
'press Tab to auto-complete commands and paths',
|
|
365
475
|
'press Ctrl-R to reverse-search your history',
|
|
366
476
|
'press Ctrl-L to clear the screen',
|
|
367
477
|
'press Esc to dismiss ghost suggestions',
|
|
368
478
|
'run `agora search --table` for a table view',
|
|
369
479
|
'run `agora search --sort stars` to sort by stars',
|
|
370
|
-
'run `agora search --sort name --order asc` for alphabetical'
|
|
480
|
+
'run `agora search --sort name --order asc` for alphabetical',
|
|
481
|
+
'run `agora completions bash | source /dev/stdin` for bash completions',
|
|
482
|
+
'run `agora completions zsh > /usr/local/share/zsh/site-functions/_agora` for zsh completions',
|
|
483
|
+
'run `agora completions fish > ~/.config/fish/completions/agora.fish` for fish completions',
|
|
484
|
+
'type /home to open the TUI Home page',
|
|
485
|
+
'type /marketplace to open the TUI Marketplace page',
|
|
486
|
+
'type /settings to open the TUI Settings page',
|
|
487
|
+
'run `agora save <id>` to bookmark a package',
|
|
488
|
+
'run `agora saved` to see your saved items',
|
|
489
|
+
'run `agora auth login --api-url <url>` to connect the community',
|
|
490
|
+
'run `agora config doctor` to check your OpenCode config',
|
|
491
|
+
'type VAR=val command to set env vars in bash',
|
|
492
|
+
'pipe output with | or redirect with > as normal in bash',
|
|
493
|
+
'append & to run a command in the background',
|
|
494
|
+
'type /jobs to see background jobs, /fg to bring one forward',
|
|
495
|
+
'type /bg to resume a stopped job in the background',
|
|
496
|
+
'run `agora shell` anywhere to re-enter this interactive mode'
|
|
371
497
|
];
|
|
372
498
|
// Amber chevron when opencode unavailable, accent otherwise
|
|
373
|
-
const accentChevron = opencodeAvailable ? style.accent('›') : '
|
|
374
|
-
//
|
|
499
|
+
const accentChevron = opencodeAvailable ? style.accent('›') : style.orange('›');
|
|
500
|
+
// Static portion of the prompt (no chevron); suffix added dynamically
|
|
375
501
|
function buildPromptBase() {
|
|
376
502
|
return style.accent('agora') + ' ' + style.dim(shortCwd(currentCwd)) + ' ';
|
|
377
503
|
}
|
|
@@ -382,15 +508,18 @@ export async function runShell(io, style) {
|
|
|
382
508
|
}
|
|
383
509
|
function buildContextLine() {
|
|
384
510
|
const model = FREE_MODELS[0];
|
|
385
|
-
const
|
|
386
|
-
|
|
511
|
+
const turnCount = meta?.turnCount ?? 0;
|
|
512
|
+
const tip = tips[turnCount % tips.length];
|
|
513
|
+
const parts = [`model: ${model}`, `${turnCount} turns`];
|
|
514
|
+
if (totalCost > 0)
|
|
515
|
+
parts.push(`$${totalCost.toFixed(6)}`);
|
|
516
|
+
parts.push(tip);
|
|
517
|
+
return style.dim(parts.join(' · '));
|
|
387
518
|
}
|
|
388
519
|
const sigintHandler = () => {
|
|
520
|
+
// SIGINT while idle: prompter handles Ctrl-C bytes directly via raw mode
|
|
389
521
|
if (childActive)
|
|
390
522
|
return;
|
|
391
|
-
// Ctrl-C while idle: the prompter handles abort; SIGINT from outside is rare
|
|
392
|
-
process.stdout.write('\n');
|
|
393
|
-
process.exit(0);
|
|
394
523
|
};
|
|
395
524
|
process.on('SIGINT', sigintHandler);
|
|
396
525
|
try {
|
|
@@ -417,9 +546,10 @@ export async function runShell(io, style) {
|
|
|
417
546
|
const line = result.value;
|
|
418
547
|
if (!line.trim())
|
|
419
548
|
continue;
|
|
420
|
-
// Add to history
|
|
549
|
+
// Add to history and persist
|
|
421
550
|
if (history[history.length - 1] !== line) {
|
|
422
551
|
history.push(line);
|
|
552
|
+
appendShellHistory(dataDir, line);
|
|
423
553
|
}
|
|
424
554
|
const dispatch = classifyInput(line, isExecutable);
|
|
425
555
|
if (dispatch.kind === 'noop')
|
|
@@ -432,6 +562,10 @@ export async function runShell(io, style) {
|
|
|
432
562
|
printHome();
|
|
433
563
|
continue;
|
|
434
564
|
}
|
|
565
|
+
if (dispatch.sub === 'abc') {
|
|
566
|
+
printLetterHelp(style);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
435
569
|
if (dispatch.sub === 'help') {
|
|
436
570
|
printHelp(style);
|
|
437
571
|
continue;
|
|
@@ -503,6 +637,115 @@ export async function runShell(io, style) {
|
|
|
503
637
|
}
|
|
504
638
|
continue;
|
|
505
639
|
}
|
|
640
|
+
if (dispatch.sub === 'jobs') {
|
|
641
|
+
if (jobs.size === 0) {
|
|
642
|
+
process.stdout.write(style.dim('No background jobs. Append & to run a command in the background.') + '\n');
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
process.stdout.write(style.accent('Background jobs') + '\n');
|
|
646
|
+
for (const [id, job] of jobs) {
|
|
647
|
+
const status = job.status === 'running' ? style.dim('running') : style.dim('stopped');
|
|
648
|
+
process.stdout.write(` [${id}] ${status} ${job.cmd}\n`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (dispatch.sub === 'fg') {
|
|
654
|
+
if (jobs.size === 0) {
|
|
655
|
+
process.stdout.write(style.dim('No background jobs. Append & to run a command in the background.') + '\n');
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const arg = dispatch.args || '';
|
|
659
|
+
if (arg && Number.isNaN(parseInt(arg, 10))) {
|
|
660
|
+
process.stdout.write(style.dim(`Invalid job id: ${arg}`) + '\n');
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const targetId = arg ? parseInt(arg, 10) : Math.max(...Array.from(jobs.keys()));
|
|
664
|
+
const job = jobs.get(targetId);
|
|
665
|
+
if (!job) {
|
|
666
|
+
process.stdout.write(style.dim(`Job ${targetId} not found.`) + '\n');
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
process.kill(job.pid, 0);
|
|
671
|
+
process.stdout.write(style.dim(`Foreground: ${job.cmd}`) + '\n');
|
|
672
|
+
await new Promise((resolve) => {
|
|
673
|
+
const check = setInterval(() => {
|
|
674
|
+
try {
|
|
675
|
+
process.kill(job.pid, 0);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
clearInterval(check);
|
|
679
|
+
jobs.delete(targetId);
|
|
680
|
+
resolve();
|
|
681
|
+
}
|
|
682
|
+
}, 200);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
jobs.delete(targetId);
|
|
687
|
+
process.stdout.write(style.dim(`Job ${targetId} has finished.`) + '\n');
|
|
688
|
+
}
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (dispatch.sub === 'bg') {
|
|
692
|
+
if (jobs.size === 0) {
|
|
693
|
+
process.stdout.write(style.dim('No background jobs. Append & to run a command in the background.') + '\n');
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
const arg = dispatch.args || '';
|
|
697
|
+
if (arg && Number.isNaN(parseInt(arg, 10))) {
|
|
698
|
+
process.stdout.write(style.dim(`Invalid job id: ${arg}`) + '\n');
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const targetId = arg ? parseInt(arg, 10) : Math.max(...Array.from(jobs.keys()));
|
|
702
|
+
const job = jobs.get(targetId);
|
|
703
|
+
if (!job) {
|
|
704
|
+
process.stdout.write(style.dim(`Job ${targetId} not found.`) + '\n');
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
process.kill(job.pid, 0);
|
|
709
|
+
job.status = 'running';
|
|
710
|
+
process.stdout.write(style.dim(`[${targetId}] ${job.cmd} (background)`) + '\n');
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
jobs.delete(targetId);
|
|
714
|
+
process.stdout.write(style.dim(`Job ${targetId} has finished.`) + '\n');
|
|
715
|
+
}
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (dispatch.sub === 'env') {
|
|
719
|
+
const arg = dispatch.args || '';
|
|
720
|
+
if (!arg) {
|
|
721
|
+
if (trackedEnv.size === 0) {
|
|
722
|
+
process.stdout.write(style.dim('No tracked environment variables. Use /env VAR=value to set one.') + '\n');
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
process.stdout.write(style.accent('Tracked environment') + '\n');
|
|
726
|
+
for (const [k, v] of [...trackedEnv.entries()].sort()) {
|
|
727
|
+
process.stdout.write(` ${k}=${v}\n`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
else if (arg.includes('=')) {
|
|
732
|
+
const eqIdx = arg.indexOf('=');
|
|
733
|
+
const key = arg.slice(0, eqIdx).trim();
|
|
734
|
+
const val = arg.slice(eqIdx + 1).trim();
|
|
735
|
+
trackedEnv.set(key, val);
|
|
736
|
+
process.stdout.write(style.dim(`${key}=${val} (tracked)`) + '\n');
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
const val = trackedEnv.get(arg);
|
|
740
|
+
if (val === undefined) {
|
|
741
|
+
process.stdout.write(style.dim(`${arg} is not tracked.`) + '\n');
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
process.stdout.write(`${arg}=${val}\n`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
506
749
|
continue;
|
|
507
750
|
}
|
|
508
751
|
if (dispatch.kind === 'tui') {
|
|
@@ -510,6 +753,46 @@ export async function runShell(io, style) {
|
|
|
510
753
|
continue;
|
|
511
754
|
}
|
|
512
755
|
if (dispatch.kind === 'bash') {
|
|
756
|
+
// Track env vars from export statements and VAR=val prefix
|
|
757
|
+
const bashLine = dispatch.cmd;
|
|
758
|
+
const exportMatch = bashLine.match(/^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.+)$/);
|
|
759
|
+
if (exportMatch) {
|
|
760
|
+
trackedEnv.set(exportMatch[1], exportMatch[2].trim());
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
const prefixMatch = bashLine.match(/^([A-Za-z_][A-Za-z0-9_]*)=(\S+)\s+/);
|
|
764
|
+
if (prefixMatch) {
|
|
765
|
+
trackedEnv.set(prefixMatch[1], prefixMatch[2].trim());
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
// Background job handling
|
|
769
|
+
const bgMatch = bashLine.match(/^(.*?)\s*&$/);
|
|
770
|
+
if (bgMatch) {
|
|
771
|
+
const actualCmd = bgMatch[1].trim();
|
|
772
|
+
if (actualCmd) {
|
|
773
|
+
jobCounter++;
|
|
774
|
+
const jobId = jobCounter;
|
|
775
|
+
const child = spawn(actualCmd, {
|
|
776
|
+
shell: true,
|
|
777
|
+
cwd: currentCwd,
|
|
778
|
+
env: env,
|
|
779
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
780
|
+
});
|
|
781
|
+
jobs.set(jobId, { pid: child.pid ?? 0, cmd: actualCmd, status: 'running' });
|
|
782
|
+
process.stdout.write(style.dim(`[${jobId}] ${actualCmd} (background, pid ${child.pid ?? '?'})`) + '\n');
|
|
783
|
+
child.stdout?.on('data', (chunk) => {
|
|
784
|
+
const text = chunk.toString();
|
|
785
|
+
const lines = text.split('\n').filter(Boolean);
|
|
786
|
+
for (const l of lines)
|
|
787
|
+
process.stdout.write(style.dim(`[${jobId}] ${l.trimRight()} |> `));
|
|
788
|
+
});
|
|
789
|
+
child.on('close', (code) => {
|
|
790
|
+
jobs.delete(jobId);
|
|
791
|
+
process.stdout.write(style.dim(`[${jobId}] finished (exit ${code ?? 0})`) + '\n');
|
|
792
|
+
});
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
513
796
|
await runBash(dispatch.cmd);
|
|
514
797
|
continue;
|
|
515
798
|
}
|
|
@@ -615,7 +898,7 @@ export async function runShell(io, style) {
|
|
|
615
898
|
childActive = false;
|
|
616
899
|
if (!done)
|
|
617
900
|
childExitCode = 1;
|
|
618
|
-
//
|
|
901
|
+
// Show non-zero exit code
|
|
619
902
|
if (childExitCode !== 0) {
|
|
620
903
|
process.stdout.write(style.dim(`· exit ${childExitCode}`) + '\n');
|
|
621
904
|
}
|
|
@@ -659,7 +942,7 @@ export async function runShell(io, style) {
|
|
|
659
942
|
};
|
|
660
943
|
process.on('SIGINT', abortChat);
|
|
661
944
|
childActive = true;
|
|
662
|
-
//
|
|
945
|
+
// Accumulate last 4 KB of stderr for failure diagnosis
|
|
663
946
|
let errBuffer = '';
|
|
664
947
|
let chatExitCode = 0;
|
|
665
948
|
let spawnError = null;
|
|
@@ -692,7 +975,7 @@ export async function runShell(io, style) {
|
|
|
692
975
|
childActive = false;
|
|
693
976
|
renderer.finalize();
|
|
694
977
|
totalCost += renderer.getTotalCost();
|
|
695
|
-
//
|
|
978
|
+
// Detect and report chat failures
|
|
696
979
|
const chatFailed = spawnError !== null || (chatExitCode !== 0 && !renderer.hasReceivedText());
|
|
697
980
|
if (chatFailed) {
|
|
698
981
|
let reason;
|
|
@@ -777,6 +1060,22 @@ export async function runShell(io, style) {
|
|
|
777
1060
|
// s or other: skip silently
|
|
778
1061
|
}
|
|
779
1062
|
}
|
|
1063
|
+
function printLetterHelp(style) {
|
|
1064
|
+
const lines = [
|
|
1065
|
+
style.accent('Agora Shell — letter shortcuts'),
|
|
1066
|
+
'',
|
|
1067
|
+
' /a again /b browse /c community /d doctor /e env',
|
|
1068
|
+
' /f fg /g search /h home /i init /j jobs',
|
|
1069
|
+
' /k search /l last /m marketplace /n news /o browse',
|
|
1070
|
+
' /p preferences /q quit /r reviews /s settings /t terminal',
|
|
1071
|
+
' /u use /v verbose /w watch /x export /y history',
|
|
1072
|
+
' /z doctor --fix',
|
|
1073
|
+
'',
|
|
1074
|
+
'Shortcuts are exact matches only (no arguments).',
|
|
1075
|
+
'Type /help for full command reference, /abc to show this again.'
|
|
1076
|
+
];
|
|
1077
|
+
process.stdout.write(lines.join('\n') + '\n\n');
|
|
1078
|
+
}
|
|
780
1079
|
function printHelp(style) {
|
|
781
1080
|
const lines = [
|
|
782
1081
|
style.accent('Agora Shell — help'),
|
|
@@ -796,6 +1095,10 @@ function printHelp(style) {
|
|
|
796
1095
|
' /last re-run most recent bash command',
|
|
797
1096
|
' /again re-send most recent chat message',
|
|
798
1097
|
' /? <cmd> dry-run an agora command (e.g. /? install mcp-github)',
|
|
1098
|
+
' /abc show letter-shortcut reference (/a /b /c ...)',
|
|
1099
|
+
' /jobs list background jobs',
|
|
1100
|
+
' /fg [N] bring job N (or last) to foreground',
|
|
1101
|
+
' /bg [N] resume job N (or last) in background',
|
|
799
1102
|
'',
|
|
800
1103
|
style.dim('Verbosity:'),
|
|
801
1104
|
' /verbose /medium /quiet',
|