@opencoven/coven-code 0.0.1 → 0.0.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 (47) hide show
  1. package/README.md +2 -1
  2. package/docs/CLI.md +65 -1
  3. package/docs/DEMO.md +453 -0
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/README.md +1 -0
  6. package/package.json +7 -6
  7. package/src/agent/{local.mjs → fixture.mjs} +1 -1
  8. package/src/cli/execute.mjs +6 -4
  9. package/src/cli/interactive-core.mjs +5 -279
  10. package/src/cli/interactive-io.mjs +101 -0
  11. package/src/cli/interactive-slash.mjs +184 -0
  12. package/src/cli/repl.mjs +1 -2
  13. package/src/cli/slash-commands.mjs +20 -2
  14. package/src/cli/tui-actions.mjs +72 -0
  15. package/src/cli/tui-blessed.mjs +198 -0
  16. package/src/cli/tui-keys.mjs +80 -0
  17. package/src/cli/tui-lane.mjs +73 -0
  18. package/src/cli/tui-render.mjs +169 -0
  19. package/src/cli/tui-submit.mjs +82 -0
  20. package/src/cli/tui.mjs +30 -613
  21. package/src/commands/permissions-eval.mjs +122 -0
  22. package/src/commands/permissions-rules.mjs +53 -0
  23. package/src/commands/permissions-text.mjs +112 -0
  24. package/src/commands/permissions.mjs +15 -281
  25. package/src/commands/usage.mjs +1 -1
  26. package/src/constants.mjs +7 -1
  27. package/src/mcp/local.mjs +55 -0
  28. package/src/mcp/parsers.mjs +46 -0
  29. package/src/mcp/probe.mjs +12 -351
  30. package/src/mcp/remote-oauth.mjs +55 -0
  31. package/src/mcp/remote-session.mjs +54 -0
  32. package/src/mcp/remote-sse.mjs +82 -0
  33. package/src/mcp/remote.mjs +74 -0
  34. package/src/plugins/api.mjs +187 -0
  35. package/src/plugins/configuration.mjs +124 -0
  36. package/src/plugins/discover.mjs +8 -804
  37. package/src/plugins/helpers.mjs +187 -0
  38. package/src/plugins/subsystems.mjs +198 -0
  39. package/src/plugins/validators.mjs +142 -0
  40. package/src/sdk-execute.mjs +82 -0
  41. package/src/sdk-settings.mjs +88 -0
  42. package/src/sdk.mjs +13 -164
  43. package/src/tools/builtin/oracle.mjs +2 -2
  44. package/src/tools/builtin/runtime-content.mjs +31 -0
  45. package/src/tools/builtin/runtime-decisions.mjs +115 -0
  46. package/src/tools/builtin/runtime.mjs +18 -148
  47. package/src/tools/builtin/task.mjs +2 -2
@@ -0,0 +1,187 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ import { createPluginConfigurationApi } from './configuration.mjs';
3
+ import {
4
+ filePathFromURI,
5
+ filesModifiedByToolCall,
6
+ isPluginUINotAvailableError,
7
+ runPluginShell,
8
+ toolCallsInMessages,
9
+ } from './helpers.mjs';
10
+ import {
11
+ createPluginAI,
12
+ createPluginExperimentalApi,
13
+ createPluginLogger,
14
+ createPluginSystem,
15
+ createPluginThreadContext,
16
+ pluginEnvConfirm,
17
+ pluginEnvInput,
18
+ pluginEnvSelection,
19
+ } from './subsystems.mjs';
20
+ import { createSubscription } from './configuration.mjs';
21
+ import {
22
+ removeFirst,
23
+ validateCommandAvailability,
24
+ validatePluginCommand,
25
+ validatePluginConfirmOptions,
26
+ validatePluginInputOptions,
27
+ validatePluginNotifyMessage,
28
+ validatePluginSelectOptions,
29
+ validatePluginToolDefinition,
30
+ validatePluginToolName,
31
+ } from './validators.mjs';
32
+
33
+ export function createPluginApi({ cwd, runtime }) {
34
+ return {
35
+ registerTool(tool) {
36
+ const summary = runtime.currentPlugin;
37
+ validatePluginToolName(tool?.name);
38
+ validatePluginToolDefinition(tool);
39
+ const registeredTool = { ...tool };
40
+ if (tool?.name) summary?.tools.push(tool.name);
41
+ runtime.tools.push(registeredTool);
42
+ return createSubscription(() => {
43
+ runtime.tools = runtime.tools.filter((entry) => entry !== registeredTool);
44
+ if (tool?.name && summary) removeFirst(summary.tools, tool.name);
45
+ });
46
+ },
47
+ registerCommand(name, metadata = {}, handler = async () => undefined) {
48
+ const summary = runtime.currentPlugin;
49
+ validatePluginCommand(name, metadata);
50
+ summary?.commands.push(name);
51
+ const commandMetadata = {
52
+ ...metadata,
53
+ category: metadata.category ?? summary?.name,
54
+ };
55
+ const command = {
56
+ name,
57
+ metadata: commandMetadata,
58
+ handler,
59
+ availability: validateCommandAvailability(name, commandMetadata.availability ?? { type: 'enabled' }),
60
+ };
61
+ runtime.commands.push(command);
62
+ return {
63
+ setAvailability(availability) {
64
+ command.availability = validateCommandAvailability(name, availability);
65
+ },
66
+ ...createSubscription(() => {
67
+ runtime.commands = runtime.commands.filter((entry) => entry !== command);
68
+ if (summary) removeFirst(summary.commands, name);
69
+ }),
70
+ };
71
+ },
72
+ on(eventName, handler) {
73
+ const summary = runtime.currentPlugin;
74
+ summary?.events.push(eventName);
75
+ if (!runtime.handlers[eventName]) runtime.handlers[eventName] = [];
76
+ runtime.handlers[eventName].push(handler);
77
+ return createSubscription(() => {
78
+ runtime.handlers[eventName] = (runtime.handlers[eventName] ?? []).filter((entry) => entry !== handler);
79
+ if (summary) removeFirst(summary.events, eventName);
80
+ });
81
+ },
82
+ configuration: createPluginConfigurationApi(cwd, runtime),
83
+ ai: createPluginAI(),
84
+ helpers: {
85
+ shellCommandFromToolCall(event = {}) {
86
+ if (event.tool !== 'Bash' && event.tool !== 'shell_command') return null;
87
+ const command = event.input?.command ?? event.input?.cmd;
88
+ if (typeof command !== 'string' || !command) return null;
89
+ const dir = typeof event.input?.dir === 'string' && event.input.dir ? event.input.dir : undefined;
90
+ return dir ? { command, dir } : { command };
91
+ },
92
+ toolCallsInMessages(messages = []) {
93
+ return toolCallsInMessages(messages);
94
+ },
95
+ filesModifiedByToolCall(event = {}) {
96
+ const files = filesModifiedByToolCall(event);
97
+ return files ? files.map((filePath) => pathToFileURL(filePath)) : null;
98
+ },
99
+ filePathFromURI(uri) {
100
+ return filePathFromURI(uri);
101
+ },
102
+ isPluginUINotAvailableError(error) {
103
+ return isPluginUINotAvailableError(error);
104
+ },
105
+ },
106
+ logger: createPluginLogger(),
107
+ system: createPluginSystem((target) => {
108
+ runtime.notifications.push(String(target));
109
+ }),
110
+ ui: {
111
+ async notify(message) {
112
+ runtime.notifications.push(validatePluginNotifyMessage(message));
113
+ },
114
+ confirm: async (options) => pluginEnvConfirm(options),
115
+ input: async (options) => pluginEnvInput(options),
116
+ select: async (options) => pluginEnvSelection(options),
117
+ },
118
+ $: (strings, ...values) => runPluginShell(cwd, strings, values),
119
+ experimental: createPluginExperimentalApi(runtime),
120
+ };
121
+ }
122
+
123
+ export function createPluginContext(event = {}) {
124
+ return {
125
+ logger: createPluginLogger(),
126
+ $: (strings, ...values) => runPluginShell(process.cwd(), strings, values),
127
+ ui: {
128
+ async notify(message) {
129
+ validatePluginNotifyMessage(message);
130
+ },
131
+ confirm: async (options) => {
132
+ validatePluginConfirmOptions(options);
133
+ return false;
134
+ },
135
+ input: async (options) => {
136
+ validatePluginInputOptions(options);
137
+ return undefined;
138
+ },
139
+ select: async (options) => {
140
+ validatePluginSelectOptions(options);
141
+ return undefined;
142
+ },
143
+ },
144
+ ai: createPluginAI(),
145
+ system: createPluginSystem(),
146
+ thread: createPluginThreadContext(event.thread?.id),
147
+ };
148
+ }
149
+
150
+ export function createPluginCommandContext(cwd = process.cwd()) {
151
+ const notifications = [];
152
+ const context = {
153
+ ui: {
154
+ async notify(message) {
155
+ notifications.push(validatePluginNotifyMessage(message));
156
+ },
157
+ confirm: async (options) => pluginEnvConfirm(options),
158
+ input: async (options) => pluginEnvInput(options),
159
+ select: async (options) => pluginEnvSelection(options),
160
+ },
161
+ system: createPluginSystem((target) => {
162
+ notifications.push(String(target));
163
+ }),
164
+ ai: createPluginAI(),
165
+ $: (strings, ...values) => runPluginShell(cwd, strings, values),
166
+ thread: createPluginThreadContext(),
167
+ };
168
+ Object.defineProperty(context, 'notifications', {
169
+ value: notifications,
170
+ enumerable: false,
171
+ });
172
+ return context;
173
+ }
174
+
175
+ export function createPluginToolContext() {
176
+ return {
177
+ ui: {
178
+ notify: async (message) => {
179
+ validatePluginNotifyMessage(message);
180
+ },
181
+ confirm: async (options) => pluginEnvConfirm(options),
182
+ input: async (options) => pluginEnvInput(options),
183
+ select: async (options) => pluginEnvSelection(options),
184
+ },
185
+ logger: createPluginLogger(),
186
+ };
187
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ SETTINGS_PREFIX,
3
+ readEffectiveSettings,
4
+ readSettings,
5
+ readSettingsFile,
6
+ writeSettings,
7
+ writeSettingsFile,
8
+ } from '../settings/load.mjs';
9
+ import { findProjectRoot, workspaceSettingsFile } from '../settings/paths.mjs';
10
+
11
+ export function createSubscription(dispose) {
12
+ return {
13
+ unsubscribe: dispose,
14
+ };
15
+ }
16
+
17
+ export function createPluginConfigurationApi(cwd, runtime) {
18
+ const observableSymbol = Symbol.observable ?? Symbol.for('observable');
19
+ const api = {
20
+ async get() {
21
+ return pluginConfiguration(readEffectiveSettings());
22
+ },
23
+ async update(patch = {}, scope = 'workspace') {
24
+ const normalizedPatch = normalizePluginConfigurationPatch(patch);
25
+ const target = normalizePluginConfigurationTarget(scope);
26
+ if (target === 'workspace') {
27
+ const filePath = workspaceSettingsFile(findProjectRoot(cwd));
28
+ await writeSettingsFile(filePath, { ...readSettingsFile(filePath), ...normalizedPatch });
29
+ } else {
30
+ await writeSettings({ ...readSettings(), ...normalizedPatch });
31
+ }
32
+ await notifyConfigurationSubscribers(runtime);
33
+ },
34
+ async delete(key, scope = 'workspace') {
35
+ const normalizedKey = normalizePluginConfigurationKey(key);
36
+ const target = normalizePluginConfigurationTarget(scope);
37
+ if (target === 'workspace') {
38
+ const filePath = workspaceSettingsFile(findProjectRoot(cwd));
39
+ const settings = readSettingsFile(filePath);
40
+ delete settings[normalizedKey];
41
+ await writeSettingsFile(filePath, settings);
42
+ } else {
43
+ const settings = readSettings();
44
+ delete settings[normalizedKey];
45
+ await writeSettings(settings);
46
+ }
47
+ await notifyConfigurationSubscribers(runtime);
48
+ },
49
+ subscribe(handler) {
50
+ validateObservableSubscriber(handler);
51
+ runtime.configurationSubscribers.push(handler);
52
+ return createSubscription(() => {
53
+ runtime.configurationSubscribers = runtime.configurationSubscribers.filter((entry) => entry !== handler);
54
+ });
55
+ },
56
+ pipe(op) {
57
+ return op(api);
58
+ },
59
+ [observableSymbol]() {
60
+ return api;
61
+ },
62
+ };
63
+ return api;
64
+ }
65
+
66
+ function pluginConfiguration(settings = {}) {
67
+ const config = { ...settings };
68
+ for (const [key, value] of Object.entries(settings)) {
69
+ if (key.startsWith(SETTINGS_PREFIX) && !Object.hasOwn(config, key.slice(SETTINGS_PREFIX.length))) {
70
+ config[key.slice(SETTINGS_PREFIX.length)] = value;
71
+ }
72
+ }
73
+ return config;
74
+ }
75
+
76
+ function normalizePluginConfigurationPatch(patch = {}) {
77
+ if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
78
+ throw new Error('plugin configuration update patch must be an object');
79
+ }
80
+ return Object.fromEntries(Object.entries(patch).map(([key, value]) => [
81
+ normalizePluginConfigurationKey(key),
82
+ value,
83
+ ]));
84
+ }
85
+
86
+ function normalizePluginConfigurationKey(key) {
87
+ if (typeof key !== 'string') {
88
+ throw new Error('plugin configuration key must be a string');
89
+ }
90
+ const text = String(key);
91
+ if (text.startsWith(SETTINGS_PREFIX)) return text;
92
+ return `${SETTINGS_PREFIX}${text}`;
93
+ }
94
+
95
+ function normalizePluginConfigurationTarget(target = 'workspace') {
96
+ if (target !== 'workspace' && target !== 'global') {
97
+ throw new Error(`plugin configuration target must be workspace or global: ${String(target)}`);
98
+ }
99
+ return target;
100
+ }
101
+
102
+ async function notifyConfigurationSubscribers(runtime) {
103
+ if (runtime.configurationSubscribers.length === 0) return;
104
+ const config = pluginConfiguration(readEffectiveSettings());
105
+ for (const subscriber of runtime.configurationSubscribers) {
106
+ await notifyObservableSubscriber(subscriber, config);
107
+ }
108
+ }
109
+
110
+ export async function notifyObservableSubscriber(subscriber, value) {
111
+ if (typeof subscriber === 'function') {
112
+ await subscriber(value);
113
+ return;
114
+ }
115
+ if (typeof subscriber?.next === 'function') {
116
+ await subscriber.next(value);
117
+ }
118
+ }
119
+
120
+ export function validateObservableSubscriber(subscriber) {
121
+ if (typeof subscriber === 'function') return;
122
+ if (subscriber && typeof subscriber === 'object' && !Array.isArray(subscriber)) return;
123
+ throw new Error('plugin observable subscriber must be a function or observer object');
124
+ }