botmux 2.60.1 → 2.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +4 -4
- package/README.md +5 -5
- package/dist/bot-registry.d.ts +24 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +30 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +28 -31
- package/dist/cli.js.map +1 -1
- package/dist/core/command-discovery.d.ts +24 -0
- package/dist/core/command-discovery.d.ts.map +1 -0
- package/dist/core/command-discovery.js +201 -0
- package/dist/core/command-discovery.js.map +1 -0
- package/dist/core/command-handler.d.ts +9 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +59 -10
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +2 -1
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +2 -1
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +6 -4
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +3 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +5 -4
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/bot-onboarding.js +10 -13
- package/dist/dashboard/bot-onboarding.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +6 -1
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +12 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +12 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +17 -0
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +84 -0
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +7 -5
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +2 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +15 -9
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/lark-hosts.d.ts +44 -0
- package/dist/im/lark/lark-hosts.d.ts.map +1 -0
- package/dist/im/lark/lark-hosts.js +49 -0
- package/dist/im/lark/lark-hosts.js.map +1 -0
- package/dist/setup/verify-permissions.d.ts +2 -1
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +5 -8
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/lark-upload.d.ts +2 -1
- package/dist/utils/lark-upload.d.ts.map +1 -1
- package/dist/utils/lark-upload.js +11 -7
- package/dist/utils/lark-upload.js.map +1 -1
- package/dist/utils/user-token.d.ts +12 -4
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +64 -25
- package/dist/utils/user-token.js.map +1 -1
- package/dist/worker.js +4 -1
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type DiscoveredSource = 'project-command' | 'user-command' | 'project-skill' | 'user-skill' | 'plugin-command' | 'plugin-skill';
|
|
2
|
+
export interface DiscoveredCommand {
|
|
3
|
+
/** Invocation form, e.g. '/deploy' or '/myplugin:foo'. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** First-line description pulled from frontmatter, when present. */
|
|
6
|
+
description?: string;
|
|
7
|
+
source: DiscoveredSource;
|
|
8
|
+
/** Human-friendly origin (relative-ish path or plugin id) for display. */
|
|
9
|
+
origin: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Discover all filesystem-visible slash commands for a session's working dir.
|
|
13
|
+
* Personal (`~/.claude`) + project (`<workingDir>/.claude`) + plugins, deduped
|
|
14
|
+
* by (name, source) and sorted by name. Safe to call on any CLI: non-Claude
|
|
15
|
+
* layouts simply yield nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function discoverSlashCommands(workingDir: string): DiscoveredCommand[];
|
|
18
|
+
/**
|
|
19
|
+
* Cheaply surface MCP *server* names from `<workingDir>/.mcp.json` (the standard
|
|
20
|
+
* project-level MCP config). The actual `/mcp__<server>__<prompt>` commands need
|
|
21
|
+
* a live handshake to enumerate and are out of scope here — this is just a hint.
|
|
22
|
+
*/
|
|
23
|
+
export declare function listMcpServerNames(workingDir: string): string[];
|
|
24
|
+
//# sourceMappingURL=command-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-discovery.d.ts","sourceRoot":"","sources":["../../src/core/command-discovery.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,cAAc,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AA+HD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA0B7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAc/D"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash-command discovery — enumerate the user-installed / project-local slash
|
|
3
|
+
* commands, skills and plugins that the underlying CLI (Claude Code family)
|
|
4
|
+
* would surface, purely by scanning the filesystem. Powers part ③ of the
|
|
5
|
+
* `/list-slash-command` daemon command.
|
|
6
|
+
*
|
|
7
|
+
* Scope (v1): the Claude `.claude/` layout —
|
|
8
|
+
* • custom commands: <root>/.claude/commands/**\/*.md → /name (subdir → ns:name)
|
|
9
|
+
* • skills: <root>/.claude/skills/*\/SKILL.md → /name
|
|
10
|
+
* • plugins: <claudeHome>/plugins/cache/<mp>/<plugin>/[<ver>/]{commands,skills}
|
|
11
|
+
* where <root> ∈ { workingDir (project), claudeHome (personal) } and
|
|
12
|
+
* <claudeHome> = $CLAUDE_CONFIG_DIR || ~/.claude.
|
|
13
|
+
*
|
|
14
|
+
* Intentionally dependency-free (no YAML / glob libs): a tiny frontmatter reader
|
|
15
|
+
* and a bounded recursive walker keep this importable from the leaf-ish command
|
|
16
|
+
* handler without pulling in the daemon graph.
|
|
17
|
+
*
|
|
18
|
+
* NOT covered: MCP-provided `/mcp__<server>__<prompt>` commands — enumerating
|
|
19
|
+
* those needs a live MCP handshake. {@link listMcpServerNames} cheaply surfaces
|
|
20
|
+
* the server *names* from `.mcp.json` so the caller can at least hint at them.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { join, basename, relative, sep } from 'node:path';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
/** Resolve the Claude config/data root the CLI reads commands & skills from.
|
|
26
|
+
* Honors CLAUDE_CONFIG_DIR (set by claude-family forks); defaults to ~/.claude. */
|
|
27
|
+
function claudeHome() {
|
|
28
|
+
const env = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
29
|
+
return env && env.length > 0 ? env : join(homedir(), '.claude');
|
|
30
|
+
}
|
|
31
|
+
/** Minimal frontmatter reader: pull `name:` / `description:` from a leading
|
|
32
|
+
* `---` … `---` block. No YAML dep — handles plain and quoted scalar values. */
|
|
33
|
+
function readFrontmatter(file) {
|
|
34
|
+
let text;
|
|
35
|
+
try {
|
|
36
|
+
text = readFileSync(file, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
if (!text.startsWith('---'))
|
|
42
|
+
return {};
|
|
43
|
+
const end = text.indexOf('\n---', 3);
|
|
44
|
+
if (end === -1)
|
|
45
|
+
return {};
|
|
46
|
+
const block = text.slice(3, end);
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const line of block.split(/\r?\n/)) {
|
|
49
|
+
const m = /^\s*(name|description)\s*:\s*(.+?)\s*$/.exec(line);
|
|
50
|
+
if (!m)
|
|
51
|
+
continue;
|
|
52
|
+
let v = m[2].trim();
|
|
53
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
54
|
+
v = v.slice(1, -1);
|
|
55
|
+
}
|
|
56
|
+
if (m[1] === 'name')
|
|
57
|
+
out.name = v;
|
|
58
|
+
else
|
|
59
|
+
out.description = v;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** Recursively collect `*.md` files under `dir` (bounded depth, fail-soft). */
|
|
64
|
+
function walkMd(dir, depth = 4) {
|
|
65
|
+
if (depth < 0 || !existsSync(dir))
|
|
66
|
+
return [];
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const e of entries) {
|
|
76
|
+
const p = join(dir, e.name);
|
|
77
|
+
if (e.isDirectory())
|
|
78
|
+
out.push(...walkMd(p, depth - 1));
|
|
79
|
+
else if (e.isFile() && e.name.endsWith('.md'))
|
|
80
|
+
out.push(p);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/** Immediate subdirectories of `dir` (fail-soft). */
|
|
85
|
+
function listDirs(dir) {
|
|
86
|
+
if (!existsSync(dir))
|
|
87
|
+
return [];
|
|
88
|
+
try {
|
|
89
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
90
|
+
.filter((e) => e.isDirectory())
|
|
91
|
+
.map((e) => join(dir, e.name));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** `<commandsDir>/**\/*.md` → commands. Filename (minus .md) is the command;
|
|
98
|
+
* nested subdirs become `ns:sub:name` namespaces (Claude convention). */
|
|
99
|
+
function scanCommandsDir(commandsDir, source, originPrefix) {
|
|
100
|
+
return walkMd(commandsDir).map((file) => {
|
|
101
|
+
const rel = relative(commandsDir, file).replace(/\.md$/, '');
|
|
102
|
+
const name = '/' + rel.split(sep).join(':');
|
|
103
|
+
const fm = readFrontmatter(file);
|
|
104
|
+
return { name, description: fm.description, source, origin: `${originPrefix}/${rel}.md` };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** `<skillsDir>/<name>/SKILL.md` → /name (optionally `ns:name` for plugins). */
|
|
108
|
+
function scanSkillsDir(skillsDir, source, originPrefix, namePrefix = '') {
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const d of listDirs(skillsDir)) {
|
|
111
|
+
const skillMd = join(d, 'SKILL.md');
|
|
112
|
+
if (!existsSync(skillMd))
|
|
113
|
+
continue;
|
|
114
|
+
const base = basename(d);
|
|
115
|
+
const fm = readFrontmatter(skillMd);
|
|
116
|
+
out.push({
|
|
117
|
+
name: '/' + (namePrefix ? `${namePrefix}:${base}` : base),
|
|
118
|
+
description: fm.description,
|
|
119
|
+
source,
|
|
120
|
+
origin: `${originPrefix}/${base}/SKILL.md`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/** Walk `<claudeRoot>/plugins/cache/<marketplace>/<plugin>/[<ver>/]{commands,skills}`.
|
|
126
|
+
* commands/skills may sit directly under the plugin or under a version subdir —
|
|
127
|
+
* we try both and let the caller's dedupe collapse any overlap. */
|
|
128
|
+
function scanPlugins(claudeRoot) {
|
|
129
|
+
const cacheRoot = join(claudeRoot, 'plugins', 'cache');
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const mp of listDirs(cacheRoot)) {
|
|
132
|
+
for (const plugin of listDirs(mp)) {
|
|
133
|
+
const pluginId = basename(plugin);
|
|
134
|
+
const originPrefix = `plugins/${basename(mp)}/${pluginId}`;
|
|
135
|
+
const roots = [plugin, ...listDirs(plugin)];
|
|
136
|
+
for (const root of roots) {
|
|
137
|
+
for (const c of scanCommandsDir(join(root, 'commands'), 'plugin-command', originPrefix)) {
|
|
138
|
+
// Re-namespace `/foo` → `/pluginId:foo`.
|
|
139
|
+
out.push({ ...c, name: c.name.replace(/^\//, `/${pluginId}:`) });
|
|
140
|
+
}
|
|
141
|
+
out.push(...scanSkillsDir(join(root, 'skills'), 'plugin-skill', originPrefix, pluginId));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Discover all filesystem-visible slash commands for a session's working dir.
|
|
149
|
+
* Personal (`~/.claude`) + project (`<workingDir>/.claude`) + plugins, deduped
|
|
150
|
+
* by (name, source) and sorted by name. Safe to call on any CLI: non-Claude
|
|
151
|
+
* layouts simply yield nothing.
|
|
152
|
+
*/
|
|
153
|
+
export function discoverSlashCommands(workingDir) {
|
|
154
|
+
const home = claudeHome();
|
|
155
|
+
const found = [];
|
|
156
|
+
// Personal (global) layer.
|
|
157
|
+
found.push(...scanCommandsDir(join(home, 'commands'), 'user-command', '~/.claude/commands'));
|
|
158
|
+
found.push(...scanSkillsDir(join(home, 'skills'), 'user-skill', '~/.claude/skills'));
|
|
159
|
+
found.push(...scanPlugins(home));
|
|
160
|
+
// Project layer.
|
|
161
|
+
if (workingDir) {
|
|
162
|
+
const proj = join(workingDir, '.claude');
|
|
163
|
+
found.push(...scanCommandsDir(join(proj, 'commands'), 'project-command', '.claude/commands'));
|
|
164
|
+
found.push(...scanSkillsDir(join(proj, 'skills'), 'project-skill', '.claude/skills'));
|
|
165
|
+
}
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
const deduped = [];
|
|
168
|
+
for (const c of found) {
|
|
169
|
+
const key = c.name; // dedupe by name across sources (fixes duplicate cmds)
|
|
170
|
+
if (seen.has(key))
|
|
171
|
+
continue;
|
|
172
|
+
seen.add(key);
|
|
173
|
+
deduped.push(c);
|
|
174
|
+
}
|
|
175
|
+
deduped.sort((a, b) => a.name.localeCompare(b.name));
|
|
176
|
+
return deduped;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Cheaply surface MCP *server* names from `<workingDir>/.mcp.json` (the standard
|
|
180
|
+
* project-level MCP config). The actual `/mcp__<server>__<prompt>` commands need
|
|
181
|
+
* a live handshake to enumerate and are out of scope here — this is just a hint.
|
|
182
|
+
*/
|
|
183
|
+
export function listMcpServerNames(workingDir) {
|
|
184
|
+
if (!workingDir)
|
|
185
|
+
return [];
|
|
186
|
+
const file = join(workingDir, '.mcp.json');
|
|
187
|
+
if (!existsSync(file))
|
|
188
|
+
return [];
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(readFileSync(file, 'utf-8'));
|
|
191
|
+
const servers = parsed?.mcpServers;
|
|
192
|
+
if (servers && typeof servers === 'object' && !Array.isArray(servers)) {
|
|
193
|
+
return Object.keys(servers);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
/* malformed .mcp.json — ignore */
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=command-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-discovery.js","sourceRoot":"","sources":["../../src/core/command-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,15 @@ export declare const SESSIONLESS_DAEMON_COMMANDS: Set<string>;
|
|
|
22
22
|
* own slash-command parser sees them.
|
|
23
23
|
*/
|
|
24
24
|
export declare const PASSTHROUGH_COMMANDS: Set<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Effective passthrough set for a bot: the fixed {@link PASSTHROUGH_COMMANDS}
|
|
27
|
+
* plus the bot's `customPassthroughCommands` (bots.json). Entries that would
|
|
28
|
+
* shadow a botmux daemon command are dropped — daemon commands must keep their
|
|
29
|
+
* daemon semantics, and passthrough is checked BEFORE DAEMON_COMMANDS in the
|
|
30
|
+
* router, so an un-filtered custom `/status` would hijack the daemon's own.
|
|
31
|
+
* Unknown / no bot → falls back to the builtin set unchanged.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolvePassthroughCommands(larkAppId?: string): Set<string>;
|
|
25
34
|
export interface SlashCommandInvocation {
|
|
26
35
|
cmd: string;
|
|
27
36
|
content: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-handler.d.ts","sourceRoot":"","sources":["../../src/core/command-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command-handler.d.ts","sourceRoot":"","sources":["../../src/core/command-handler.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAoF,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACjJ,OAAO,EAA8D,KAAK,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACtI,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAOnG,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,aAAa,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,eAAO,MAAM,eAAe,aAAkO,CAAC;AAE/P;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,aAA6D,CAAC;AAEtG;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,aAO/B,CAAC;AAEH;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAY1E;AAID,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0C9C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKpF;AAED;;;;mCAImC;AACnC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,GAAG,IAAI,CAqB1F;AAmED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC3C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzG,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,gCAAgC,EAAE,WAAW,EAAE,CAAC,CAAC;CACnF;AAoOD;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,kBAAkB,EACxB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAyuCf;AAoDD,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,qBAAqB,EAC7B,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,kBAAkB,EACxB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,gBAAgB,GAAG,sBAAsB,EACjD,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,kBAAkB,EACxB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
|
|
@@ -11,13 +11,15 @@ import * as sessionStore from '../services/session-store.js';
|
|
|
11
11
|
import * as scheduleStore from '../services/schedule-store.js';
|
|
12
12
|
import * as scheduler from './scheduler.js';
|
|
13
13
|
import { scanMultipleProjects, describeProjectDir } from '../services/project-scanner.js';
|
|
14
|
-
import { buildRepoSelectCard, buildAdoptSelectCard, buildCodexAppThreadSelectCard, buildSessionClosedCard, getCliDisplayName } from '../im/lark/card-builder.js';
|
|
14
|
+
import { buildRepoSelectCard, buildAdoptSelectCard, buildCodexAppThreadSelectCard, buildSessionClosedCard, buildSlashListCard, getCliDisplayName } from '../im/lark/card-builder.js';
|
|
15
15
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
16
16
|
import { deleteMessage, sendMessage, listChatBotMembers, resolveUserUnionId, getChatModeStrict } from '../im/lark/client.js';
|
|
17
|
+
import { chatAppLink, normalizeBrand } from '../im/lark/lark-hosts.js';
|
|
17
18
|
import { claimPairing } from '../services/pairing-store.js';
|
|
18
19
|
import { logger } from '../utils/logger.js';
|
|
19
20
|
import { killWorker, forkWorker, forkAdoptWorker, getCurrentCliVersion, postFreshStreamingCard, postPrivateSnapshotCard, resolvePrivateCardAudience, deliverEphemeralOrReply } from './worker-pool.js';
|
|
20
21
|
import { expandHome, getSessionWorkingDir, getProjectScanDirs, rememberLastCliInput } from './session-manager.js';
|
|
22
|
+
import { discoverSlashCommands, listMcpServerNames } from './command-discovery.js';
|
|
21
23
|
import { validateWorkingDir } from './working-dir.js';
|
|
22
24
|
import { discoverAdoptableSessions, validateAdoptTarget, adoptTargetKey, adoptTargetLabel } from './session-discovery.js';
|
|
23
25
|
import { discoverAdoptableZellijSessions, validateZellijAdoptTarget } from './zellij-adopt-discovery.js';
|
|
@@ -31,7 +33,7 @@ import { getBotCapability, setBotCapability, clearBotCapability } from '../servi
|
|
|
31
33
|
import { sessionKey, sessionAnchorId } from './types.js';
|
|
32
34
|
import { t, localeForBot } from '../i18n/index.js';
|
|
33
35
|
// ─── Exported constants ──────────────────────────────────────────────────────
|
|
34
|
-
export const DAEMON_COMMANDS = new Set(['/close', '/restart', '/status', '/help', '/cd', '/repo', '/schedule', '/role', '/pair', '/login', '/adopt', '/detach', '/disconnect', '/oncall', '/group', '/g', '/relay', '/card']);
|
|
36
|
+
export const DAEMON_COMMANDS = new Set(['/close', '/restart', '/status', '/help', '/cd', '/repo', '/schedule', '/role', '/pair', '/login', '/adopt', '/detach', '/disconnect', '/oncall', '/group', '/g', '/relay', '/card', '/list-slash-command', '/slash']);
|
|
35
37
|
/**
|
|
36
38
|
* Daemon commands that act on the chat itself rather than opening a
|
|
37
39
|
* conversation. `/group` (`/g`) just creates a Lark group and replies once —
|
|
@@ -40,7 +42,7 @@ export const DAEMON_COMMANDS = new Set(['/close', '/restart', '/status', '/help'
|
|
|
40
42
|
* card buttons routable, but for these that record is a phantom conversation
|
|
41
43
|
* that pollutes the dashboard's session list. Handle them without a session.
|
|
42
44
|
*/
|
|
43
|
-
export const SESSIONLESS_DAEMON_COMMANDS = new Set(['/group', '/g']);
|
|
45
|
+
export const SESSIONLESS_DAEMON_COMMANDS = new Set(['/group', '/g', '/list-slash-command', '/slash']);
|
|
44
46
|
/**
|
|
45
47
|
* Slash commands that are forwarded verbatim to the underlying CLI (e.g.
|
|
46
48
|
* Claude Code's `/compact`, `/model`, `/usage`). The daemon does NOT handle
|
|
@@ -56,6 +58,30 @@ export const PASSTHROUGH_COMMANDS = new Set([
|
|
|
56
58
|
// Codex:/btw 向当前会话追加一条旁注/引导消息
|
|
57
59
|
'/btw',
|
|
58
60
|
]);
|
|
61
|
+
/**
|
|
62
|
+
* Effective passthrough set for a bot: the fixed {@link PASSTHROUGH_COMMANDS}
|
|
63
|
+
* plus the bot's `customPassthroughCommands` (bots.json). Entries that would
|
|
64
|
+
* shadow a botmux daemon command are dropped — daemon commands must keep their
|
|
65
|
+
* daemon semantics, and passthrough is checked BEFORE DAEMON_COMMANDS in the
|
|
66
|
+
* router, so an un-filtered custom `/status` would hijack the daemon's own.
|
|
67
|
+
* Unknown / no bot → falls back to the builtin set unchanged.
|
|
68
|
+
*/
|
|
69
|
+
export function resolvePassthroughCommands(larkAppId) {
|
|
70
|
+
const effective = new Set(PASSTHROUGH_COMMANDS);
|
|
71
|
+
if (!larkAppId)
|
|
72
|
+
return effective;
|
|
73
|
+
try {
|
|
74
|
+
for (const c of getBot(larkAppId).config.customPassthroughCommands ?? []) {
|
|
75
|
+
if (DAEMON_COMMANDS.has(c))
|
|
76
|
+
continue; // never shadow a daemon command
|
|
77
|
+
effective.add(c);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* unknown bot — builtin set only */
|
|
82
|
+
}
|
|
83
|
+
return effective;
|
|
84
|
+
}
|
|
59
85
|
const MULTILINE_COMMANDS = new Set(['/schedule', '/role']);
|
|
60
86
|
// `validateWorkingDir` now lives in ./working-dir.js (leaf module the CLI can
|
|
61
87
|
// import without the daemon graph); re-exported here for existing callers.
|
|
@@ -843,16 +869,17 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
843
869
|
}
|
|
844
870
|
case '/login': {
|
|
845
871
|
const subCmd = message.content.replace(/^\/login\s*/, '').trim();
|
|
846
|
-
|
|
847
|
-
await sessionReply(rootId, getTokenStatus());
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
872
|
+
// 先定位本 bot 配置——token 状态与 OAuth URL 都按 per-bot appId/brand 走。
|
|
850
873
|
const botCfg2 = ds ? getBot(ds.larkAppId).config : (larkAppId ? getBot(larkAppId).config : getAllBots()[0]?.config);
|
|
851
874
|
if (!botCfg2?.larkAppId || !botCfg2?.larkAppSecret) {
|
|
852
875
|
await sessionReply(rootId, t('cmd.login.no_credentials', undefined, loc));
|
|
853
876
|
break;
|
|
854
877
|
}
|
|
855
|
-
|
|
878
|
+
if (subCmd === 'status' || subCmd === '状态') {
|
|
879
|
+
await sessionReply(rootId, getTokenStatus(botCfg2.larkAppId, normalizeBrand(botCfg2.brand)));
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
const { authUrl } = generateAuthUrl(botCfg2.larkAppId, botCfg2.larkAppSecret, normalizeBrand(botCfg2.brand));
|
|
856
883
|
await sessionReply(rootId, [
|
|
857
884
|
t('cmd.login.title', undefined, loc),
|
|
858
885
|
'',
|
|
@@ -1133,7 +1160,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1133
1160
|
});
|
|
1134
1161
|
// Prefer the shareable join link (others can click to *join*); fall
|
|
1135
1162
|
// back to the member-only applink URL when Lark's link API failed.
|
|
1136
|
-
const applink =
|
|
1163
|
+
const applink = chatAppLink(result.chatId, normalizeBrand(getBot(creatorAppId).config.brand));
|
|
1137
1164
|
const link = result.shareLink ?? applink;
|
|
1138
1165
|
// Partial failures are non-fatal — the chat exists; surface them as
|
|
1139
1166
|
// hints so the user knows whether to expect to be auto-invited.
|
|
@@ -1436,7 +1463,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1436
1463
|
transferOwnerTo: senderOpenId,
|
|
1437
1464
|
});
|
|
1438
1465
|
newChatId = result.chatId;
|
|
1439
|
-
const applink =
|
|
1466
|
+
const applink = chatAppLink(result.chatId, normalizeBrand(getBot(creatorAppId).config.brand));
|
|
1440
1467
|
inviteLink = result.shareLink ?? applink;
|
|
1441
1468
|
}
|
|
1442
1469
|
catch (err) {
|
|
@@ -1658,6 +1685,27 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1658
1685
|
await handleCardCommand(rootId, appId, cardChatId, message.senderId, message.content, deps);
|
|
1659
1686
|
break;
|
|
1660
1687
|
}
|
|
1688
|
+
case '/list-slash-command':
|
|
1689
|
+
case '/slash': {
|
|
1690
|
+
// 列出本 bot 当前可用的 slash 命令,分三段:
|
|
1691
|
+
// ① botmux 固定放行的透传白名单(PASSTHROUGH_COMMANDS)
|
|
1692
|
+
// ② 用户在 bots.json 自定义配置的额外透传命令(customPassthroughCommands)
|
|
1693
|
+
// ③ 文件系统自动发现的 .claude 自定义命令 / skill / 插件(discoverSlashCommands)
|
|
1694
|
+
// MCP 的 /mcp__<server>__<prompt> 需运行时握手才能枚举,这里仅按 .mcp.json 提示 server 名。
|
|
1695
|
+
const botCfg = ds
|
|
1696
|
+
? getBot(ds.larkAppId).config
|
|
1697
|
+
: (larkAppId ? getBot(larkAppId).config : getAllBots()[0]?.config);
|
|
1698
|
+
const cliName = getCliDisplayName(botCfg?.cliId ?? 'claude-code');
|
|
1699
|
+
const workingDir = getSessionWorkingDir(ds);
|
|
1700
|
+
const builtin = [...PASSTHROUGH_COMMANDS];
|
|
1701
|
+
const custom = botCfg?.customPassthroughCommands ?? [];
|
|
1702
|
+
const discovered = discoverSlashCommands(workingDir);
|
|
1703
|
+
const mcpServers = listMcpServerNames(workingDir);
|
|
1704
|
+
const card = buildSlashListCard({ cliName, builtin, custom, discovered, workingDir, mcpServers }, loc);
|
|
1705
|
+
await sessionReply(rootId, card, 'interactive');
|
|
1706
|
+
logger.info(`[${logTag}] /list-slash-command builtin=${builtin.length} custom=${custom.length} discovered=${discovered.length}`);
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1661
1709
|
case '/help': {
|
|
1662
1710
|
const botCfg = ds ? getBot(ds.larkAppId).config : getAllBots()[0]?.config;
|
|
1663
1711
|
const cliName = getCliDisplayName(botCfg?.cliId ?? 'claude-code');
|
|
@@ -1706,6 +1754,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1706
1754
|
t('help.heading_group', undefined, loc),
|
|
1707
1755
|
t('help.group', undefined, loc),
|
|
1708
1756
|
'',
|
|
1757
|
+
t('help.list_slash', undefined, loc),
|
|
1709
1758
|
t('help.help', undefined, loc),
|
|
1710
1759
|
];
|
|
1711
1760
|
await sessionReply(rootId, help.join('\n'));
|