@aragon-agent/cli 0.4.3

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 (156) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +558 -0
  3. package/dist/agent/coalesce.d.ts +14 -0
  4. package/dist/agent/coalesce.js +41 -0
  5. package/dist/agent/controller.d.ts +132 -0
  6. package/dist/agent/controller.js +332 -0
  7. package/dist/agent/headless.d.ts +22 -0
  8. package/dist/agent/headless.js +74 -0
  9. package/dist/agent/reducer.d.ts +181 -0
  10. package/dist/agent/reducer.js +420 -0
  11. package/dist/agent/system-prompt.d.ts +30 -0
  12. package/dist/agent/system-prompt.js +58 -0
  13. package/dist/agent/usage.d.ts +13 -0
  14. package/dist/agent/usage.js +41 -0
  15. package/dist/cli.d.ts +11 -0
  16. package/dist/cli.js +482 -0
  17. package/dist/commands/builtins.d.ts +16 -0
  18. package/dist/commands/builtins.js +223 -0
  19. package/dist/commands/registry.d.ts +48 -0
  20. package/dist/commands/registry.js +45 -0
  21. package/dist/commands/skills.d.ts +22 -0
  22. package/dist/commands/skills.js +625 -0
  23. package/dist/config/app-paths.d.ts +11 -0
  24. package/dist/config/app-paths.js +16 -0
  25. package/dist/config/env.d.ts +28 -0
  26. package/dist/config/env.js +92 -0
  27. package/dist/config/load.d.ts +48 -0
  28. package/dist/config/load.js +158 -0
  29. package/dist/config/migrate-legacy-state.d.ts +78 -0
  30. package/dist/config/migrate-legacy-state.js +181 -0
  31. package/dist/config/schema.d.ts +249 -0
  32. package/dist/config/schema.js +247 -0
  33. package/dist/config/store.d.ts +44 -0
  34. package/dist/config/store.js +126 -0
  35. package/dist/input/keymap.d.ts +20 -0
  36. package/dist/input/keymap.js +85 -0
  37. package/dist/session/persist.d.ts +24 -0
  38. package/dist/session/persist.js +51 -0
  39. package/dist/skills/archive.d.ts +41 -0
  40. package/dist/skills/archive.js +152 -0
  41. package/dist/skills/cli-commands.d.ts +52 -0
  42. package/dist/skills/cli-commands.js +502 -0
  43. package/dist/skills/fetch-source.d.ts +104 -0
  44. package/dist/skills/fetch-source.js +448 -0
  45. package/dist/skills/installer.d.ts +117 -0
  46. package/dist/skills/installer.js +414 -0
  47. package/dist/skills/lock.d.ts +75 -0
  48. package/dist/skills/lock.js +223 -0
  49. package/dist/skills/manifest.d.ts +47 -0
  50. package/dist/skills/manifest.js +95 -0
  51. package/dist/skills/node-host.d.ts +23 -0
  52. package/dist/skills/node-host.js +59 -0
  53. package/dist/skills/paths.d.ts +61 -0
  54. package/dist/skills/paths.js +107 -0
  55. package/dist/skills/service.d.ts +256 -0
  56. package/dist/skills/service.js +730 -0
  57. package/dist/skills/tools.d.ts +35 -0
  58. package/dist/skills/tools.js +164 -0
  59. package/dist/skills/updater.d.ts +101 -0
  60. package/dist/skills/updater.js +330 -0
  61. package/dist/skills/usage.d.ts +72 -0
  62. package/dist/skills/usage.js +216 -0
  63. package/dist/tools/bash-tool.d.ts +12 -0
  64. package/dist/tools/bash-tool.js +131 -0
  65. package/dist/tools/diff.d.ts +15 -0
  66. package/dist/tools/diff.js +50 -0
  67. package/dist/tools/fs-tools.d.ts +16 -0
  68. package/dist/tools/fs-tools.js +215 -0
  69. package/dist/tools/index.d.ts +93 -0
  70. package/dist/tools/index.js +157 -0
  71. package/dist/tools/search-tools.d.ts +11 -0
  72. package/dist/tools/search-tools.js +225 -0
  73. package/dist/ui/App.d.ts +24 -0
  74. package/dist/ui/App.js +522 -0
  75. package/dist/ui/AutocompletePopup.d.ts +21 -0
  76. package/dist/ui/AutocompletePopup.js +19 -0
  77. package/dist/ui/Composer.d.ts +40 -0
  78. package/dist/ui/Composer.js +61 -0
  79. package/dist/ui/Header.d.ts +30 -0
  80. package/dist/ui/Header.js +25 -0
  81. package/dist/ui/Logo.d.ts +50 -0
  82. package/dist/ui/Logo.js +85 -0
  83. package/dist/ui/Markdown.d.ts +23 -0
  84. package/dist/ui/Markdown.js +122 -0
  85. package/dist/ui/PromptInput.d.ts +81 -0
  86. package/dist/ui/PromptInput.js +383 -0
  87. package/dist/ui/SessionOpener.d.ts +30 -0
  88. package/dist/ui/SessionOpener.js +12 -0
  89. package/dist/ui/StatusBar.d.ts +39 -0
  90. package/dist/ui/StatusBar.js +43 -0
  91. package/dist/ui/ToastStack.d.ts +24 -0
  92. package/dist/ui/ToastStack.js +38 -0
  93. package/dist/ui/Transcript.d.ts +62 -0
  94. package/dist/ui/Transcript.js +128 -0
  95. package/dist/ui/capabilities.d.ts +25 -0
  96. package/dist/ui/capabilities.js +59 -0
  97. package/dist/ui/console-bridge.d.ts +19 -0
  98. package/dist/ui/console-bridge.js +52 -0
  99. package/dist/ui/density.d.ts +26 -0
  100. package/dist/ui/density.js +34 -0
  101. package/dist/ui/entries/AssistantEntry.d.ts +23 -0
  102. package/dist/ui/entries/AssistantEntry.js +13 -0
  103. package/dist/ui/entries/EntryFrame.d.ts +23 -0
  104. package/dist/ui/entries/EntryFrame.js +6 -0
  105. package/dist/ui/entries/ToolCard.d.ts +38 -0
  106. package/dist/ui/entries/ToolCard.js +72 -0
  107. package/dist/ui/entries/ToolPreview.d.ts +57 -0
  108. package/dist/ui/entries/ToolPreview.js +144 -0
  109. package/dist/ui/entries/UserEntry.d.ts +14 -0
  110. package/dist/ui/entries/UserEntry.js +5 -0
  111. package/dist/ui/exit-snapshot.d.ts +27 -0
  112. package/dist/ui/exit-snapshot.js +26 -0
  113. package/dist/ui/gauge.d.ts +24 -0
  114. package/dist/ui/gauge.js +36 -0
  115. package/dist/ui/glyphs.d.ts +87 -0
  116. package/dist/ui/glyphs.js +127 -0
  117. package/dist/ui/gradient.d.ts +11 -0
  118. package/dist/ui/gradient.js +50 -0
  119. package/dist/ui/layout/AppShell.d.ts +24 -0
  120. package/dist/ui/layout/AppShell.js +9 -0
  121. package/dist/ui/layout/Gutter.d.ts +67 -0
  122. package/dist/ui/layout/Gutter.js +42 -0
  123. package/dist/ui/layout/OverlayFrame.d.ts +62 -0
  124. package/dist/ui/layout/OverlayFrame.js +92 -0
  125. package/dist/ui/layout/ScrollViewport.d.ts +33 -0
  126. package/dist/ui/layout/ScrollViewport.js +71 -0
  127. package/dist/ui/layout/budget.d.ts +42 -0
  128. package/dist/ui/layout/budget.js +47 -0
  129. package/dist/ui/layout/frame.d.ts +58 -0
  130. package/dist/ui/layout/frame.js +75 -0
  131. package/dist/ui/layout/overlay-window.d.ts +43 -0
  132. package/dist/ui/layout/overlay-window.js +51 -0
  133. package/dist/ui/layout/scroll.d.ts +28 -0
  134. package/dist/ui/layout/scroll.js +76 -0
  135. package/dist/ui/layout/useTerminalSize.d.ts +13 -0
  136. package/dist/ui/layout/useTerminalSize.js +50 -0
  137. package/dist/ui/markdown-blocks.d.ts +62 -0
  138. package/dist/ui/markdown-blocks.js +120 -0
  139. package/dist/ui/overlays/ConfirmDialog.d.ts +30 -0
  140. package/dist/ui/overlays/ConfirmDialog.js +25 -0
  141. package/dist/ui/overlays/HelpOverlay.d.ts +17 -0
  142. package/dist/ui/overlays/HelpOverlay.js +60 -0
  143. package/dist/ui/overlays/ModelPicker.d.ts +29 -0
  144. package/dist/ui/overlays/ModelPicker.js +29 -0
  145. package/dist/ui/overlays/SettingsScreen.d.ts +37 -0
  146. package/dist/ui/overlays/SettingsScreen.js +106 -0
  147. package/dist/ui/palettes.d.ts +79 -0
  148. package/dist/ui/palettes.js +115 -0
  149. package/dist/ui/screen.d.ts +27 -0
  150. package/dist/ui/screen.js +57 -0
  151. package/dist/ui/theme.d.ts +78 -0
  152. package/dist/ui/theme.js +127 -0
  153. package/dist/ui/transcript-text.d.ts +27 -0
  154. package/dist/ui/transcript-text.js +110 -0
  155. package/package.json +73 -0
  156. package/skills/skill-creator/SKILL.md +96 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Built-in slash commands (spec §5.2):
3
+ * /help /model /settings /thinking /tools /clear /reset /cwd /save /resume
4
+ * /copy /exit (/quit).
5
+ */
6
+ import { spawn } from 'node:child_process';
7
+ import { existsSync, statSync } from 'node:fs';
8
+ import { isAbsolute, resolve } from 'node:path';
9
+ import process from 'node:process';
10
+ import { clampThinkingLevel, clampTheme, isThemeName, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
11
+ import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
12
+ const COMMANDS = [
13
+ {
14
+ name: 'help',
15
+ description: 'Show keybindings and commands',
16
+ run: (ctx) => ctx.setOverlay('help'),
17
+ },
18
+ {
19
+ name: 'model',
20
+ description: 'Open the model picker',
21
+ run: (ctx) => ctx.setOverlay('model'),
22
+ },
23
+ {
24
+ name: 'settings',
25
+ description: 'Open the settings screen',
26
+ run: (ctx) => ctx.setOverlay('settings'),
27
+ },
28
+ {
29
+ name: 'thinking',
30
+ description: 'Set the thinking level',
31
+ run: (ctx) => {
32
+ const level = ctx.args.trim();
33
+ if (!level) {
34
+ ctx.notify('info', `Thinking levels: ${THINKING_LEVELS.join(', ')}.`);
35
+ return;
36
+ }
37
+ const clamped = clampThinkingLevel(level, ctx.controller.getConfig().thinkingLevel);
38
+ ctx.controller.setThinkingLevel(clamped);
39
+ ctx.persistConfig({ thinkingLevel: clamped });
40
+ ctx.toast('info', `Thinking level set to "${clamped}".`);
41
+ },
42
+ },
43
+ {
44
+ name: 'theme',
45
+ description: 'Switch the color theme (auto|warm|cool|light)',
46
+ run: (ctx) => {
47
+ const arg = ctx.args.trim().toLowerCase();
48
+ const current = ctx.controller.getConfig().theme;
49
+ if (!arg) {
50
+ ctx.notify('info', `Theme: ${current}. Use /theme <auto|warm|cool|light>.`);
51
+ return;
52
+ }
53
+ // Validate BEFORE clamping. The old `arg !== clampTheme(arg)` test would
54
+ // now reject `/theme dark`, which is a legal compatibility alias for
55
+ // `cool` rather than an unknown name (§4.5).
56
+ if (!isThemeName(arg) && arg !== LEGACY_DARK_THEME) {
57
+ ctx.notify('warn', `Unknown theme "${arg}" - use ${THEME_NAMES.join(', ')}.`);
58
+ return;
59
+ }
60
+ const name = clampTheme(arg, current);
61
+ ctx.controller.setTheme(name);
62
+ ctx.persistConfig({ theme: name });
63
+ ctx.toast('success', `Theme set to ${name}.`);
64
+ },
65
+ },
66
+ {
67
+ name: 'expand',
68
+ description: 'Expand / collapse a recent tool card (default: last)',
69
+ run: (ctx) => {
70
+ const toolEntries = ctx.state.entries.filter((e) => e.kind === 'tool');
71
+ if (toolEntries.length === 0) {
72
+ ctx.toast('info', 'No tool output to expand.');
73
+ return;
74
+ }
75
+ const n = Number.parseInt(ctx.args.trim(), 10);
76
+ const fromLast = Number.isFinite(n) && n > 0 ? n : 1;
77
+ const idx = Math.max(0, toolEntries.length - fromLast);
78
+ const target = toolEntries[idx];
79
+ if (target)
80
+ ctx.dispatch({ type: 'toggleExpand', id: target.id });
81
+ },
82
+ },
83
+ {
84
+ name: 'tools',
85
+ description: 'List active tools',
86
+ run: (ctx) => {
87
+ const lines = ctx.controller
88
+ .listTools()
89
+ .map((t) => `- ${t.name}: ${t.description.split('\n')[0]}`)
90
+ .join('\n');
91
+ ctx.notify('info', `Active tools:\n${lines}`);
92
+ },
93
+ },
94
+ {
95
+ name: 'clear',
96
+ description: 'Clear the visible transcript',
97
+ run: (ctx) => ctx.dispatch({ type: 'clearTranscript' }),
98
+ },
99
+ {
100
+ name: 'reset',
101
+ description: 'Start a new conversation',
102
+ run: (ctx) => {
103
+ ctx.controller.clearMessages();
104
+ ctx.controller.clearAllQueues();
105
+ ctx.dispatch({ type: 'resetConversation' });
106
+ ctx.toast('info', 'Started a new conversation.');
107
+ },
108
+ },
109
+ {
110
+ name: 'cwd',
111
+ description: 'Show or change the tool working directory',
112
+ run: (ctx) => {
113
+ const arg = ctx.args.trim();
114
+ if (!arg) {
115
+ ctx.notify('info', `Working directory: ${ctx.controller.getCwd()}`);
116
+ return;
117
+ }
118
+ const target = isAbsolute(arg) ? arg : resolve(ctx.controller.getCwd(), arg);
119
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
120
+ ctx.notify('error', `Not a directory: ${arg}`);
121
+ return;
122
+ }
123
+ ctx.controller.setCwd(target);
124
+ ctx.notify('info', `Working directory set to ${target}`);
125
+ },
126
+ },
127
+ {
128
+ name: 'save',
129
+ description: 'Save the session to JSON',
130
+ run: (ctx) => {
131
+ try {
132
+ const path = resolveSessionPath(ctx.args, ctx.controller.getCwd());
133
+ const cfg = ctx.controller.getConfig();
134
+ saveSession(path, {
135
+ model: {
136
+ providerId: cfg.provider,
137
+ modelId: cfg.model,
138
+ ...(cfg.baseUrl ? { baseUrl: cfg.baseUrl } : {}),
139
+ },
140
+ messages: ctx.controller.getMessages(),
141
+ entries: ctx.state.entries,
142
+ });
143
+ ctx.notify('info', `Saved session to ${path}`);
144
+ }
145
+ catch (err) {
146
+ ctx.notify('error', `Save failed: ${err instanceof Error ? err.message : String(err)}`);
147
+ }
148
+ },
149
+ },
150
+ {
151
+ name: 'resume',
152
+ description: 'Load a saved session',
153
+ run: (ctx) => {
154
+ try {
155
+ const path = resolveSessionPath(ctx.args, ctx.controller.getCwd());
156
+ const session = loadSession(path);
157
+ ctx.controller.replaceMessages(session.messages);
158
+ ctx.dispatch({ type: 'restoreEntries', entries: session.entries });
159
+ if (session.model?.providerId && session.model?.modelId) {
160
+ ctx.controller.setModel(session.model.providerId, session.model.modelId, session.model.baseUrl);
161
+ }
162
+ ctx.notify('info', `Resumed session from ${path}`);
163
+ }
164
+ catch (err) {
165
+ ctx.notify('error', `Resume failed: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ },
168
+ },
169
+ {
170
+ name: 'copy',
171
+ description: 'Copy the last answer to the clipboard',
172
+ run: (ctx) => {
173
+ const last = [...ctx.state.entries].reverse().find((e) => e.kind === 'assistant');
174
+ if (!last || last.kind !== 'assistant' || last.text.trim().length === 0) {
175
+ ctx.notify('warn', 'No assistant message to copy.');
176
+ return;
177
+ }
178
+ const ok = copyToClipboard(last.text);
179
+ ctx.toast(ok ? 'success' : 'warn', ok ? 'Copied to clipboard.' : 'Clipboard not available.');
180
+ },
181
+ },
182
+ {
183
+ name: 'exit',
184
+ aliases: ['quit'],
185
+ description: 'Exit',
186
+ run: (ctx) => ctx.exit(),
187
+ },
188
+ ];
189
+ export function registerBuiltinCommands(registry) {
190
+ for (const command of COMMANDS)
191
+ registry.register(command);
192
+ }
193
+ /**
194
+ * Every built-in command name INCLUDING aliases (`quit`), for documentation and
195
+ * tests.
196
+ *
197
+ * Not for run-time conflict detection — `registerSkillCommands` probes
198
+ * `registry.get(name)` instead, because that is the only check that stays
199
+ * correct when commands are registered dynamically (§7.1 / P1-2).
200
+ */
201
+ export const BUILTIN_COMMAND_NAMES = COMMANDS.flatMap((c) => [
202
+ c.name,
203
+ ...(c.aliases ?? []),
204
+ ]).sort();
205
+ /** Best-effort clipboard copy via the platform's clipboard CLI. */
206
+ function copyToClipboard(text) {
207
+ const cmd = process.platform === 'win32'
208
+ ? { file: 'clip', args: [] }
209
+ : process.platform === 'darwin'
210
+ ? { file: 'pbcopy', args: [] }
211
+ : { file: 'xclip', args: ['-selection', 'clipboard'] };
212
+ try {
213
+ const child = spawn(cmd.file, cmd.args, { stdio: ['pipe', 'ignore', 'ignore'] });
214
+ child.on('error', () => {
215
+ /* swallow — clipboard is best-effort */
216
+ });
217
+ child.stdin?.end(text);
218
+ return true;
219
+ }
220
+ catch {
221
+ return false;
222
+ }
223
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Slash-command registry + dispatcher (spec §5.2). A leading `/` in the prompt
3
+ * input is routed here instead of to the agent; `//` is the literal-`/` escape.
4
+ */
5
+ import type { AgentController } from '../agent/controller.js';
6
+ import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
7
+ import type { PersistedConfig } from '../config/schema.js';
8
+ export interface CommandContext {
9
+ /** Raw argument string after the command word (may be empty). */
10
+ args: string;
11
+ controller: AgentController;
12
+ state: ViewState;
13
+ dispatch: (action: ViewAction) => void;
14
+ setOverlay: (overlay: Overlay) => void;
15
+ /** Durable transcript message (errors, listings, saved paths). */
16
+ notify: (level: NoticeLevel, text: string) => void;
17
+ /** Ephemeral, auto-dismissing acknowledgement (spec §3.9). */
18
+ toast: (level: ToastLevel, text: string) => void;
19
+ persistConfig: (patch: Partial<PersistedConfig>) => void;
20
+ exit: () => void;
21
+ /**
22
+ * Submit `text` as if the user had typed it (steers when running, prompts when
23
+ * idle). Dynamic skill commands need this: they turn `/pdf-forms a.pdf` into a
24
+ * real user message. It is a separate entry point from `handleSubmit` on
25
+ * purpose — routing back through that would re-enter slash-command parsing.
26
+ */
27
+ submit: (text: string) => void;
28
+ /** Rebuild the system prompt and the dynamic command list after a skill change. */
29
+ refreshSkills: () => void;
30
+ }
31
+ export interface SlashCommand {
32
+ name: string;
33
+ aliases?: string[];
34
+ description: string;
35
+ run: (ctx: CommandContext) => void | Promise<void>;
36
+ }
37
+ export declare class CommandRegistry {
38
+ private readonly byName;
39
+ register(command: SlashCommand): void;
40
+ get(name: string): SlashCommand | undefined;
41
+ all(): SlashCommand[];
42
+ }
43
+ /**
44
+ * Try to run a slash command from raw input. Returns `true` if the input was a
45
+ * (known or unknown) slash command and was consumed; `false` if it is a normal
46
+ * message that should go to the agent.
47
+ */
48
+ export declare function runSlashInput(registry: CommandRegistry, input: string, makeCtx: (args: string) => CommandContext): Promise<boolean>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Slash-command registry + dispatcher (spec §5.2). A leading `/` in the prompt
3
+ * input is routed here instead of to the agent; `//` is the literal-`/` escape.
4
+ */
5
+ export class CommandRegistry {
6
+ byName = new Map();
7
+ register(command) {
8
+ this.byName.set(command.name, command);
9
+ for (const alias of command.aliases ?? []) {
10
+ this.byName.set(alias, command);
11
+ }
12
+ }
13
+ get(name) {
14
+ return this.byName.get(name);
15
+ }
16
+ all() {
17
+ // De-duplicate aliases pointing at the same command.
18
+ return Array.from(new Set(this.byName.values()));
19
+ }
20
+ }
21
+ /**
22
+ * Try to run a slash command from raw input. Returns `true` if the input was a
23
+ * (known or unknown) slash command and was consumed; `false` if it is a normal
24
+ * message that should go to the agent.
25
+ */
26
+ export async function runSlashInput(registry, input, makeCtx) {
27
+ const trimmed = input.trimStart();
28
+ if (!trimmed.startsWith('/'))
29
+ return false;
30
+ // `//...` escapes a literal leading slash — not a command.
31
+ if (trimmed.startsWith('//'))
32
+ return false;
33
+ const withoutSlash = trimmed.slice(1);
34
+ const spaceIdx = withoutSlash.search(/\s/);
35
+ const name = spaceIdx === -1 ? withoutSlash : withoutSlash.slice(0, spaceIdx);
36
+ const args = spaceIdx === -1 ? '' : withoutSlash.slice(spaceIdx + 1).trim();
37
+ const command = registry.get(name);
38
+ const ctx = makeCtx(args);
39
+ if (!command) {
40
+ ctx.notify('warn', `Unknown command "/${name}" - try /help.`);
41
+ return true;
42
+ }
43
+ await command.run(ctx);
44
+ return true;
45
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `/skills …` management commands and the dynamic `/<skill-name>` registrations
3
+ * (spec §7.1 / §7.2).
4
+ */
5
+ import type { SkillService } from '../skills/service.js';
6
+ import { CommandRegistry, type SlashCommand } from './registry.js';
7
+ export declare function makeSkillsCommand(service: SkillService, version: string): SlashCommand;
8
+ /**
9
+ * Register one dynamic command per usable skill.
10
+ *
11
+ * CONFLICT RULE (D6 / P1-2): the namespaced `/skill:<name>` form is ALWAYS
12
+ * registered — documentation and scripts need a stable handle — while the bare
13
+ * `/<name>` is only taken when `registry.get(name)` is undefined.
14
+ *
15
+ * The probe MUST be `get()`, never `all().map(c => c.name)`. `all()` de-dupes
16
+ * by command OBJECT, so it returns primary names only and silently omits every
17
+ * alias. `quit` is an alias of `exit`, and `register()` overwrites
18
+ * unconditionally — so a third-party skill named `quit` would take over `/quit`.
19
+ * D6 calls hijacking the user's muscle memory a security boundary, not a
20
+ * papercut, and `get()` is the only probe that sees the alias table.
21
+ */
22
+ export declare function registerSkillCommands(registry: CommandRegistry, service: SkillService): void;