@gajae-code/coding-agent 0.7.2 → 0.7.4
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/CHANGELOG.md +86 -0
- package/bin/gjc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/extensibility/gjc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/gjc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +8 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +20 -1
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +72 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +181 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli.ts +27 -6
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +55 -7
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/gjc/skills/team/SKILL.md +5 -3
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/gjc-plugins/compiler.ts +351 -0
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +109 -0
- package/src/extensibility/gjc-plugins/installer.ts +434 -0
- package/src/extensibility/gjc-plugins/loader.ts +3 -1
- package/src/extensibility/gjc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/gjc-plugins/observability.ts +84 -0
- package/src/extensibility/gjc-plugins/paths.ts +1 -1
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/gjc-plugins/registry.ts +180 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/gjc-plugins/schema.ts +250 -20
- package/src/extensibility/gjc-plugins/session-validation.ts +147 -0
- package/src/extensibility/gjc-plugins/types.ts +199 -3
- package/src/extensibility/gjc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/gjc-runtime/launch-tmux.ts +61 -7
- package/src/gjc-runtime/psmux-detect.ts +239 -0
- package/src/gjc-runtime/team-runtime.ts +56 -23
- package/src/gjc-runtime/tmux-common.ts +30 -3
- package/src/gjc-runtime/tmux-sessions.ts +51 -1
- package/src/gjc-runtime/ultragoal-guard.ts +25 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +75 -15
- package/src/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +12 -8
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +56 -11
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +53 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +228 -0
- package/src/notifications/lifecycle-control-runtime.ts +400 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +166 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +678 -7
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +3 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct MCP server registration CLI helpers.
|
|
3
|
+
*
|
|
4
|
+
* This surface only writes explicit user-provided server definitions to GJC's
|
|
5
|
+
* own MCP config. It never imports or inherits live configs from other agents.
|
|
6
|
+
*/
|
|
7
|
+
import { getMCPConfigPath, getProjectDir } from "@gajae-code/utils";
|
|
8
|
+
import { getMCPServer, readMCPConfigFile, removeMCPServer, upsertMCPServer } from "../runtime-mcp/config-writer";
|
|
9
|
+
import type { MCPConfigFile, MCPServerConfig } from "../runtime-mcp/types";
|
|
10
|
+
|
|
11
|
+
export type MCPAction = "add" | "list" | "remove";
|
|
12
|
+
|
|
13
|
+
export interface MCPCommandArgs {
|
|
14
|
+
action: MCPAction;
|
|
15
|
+
name?: string;
|
|
16
|
+
commandArgs?: string[];
|
|
17
|
+
flags: {
|
|
18
|
+
project?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
type?: "stdio" | "http" | "sse";
|
|
22
|
+
command?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
arg?: string[];
|
|
25
|
+
env?: string[];
|
|
26
|
+
header?: string[];
|
|
27
|
+
cwd?: string;
|
|
28
|
+
timeout?: number;
|
|
29
|
+
};
|
|
30
|
+
cwd?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class MCPArgsError extends Error {}
|
|
34
|
+
|
|
35
|
+
interface ScopedPath {
|
|
36
|
+
scope: "user" | "project";
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface RedactedServerEntry {
|
|
41
|
+
name: string;
|
|
42
|
+
config: MCPServerConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const REDACTED = "<redacted>";
|
|
46
|
+
const SENSITIVE_KEY_PATTERN =
|
|
47
|
+
/(?:token|secret|key|credential|password|passwd|pwd|authorization|auth|bearer|cookie|session)/i;
|
|
48
|
+
|
|
49
|
+
function resolvePath(args: MCPCommandArgs): ScopedPath {
|
|
50
|
+
const scope = args.flags.project ? "project" : "user";
|
|
51
|
+
return { scope, path: getMCPConfigPath(scope, args.cwd ?? getProjectDir()) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parsePairs(values: string[] | undefined, label: string): Record<string, string> | undefined {
|
|
55
|
+
if (!values || values.length === 0) return undefined;
|
|
56
|
+
const parsed: Record<string, string> = {};
|
|
57
|
+
for (const value of values) {
|
|
58
|
+
const index = value.indexOf("=");
|
|
59
|
+
if (index <= 0) {
|
|
60
|
+
throw new MCPArgsError(`Invalid ${label}. Use KEY=VALUE.`);
|
|
61
|
+
}
|
|
62
|
+
const key = value.slice(0, index).trim();
|
|
63
|
+
if (!key) {
|
|
64
|
+
throw new MCPArgsError(`Invalid ${label}. Key cannot be empty.`);
|
|
65
|
+
}
|
|
66
|
+
parsed[key] = value.slice(index + 1);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildServerConfig(args: MCPCommandArgs): MCPServerConfig {
|
|
72
|
+
const type = args.flags.type ?? (args.flags.url ? "http" : "stdio");
|
|
73
|
+
const timeout = args.flags.timeout;
|
|
74
|
+
const shared = timeout === undefined ? {} : { timeout };
|
|
75
|
+
|
|
76
|
+
if (type === "stdio") {
|
|
77
|
+
const command = args.flags.command ?? args.commandArgs?.[0];
|
|
78
|
+
if (!command) {
|
|
79
|
+
throw new MCPArgsError("`gjc mcp add` requires --command <cmd> or a positional command for stdio servers.");
|
|
80
|
+
}
|
|
81
|
+
const config: MCPServerConfig = {
|
|
82
|
+
...shared,
|
|
83
|
+
type: "stdio",
|
|
84
|
+
command,
|
|
85
|
+
};
|
|
86
|
+
const positionalArgs = args.flags.command ? [] : (args.commandArgs ?? []).slice(1);
|
|
87
|
+
const serverArgs = [...positionalArgs, ...(args.flags.arg ?? [])];
|
|
88
|
+
if (serverArgs.length > 0) config.args = serverArgs;
|
|
89
|
+
const env = parsePairs(args.flags.env, "env");
|
|
90
|
+
if (env) config.env = env;
|
|
91
|
+
if (args.flags.cwd) config.cwd = args.flags.cwd;
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const url = args.flags.url ?? args.commandArgs?.[0];
|
|
96
|
+
if (!url) {
|
|
97
|
+
throw new MCPArgsError(`\`gjc mcp add --type ${type}\` requires --url <url> or a positional URL.`);
|
|
98
|
+
}
|
|
99
|
+
const headers = parsePairs(args.flags.header, "header");
|
|
100
|
+
if (type === "http") {
|
|
101
|
+
const config: MCPServerConfig = {
|
|
102
|
+
...shared,
|
|
103
|
+
type,
|
|
104
|
+
url,
|
|
105
|
+
};
|
|
106
|
+
if (headers) config.headers = headers;
|
|
107
|
+
return config;
|
|
108
|
+
}
|
|
109
|
+
const config: MCPServerConfig = {
|
|
110
|
+
...shared,
|
|
111
|
+
type,
|
|
112
|
+
url,
|
|
113
|
+
};
|
|
114
|
+
if (headers) config.headers = headers;
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function redactRecord(
|
|
119
|
+
record: Record<string, string> | undefined,
|
|
120
|
+
redactAllValues: boolean,
|
|
121
|
+
): Record<string, string> | undefined {
|
|
122
|
+
if (!record) return undefined;
|
|
123
|
+
return Object.fromEntries(
|
|
124
|
+
Object.entries(record).map(([key, value]) => [
|
|
125
|
+
key,
|
|
126
|
+
redactAllValues || SENSITIVE_KEY_PATTERN.test(key) ? REDACTED : value,
|
|
127
|
+
]),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig {
|
|
132
|
+
const redacted = { ...config } as MCPServerConfig;
|
|
133
|
+
if ("env" in redacted) {
|
|
134
|
+
const env = redactRecord(redacted.env, true);
|
|
135
|
+
if (env) redacted.env = env;
|
|
136
|
+
}
|
|
137
|
+
if ("headers" in redacted) {
|
|
138
|
+
const headers = redactRecord(redacted.headers, true);
|
|
139
|
+
if (headers) redacted.headers = headers;
|
|
140
|
+
}
|
|
141
|
+
if (redacted.auth) {
|
|
142
|
+
redacted.auth = {
|
|
143
|
+
type: redacted.auth.type,
|
|
144
|
+
credentialId: redacted.auth.credentialId ? REDACTED : undefined,
|
|
145
|
+
tokenUrl: redacted.auth.tokenUrl,
|
|
146
|
+
clientId: redacted.auth.clientId ? REDACTED : undefined,
|
|
147
|
+
clientSecret: redacted.auth.clientSecret ? REDACTED : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (redacted.oauth) {
|
|
151
|
+
redacted.oauth = {
|
|
152
|
+
clientId: redacted.oauth.clientId ? REDACTED : undefined,
|
|
153
|
+
clientSecret: redacted.oauth.clientSecret ? REDACTED : undefined,
|
|
154
|
+
redirectUri: redacted.oauth.redirectUri,
|
|
155
|
+
callbackPort: redacted.oauth.callbackPort,
|
|
156
|
+
callbackPath: redacted.oauth.callbackPath,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return redacted;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function collectEntries(config: MCPConfigFile): RedactedServerEntry[] {
|
|
163
|
+
return Object.entries(config.mcpServers ?? {})
|
|
164
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
165
|
+
.map(([name, serverConfig]) => ({ name, config: redactMCPServerConfig(serverConfig) }));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeJson(value: unknown): void {
|
|
169
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderServerLine(entry: RedactedServerEntry): string {
|
|
173
|
+
const config = entry.config;
|
|
174
|
+
if (config.type === "http" || config.type === "sse") {
|
|
175
|
+
return `${entry.name}\t${config.type}\t${config.url}`;
|
|
176
|
+
}
|
|
177
|
+
const args = config.args && config.args.length > 0 ? ` ${config.args.join(" ")}` : "";
|
|
178
|
+
return `${entry.name}\tstdio\t${config.command}${args}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderDetails(entry: RedactedServerEntry): string {
|
|
182
|
+
return `${renderServerLine(entry)}\n${JSON.stringify(entry.config, null, 2)}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runAdd(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
186
|
+
if (!args.name) throw new MCPArgsError("`gjc mcp add` requires a server name.");
|
|
187
|
+
const config = buildServerConfig(args);
|
|
188
|
+
const result = await upsertMCPServer(scoped.path, args.name, config, { force: args.flags.force });
|
|
189
|
+
const redacted = redactMCPServerConfig(config);
|
|
190
|
+
if (args.flags.json) {
|
|
191
|
+
writeJson({
|
|
192
|
+
action: "add",
|
|
193
|
+
status: result.status,
|
|
194
|
+
name: args.name,
|
|
195
|
+
scope: scoped.scope,
|
|
196
|
+
path: scoped.path,
|
|
197
|
+
config: redacted,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (result.status === "skipped") {
|
|
202
|
+
process.stdout.write(
|
|
203
|
+
`MCP server "${args.name}" already exists in ${scoped.scope} config. Pass --force to overwrite.\n`,
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write(`MCP server "${args.name}" ${result.status} in ${scoped.scope} config: ${scoped.path}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runList(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
211
|
+
const config = await readMCPConfigFile(scoped.path);
|
|
212
|
+
const entries = collectEntries(config);
|
|
213
|
+
if (args.flags.json) {
|
|
214
|
+
writeJson({ action: "list", scope: scoped.scope, path: scoped.path, servers: entries });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (entries.length === 0) {
|
|
218
|
+
process.stdout.write(`No MCP servers registered in ${scoped.scope} config: ${scoped.path}\n`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
process.stdout.write(`MCP servers in ${scoped.scope} config: ${scoped.path}\n`);
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
process.stdout.write(`${renderDetails(entry)}\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function runRemove(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
228
|
+
if (!args.name) throw new MCPArgsError("`gjc mcp remove` requires a server name.");
|
|
229
|
+
const existing = await getMCPServer(scoped.path, args.name);
|
|
230
|
+
if (!existing) {
|
|
231
|
+
throw new MCPArgsError(`MCP server "${args.name}" not found in ${scoped.scope} config.`);
|
|
232
|
+
}
|
|
233
|
+
await removeMCPServer(scoped.path, args.name);
|
|
234
|
+
const entry = { name: args.name, config: redactMCPServerConfig(existing) };
|
|
235
|
+
if (args.flags.json) {
|
|
236
|
+
writeJson({
|
|
237
|
+
action: "remove",
|
|
238
|
+
status: "removed",
|
|
239
|
+
name: args.name,
|
|
240
|
+
scope: scoped.scope,
|
|
241
|
+
path: scoped.path,
|
|
242
|
+
removed: entry,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`Removed MCP server "${args.name}" from ${scoped.scope} config: ${scoped.path}\n`);
|
|
247
|
+
process.stdout.write(`${renderDetails(entry)}\n`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function runMCPCommand(args: MCPCommandArgs): Promise<void> {
|
|
251
|
+
const scoped = resolvePath(args);
|
|
252
|
+
try {
|
|
253
|
+
switch (args.action) {
|
|
254
|
+
case "add":
|
|
255
|
+
await runAdd(args, scoped);
|
|
256
|
+
return;
|
|
257
|
+
case "list":
|
|
258
|
+
await runList(args, scoped);
|
|
259
|
+
return;
|
|
260
|
+
case "remove":
|
|
261
|
+
await runRemove(args, scoped);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof MCPArgsError) {
|
|
266
|
+
process.stderr.write(`${error.message}\n`);
|
|
267
|
+
process.exitCode = 2;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { APP_NAME, getProjectDir } from "@gajae-code/utils";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { resolveOrDefaultProjectRegistryPath } from "../discovery/helpers";
|
|
10
|
+
import { installGjcPluginBundle, isGjcPluginBundleSource, readRegistry } from "../extensibility/gjc-plugins";
|
|
10
11
|
import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
|
|
11
12
|
import {
|
|
12
13
|
getInstalledPluginsRegistryPath,
|
|
@@ -48,6 +49,8 @@ export interface PluginCommandArgs {
|
|
|
48
49
|
disable?: string;
|
|
49
50
|
set?: string;
|
|
50
51
|
scope?: "user" | "project";
|
|
52
|
+
user?: boolean;
|
|
53
|
+
project?: boolean;
|
|
51
54
|
};
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -109,6 +112,10 @@ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
|
|
|
109
112
|
result.flags.dryRun = true;
|
|
110
113
|
} else if (arg === "-l" || arg === "--local") {
|
|
111
114
|
result.flags.local = true;
|
|
115
|
+
} else if (arg === "--user") {
|
|
116
|
+
result.flags.user = true;
|
|
117
|
+
} else if (arg === "--project") {
|
|
118
|
+
result.flags.project = true;
|
|
112
119
|
} else if (arg === "--enable" && i + 1 < args.length) {
|
|
113
120
|
result.flags.enable = args[++i];
|
|
114
121
|
} else if (arg === "--disable" && i + 1 < args.length) {
|
|
@@ -345,7 +352,14 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
|
|
|
345
352
|
async function handleInstall(
|
|
346
353
|
manager: PluginManager,
|
|
347
354
|
packages: string[],
|
|
348
|
-
flags: {
|
|
355
|
+
flags: {
|
|
356
|
+
json?: boolean;
|
|
357
|
+
force?: boolean;
|
|
358
|
+
dryRun?: boolean;
|
|
359
|
+
scope?: "user" | "project";
|
|
360
|
+
user?: boolean;
|
|
361
|
+
project?: boolean;
|
|
362
|
+
},
|
|
349
363
|
): Promise<void> {
|
|
350
364
|
if (packages.length === 0) {
|
|
351
365
|
console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
|
|
@@ -360,6 +374,32 @@ async function handleInstall(
|
|
|
360
374
|
const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
|
|
361
375
|
|
|
362
376
|
for (const spec of packages) {
|
|
377
|
+
// GJC plugin bundle classifier: a source containing gajae-plugin.json (or a
|
|
378
|
+
// git/tarball source) routes to the bundle installer BEFORE marketplace/npm.
|
|
379
|
+
if (await isGjcPluginBundleSource(spec)) {
|
|
380
|
+
if (flags.user === flags.project) {
|
|
381
|
+
console.error(
|
|
382
|
+
chalk.red(`GJC plugin bundle install requires exactly one of --user or --project for "${spec}".`),
|
|
383
|
+
);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
const scope: "user" | "project" = flags.user ? "user" : "project";
|
|
387
|
+
try {
|
|
388
|
+
const res = await installGjcPluginBundle(spec, { scope, cwd: process.cwd(), force: flags.force });
|
|
389
|
+
if (flags.json) {
|
|
390
|
+
console.log(JSON.stringify({ name: res.entry.name, status: res.status, scope }, null, 2));
|
|
391
|
+
} else {
|
|
392
|
+
console.log(
|
|
393
|
+
chalk.green(`${theme.status.success} ${res.status} GJC plugin ${res.entry.name} (${scope})`),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
console.error(chalk.red(`${theme.status.error} Failed to install GJC plugin ${spec}: ${err}`));
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
363
403
|
const target = classifyInstallTarget(spec, knownMarketplaces);
|
|
364
404
|
|
|
365
405
|
if (target.type === "marketplace") {
|
|
@@ -462,13 +502,16 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
|
|
462
502
|
const npmPlugins = await manager.list();
|
|
463
503
|
const mktMgr = await makeMarketplaceManager();
|
|
464
504
|
const mktPlugins = await mktMgr.listInstalledPlugins();
|
|
505
|
+
const cwd = getProjectDir();
|
|
506
|
+
const [gjcUser, gjcProject] = await Promise.all([readRegistry("user", cwd), readRegistry("project", cwd)]);
|
|
507
|
+
const gjcBundles = [...gjcUser.plugins, ...gjcProject.plugins];
|
|
465
508
|
|
|
466
509
|
if (flags.json) {
|
|
467
|
-
console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins }, null, 2));
|
|
510
|
+
console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins, gjc: gjcBundles }, null, 2));
|
|
468
511
|
return;
|
|
469
512
|
}
|
|
470
513
|
|
|
471
|
-
if (npmPlugins.length === 0 && mktPlugins.length === 0) {
|
|
514
|
+
if (npmPlugins.length === 0 && mktPlugins.length === 0 && gjcBundles.length === 0) {
|
|
472
515
|
console.log(chalk.dim("No plugins installed"));
|
|
473
516
|
console.log(chalk.dim(`\nInstall plugins with: ${APP_NAME} plugin install <package>`));
|
|
474
517
|
return;
|
|
@@ -510,6 +553,26 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
|
|
510
553
|
console.log(` ${plugin.id} (${version})${scopeLabel}${shadowLabel}`);
|
|
511
554
|
}
|
|
512
555
|
}
|
|
556
|
+
|
|
557
|
+
if (gjcBundles.length > 0) {
|
|
558
|
+
if (npmPlugins.length > 0 || mktPlugins.length > 0) console.log();
|
|
559
|
+
console.log(chalk.bold("GJC Plugin Bundles:\n"));
|
|
560
|
+
for (const plugin of gjcBundles) {
|
|
561
|
+
const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
|
|
562
|
+
const scopeLabel = chalk.dim(` (${plugin.scope})`);
|
|
563
|
+
const disabledCount = plugin.disabledSurfaceIds.length;
|
|
564
|
+
const quarantineCount = plugin.quarantine?.length ?? 0;
|
|
565
|
+
const detail = [
|
|
566
|
+
disabledCount > 0 ? `${disabledCount} disabled` : null,
|
|
567
|
+
quarantineCount > 0 ? `${quarantineCount} quarantined` : null,
|
|
568
|
+
]
|
|
569
|
+
.filter((v): v is string => Boolean(v))
|
|
570
|
+
.join(", ");
|
|
571
|
+
console.log(
|
|
572
|
+
`${status} ${plugin.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
513
576
|
}
|
|
514
577
|
|
|
515
578
|
async function handleLink(manager: PluginManager, paths: string[], flags: { json?: boolean }): Promise<void> {
|
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ process.title = APP_NAME;
|
|
|
22
22
|
const rootHelpFlags = ["--help", "-h", "help"];
|
|
23
23
|
const versionFlags = ["--version", "-v"];
|
|
24
24
|
|
|
25
|
-
const commands: CommandEntry[] = [
|
|
25
|
+
export const commands: CommandEntry[] = [
|
|
26
26
|
{ name: "codex-native-hook", load: () => import("./commands/codex-native-hook").then(m => m.default) },
|
|
27
27
|
{ name: "state", load: () => import("./commands/state").then(m => m.default) },
|
|
28
28
|
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
@@ -39,6 +39,7 @@ const commands: CommandEntry[] = [
|
|
|
39
39
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
40
40
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
41
41
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
42
|
+
{ name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
|
|
42
43
|
{
|
|
43
44
|
name: "contribute-pr",
|
|
44
45
|
aliases: ["contribution-prep"],
|
|
@@ -48,6 +49,7 @@ const commands: CommandEntry[] = [
|
|
|
48
49
|
{ name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
|
|
49
50
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
50
51
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
52
|
+
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
51
53
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
52
54
|
];
|
|
53
55
|
|
|
@@ -62,7 +64,7 @@ async function showHelp(config: CliConfig): Promise<void> {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
async function installRuntimeGlobals(): Promise<void> {
|
|
65
|
-
const
|
|
67
|
+
const { installH2Fetch } = await import("@gajae-code/ai/utils/h2-fetch");
|
|
66
68
|
// Activate HTTP/2 for all `fetch()` calls (provider streams, OAuth, model
|
|
67
69
|
// discovery, web tools). Bun's HTTP/2 client is gated on a startup flag we
|
|
68
70
|
// can't toggle from JS, so we patch globalThis.fetch to pass
|
|
@@ -73,7 +75,24 @@ async function installRuntimeGlobals(): Promise<void> {
|
|
|
73
75
|
// Strip macOS malloc-stack-logging env vars before any subprocess is spawned.
|
|
74
76
|
// Otherwise every child bun process (subagents, plugin installs, ptree spawns,
|
|
75
77
|
// etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
|
|
76
|
-
|
|
78
|
+
delete process.env.MallocStackLogging;
|
|
79
|
+
delete process.env.MallocStackLoggingNoCompact;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
|
|
83
|
+
for (const arg of argv) {
|
|
84
|
+
if (isSubcommand(arg)) return false;
|
|
85
|
+
if (flags.includes(arg)) return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasRootHelpFlag(argv: string[]): boolean {
|
|
91
|
+
return hasRootFastFlag(argv, rootHelpFlags);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasRootVersionFlag(argv: string[]): boolean {
|
|
95
|
+
return hasRootFastFlag(argv, versionFlags);
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
class RootHelpCommand extends Command {
|
|
@@ -193,7 +212,7 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
193
212
|
await runSmokeTest();
|
|
194
213
|
return;
|
|
195
214
|
}
|
|
196
|
-
if (
|
|
215
|
+
if (hasRootHelpFlag(argv)) {
|
|
197
216
|
const { renderRootHelp } = await import("@gajae-code/utils/cli");
|
|
198
217
|
const { getExtraHelpText } = await import("./cli/fast-help");
|
|
199
218
|
renderRootHelp({ bin: APP_NAME, version: VERSION, commands: new Map([["launch", RootHelpCommand]]) });
|
|
@@ -203,7 +222,7 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
203
222
|
}
|
|
204
223
|
return;
|
|
205
224
|
}
|
|
206
|
-
if (
|
|
225
|
+
if (hasRootVersionFlag(argv)) {
|
|
207
226
|
process.stdout.write(`${APP_NAME}/${VERSION}\n`);
|
|
208
227
|
return;
|
|
209
228
|
}
|
|
@@ -220,4 +239,6 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
220
239
|
return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
|
|
221
240
|
}
|
|
222
241
|
|
|
223
|
-
|
|
242
|
+
if (import.meta.main) {
|
|
243
|
+
await runCli(process.argv.slice(2));
|
|
244
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct MCP server registration for standalone GJC.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
5
|
+
import { type MCPAction, type MCPCommandArgs, runMCPCommand } from "../cli/mcp-cli";
|
|
6
|
+
|
|
7
|
+
const ACTIONS: MCPAction[] = ["add", "list", "remove"];
|
|
8
|
+
|
|
9
|
+
export default class MCP extends Command {
|
|
10
|
+
static description = "Register standalone MCP servers explicitly in GJC config";
|
|
11
|
+
static delegateHelp = true;
|
|
12
|
+
|
|
13
|
+
static examples = [
|
|
14
|
+
"gjc mcp add context7 npx -y @upstash/context7-mcp",
|
|
15
|
+
"gjc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN",
|
|
16
|
+
"gjc mcp list --json",
|
|
17
|
+
"gjc mcp remove context7",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
static args = {
|
|
21
|
+
action: Args.string({ description: "MCP action", required: false, options: ACTIONS }),
|
|
22
|
+
name: Args.string({ description: "Server name", required: false }),
|
|
23
|
+
commandArgs: Args.string({
|
|
24
|
+
description: "Command/URL and trailing args for add",
|
|
25
|
+
required: false,
|
|
26
|
+
multiple: true,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static flags = {
|
|
31
|
+
project: Flags.boolean({ description: "Write/read project scope (./.gjc/mcp.json) instead of user scope" }),
|
|
32
|
+
force: Flags.boolean({ description: "Overwrite an existing server during add", default: false }),
|
|
33
|
+
json: Flags.boolean({
|
|
34
|
+
char: "j",
|
|
35
|
+
description: "Emit machine-readable JSON with sensitive values redacted",
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
type: Flags.string({ description: "Server transport type", options: ["stdio", "http", "sse"] }),
|
|
39
|
+
command: Flags.string({ description: "Stdio server command for add" }),
|
|
40
|
+
url: Flags.string({ description: "HTTP/SSE server URL for add" }),
|
|
41
|
+
arg: Flags.string({ description: "Argument passed to a stdio server (repeatable)", multiple: true }),
|
|
42
|
+
env: Flags.string({
|
|
43
|
+
description: "Environment variable for stdio server as KEY=VALUE (repeatable)",
|
|
44
|
+
multiple: true,
|
|
45
|
+
}),
|
|
46
|
+
header: Flags.string({
|
|
47
|
+
description: "HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)",
|
|
48
|
+
multiple: true,
|
|
49
|
+
}),
|
|
50
|
+
cwd: Flags.string({ description: "Working directory for stdio server" }),
|
|
51
|
+
timeout: Flags.integer({ description: "Connection timeout in milliseconds" }),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async run(): Promise<void> {
|
|
55
|
+
if (this.argv.includes("--help") || this.argv.includes("-h")) {
|
|
56
|
+
this.printHelp();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { args, flags } = await this.parse(MCP);
|
|
61
|
+
const action = (args.action ?? "list") as MCPAction;
|
|
62
|
+
const cmd: MCPCommandArgs = {
|
|
63
|
+
action,
|
|
64
|
+
name: args.name,
|
|
65
|
+
commandArgs: args.commandArgs,
|
|
66
|
+
flags: {
|
|
67
|
+
project: flags.project,
|
|
68
|
+
force: flags.force,
|
|
69
|
+
json: flags.json,
|
|
70
|
+
type: flags.type as MCPCommandArgs["flags"]["type"],
|
|
71
|
+
command: flags.command,
|
|
72
|
+
url: flags.url,
|
|
73
|
+
arg: flags.arg,
|
|
74
|
+
env: flags.env,
|
|
75
|
+
header: flags.header,
|
|
76
|
+
cwd: flags.cwd,
|
|
77
|
+
timeout: flags.timeout,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
await runMCPCommand(cmd);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private printHelp(): void {
|
|
84
|
+
process.stdout.write(`Register standalone MCP servers explicitly in GJC config
|
|
85
|
+
|
|
86
|
+
USAGE
|
|
87
|
+
$ gjc mcp [add|list|remove] [NAME] [COMMAND_OR_URL] [ARGS...] [FLAGS]
|
|
88
|
+
|
|
89
|
+
COMMANDS
|
|
90
|
+
add Add an explicit user-provided MCP server definition
|
|
91
|
+
list List registered servers with env/header/auth values redacted
|
|
92
|
+
remove Remove a registered server and print the removed definition redacted
|
|
93
|
+
|
|
94
|
+
FLAGS
|
|
95
|
+
--project Use project scope (./.gjc/mcp.json) instead of user scope
|
|
96
|
+
--force Overwrite an existing server during add
|
|
97
|
+
-j, --json Emit machine-readable JSON with sensitive values redacted
|
|
98
|
+
--type=<value> stdio | http | sse (default: stdio, or http when --url is set)
|
|
99
|
+
--command=<value> Stdio server command for add
|
|
100
|
+
--url=<value> HTTP/SSE server URL for add
|
|
101
|
+
--arg=<value> Stdio server argument (repeatable)
|
|
102
|
+
--env=<value> Stdio env var as KEY=VALUE (repeatable; redacted in output)
|
|
103
|
+
--header=<value> HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)
|
|
104
|
+
--cwd=<value> Working directory for stdio server
|
|
105
|
+
--timeout=<int> Connection timeout in milliseconds
|
|
106
|
+
|
|
107
|
+
EXAMPLES
|
|
108
|
+
$ gjc mcp add context7 npx -y @upstash/context7-mcp
|
|
109
|
+
$ gjc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN
|
|
110
|
+
$ gjc mcp list --json
|
|
111
|
+
$ gjc mcp remove context7
|
|
112
|
+
|
|
113
|
+
SECURITY
|
|
114
|
+
This command writes only the server definition supplied on this invocation. It does not import or inherit Claude Code, Codex, OpenCode, or other live MCP configs. Public output redacts env, header, auth, and OAuth credential values.
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/commands/plugin.ts
CHANGED
|
@@ -49,6 +49,8 @@ export default class Plugin extends Command {
|
|
|
49
49
|
description: 'Install scope: "user" (default) or "project"',
|
|
50
50
|
options: ["user", "project"],
|
|
51
51
|
}),
|
|
52
|
+
user: Flags.boolean({ description: "Install GJC plugin bundle into the user root" }),
|
|
53
|
+
project: Flags.boolean({ description: "Install GJC plugin bundle into the project root" }),
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
async run(): Promise<void> {
|
|
@@ -69,6 +71,8 @@ export default class Plugin extends Command {
|
|
|
69
71
|
disable: flags.disable,
|
|
70
72
|
set: flags.set,
|
|
71
73
|
scope: flags.scope as "user" | "project" | undefined,
|
|
74
|
+
user: flags.user,
|
|
75
|
+
project: flags.project,
|
|
72
76
|
},
|
|
73
77
|
};
|
|
74
78
|
|
package/src/commands/session.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
|
2
2
|
import {
|
|
3
3
|
attachGjcTmuxSession,
|
|
4
4
|
createGjcTmuxSession,
|
|
5
|
+
forceCloseGjcTmuxSession,
|
|
5
6
|
listGjcTmuxSessions,
|
|
6
7
|
removeGjcTmuxSession,
|
|
7
8
|
statusGjcTmuxSession,
|
|
@@ -60,6 +61,12 @@ export default class Session extends Command {
|
|
|
60
61
|
|
|
61
62
|
static flags = {
|
|
62
63
|
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
|
|
64
|
+
"session-id": Flags.string({
|
|
65
|
+
description: "Expected @gjc-session-id tag for force-close (defense-in-depth match)",
|
|
66
|
+
}),
|
|
67
|
+
"state-file": Flags.string({
|
|
68
|
+
description: "Expected @gjc-session-state-file tag for force-close (defense-in-depth match)",
|
|
69
|
+
}),
|
|
63
70
|
};
|
|
64
71
|
|
|
65
72
|
static examples = [
|
|
@@ -68,6 +75,7 @@ export default class Session extends Command {
|
|
|
68
75
|
"gjc session status <session>",
|
|
69
76
|
"gjc session attach <session>",
|
|
70
77
|
"gjc session remove <session>",
|
|
78
|
+
"gjc session force-close <session> --session-id <id>",
|
|
71
79
|
];
|
|
72
80
|
|
|
73
81
|
async run(): Promise<void> {
|
|
@@ -136,6 +144,16 @@ export default class Session extends Command {
|
|
|
136
144
|
return;
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
if (action === "force-close" || action === "force-remove") {
|
|
148
|
+
const closed = forceCloseGjcTmuxSession(sessionName, process.env, flags["session-id"], flags["state-file"]);
|
|
149
|
+
if (json) {
|
|
150
|
+
writeJson({ ok: true, session: sessionJson(closed) });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
writeText([`force-closed: ${closed.name}`]);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
if (action === "attach") {
|
|
140
158
|
attachGjcTmuxSession(sessionName);
|
|
141
159
|
return;
|
|
@@ -117,8 +117,8 @@ export const KEYBINDINGS = {
|
|
|
117
117
|
description: "Open external editor",
|
|
118
118
|
},
|
|
119
119
|
"app.message.followUp": {
|
|
120
|
-
defaultKeys:
|
|
121
|
-
description: "Send follow-up message",
|
|
120
|
+
defaultKeys: [],
|
|
121
|
+
description: "Send follow-up message (no default; Ctrl+Enter inserts a newline)",
|
|
122
122
|
},
|
|
123
123
|
"app.message.queue": {
|
|
124
124
|
defaultKeys: "alt+enter",
|