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.
Files changed (75) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +5 -5
  3. package/dist/bot-registry.d.ts +24 -0
  4. package/dist/bot-registry.d.ts.map +1 -1
  5. package/dist/bot-registry.js +30 -0
  6. package/dist/bot-registry.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +28 -31
  9. package/dist/cli.js.map +1 -1
  10. package/dist/core/command-discovery.d.ts +24 -0
  11. package/dist/core/command-discovery.d.ts.map +1 -0
  12. package/dist/core/command-discovery.js +201 -0
  13. package/dist/core/command-discovery.js.map +1 -0
  14. package/dist/core/command-handler.d.ts +9 -0
  15. package/dist/core/command-handler.d.ts.map +1 -1
  16. package/dist/core/command-handler.js +59 -10
  17. package/dist/core/command-handler.js.map +1 -1
  18. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  19. package/dist/core/dashboard-ipc-server.js +2 -1
  20. package/dist/core/dashboard-ipc-server.js.map +1 -1
  21. package/dist/core/dashboard-rows.d.ts +2 -1
  22. package/dist/core/dashboard-rows.d.ts.map +1 -1
  23. package/dist/core/dashboard-rows.js +6 -4
  24. package/dist/core/dashboard-rows.js.map +1 -1
  25. package/dist/core/worker-pool.d.ts.map +1 -1
  26. package/dist/core/worker-pool.js +3 -0
  27. package/dist/core/worker-pool.js.map +1 -1
  28. package/dist/daemon.d.ts.map +1 -1
  29. package/dist/daemon.js +5 -4
  30. package/dist/daemon.js.map +1 -1
  31. package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
  32. package/dist/dashboard/bot-onboarding.js +10 -13
  33. package/dist/dashboard/bot-onboarding.js.map +1 -1
  34. package/dist/dashboard/web/groups.d.ts.map +1 -1
  35. package/dist/dashboard/web/groups.js +6 -1
  36. package/dist/dashboard/web/groups.js.map +1 -1
  37. package/dist/dashboard-web/app.js +1 -1
  38. package/dist/i18n/en.d.ts.map +1 -1
  39. package/dist/i18n/en.js +12 -0
  40. package/dist/i18n/en.js.map +1 -1
  41. package/dist/i18n/zh.d.ts.map +1 -1
  42. package/dist/i18n/zh.js +12 -0
  43. package/dist/i18n/zh.js.map +1 -1
  44. package/dist/im/lark/card-builder.d.ts +17 -0
  45. package/dist/im/lark/card-builder.d.ts.map +1 -1
  46. package/dist/im/lark/card-builder.js +84 -0
  47. package/dist/im/lark/card-builder.js.map +1 -1
  48. package/dist/im/lark/client.d.ts.map +1 -1
  49. package/dist/im/lark/client.js +7 -5
  50. package/dist/im/lark/client.js.map +1 -1
  51. package/dist/im/lark/event-dispatcher.d.ts +2 -1
  52. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  53. package/dist/im/lark/event-dispatcher.js +15 -9
  54. package/dist/im/lark/event-dispatcher.js.map +1 -1
  55. package/dist/im/lark/lark-hosts.d.ts +44 -0
  56. package/dist/im/lark/lark-hosts.d.ts.map +1 -0
  57. package/dist/im/lark/lark-hosts.js +49 -0
  58. package/dist/im/lark/lark-hosts.js.map +1 -0
  59. package/dist/setup/verify-permissions.d.ts +2 -1
  60. package/dist/setup/verify-permissions.d.ts.map +1 -1
  61. package/dist/setup/verify-permissions.js +5 -8
  62. package/dist/setup/verify-permissions.js.map +1 -1
  63. package/dist/types.d.ts +1 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/utils/lark-upload.d.ts +2 -1
  66. package/dist/utils/lark-upload.d.ts.map +1 -1
  67. package/dist/utils/lark-upload.js +11 -7
  68. package/dist/utils/lark-upload.js.map +1 -1
  69. package/dist/utils/user-token.d.ts +12 -4
  70. package/dist/utils/user-token.d.ts.map +1 -1
  71. package/dist/utils/user-token.js +64 -25
  72. package/dist/utils/user-token.js.map +1 -1
  73. package/dist/worker.js +4 -1
  74. package/dist/worker.js.map +1 -1
  75. 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":"AAoBA,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,aAAiM,CAAC;AAE9N;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,aAA4B,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,aAO/B,CAAC;AAIH,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,CA6sCf;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"}
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
- if (subCmd === 'status' || subCmd === '状态') {
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
- const { authUrl } = generateAuthUrl(botCfg2.larkAppId, botCfg2.larkAppSecret);
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 = `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(result.chatId)}`;
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 = `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(result.chatId)}`;
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'));