@gitim-runtime/cli 0.1.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/dist/client.d.ts +34 -0
- package/dist/client.js +99 -0
- package/dist/commands/archive-channel.d.ts +1 -0
- package/dist/commands/archive-channel.js +19 -0
- package/dist/commands/archived-channels.d.ts +1 -0
- package/dist/commands/archived-channels.js +26 -0
- package/dist/commands/channels.d.ts +1 -0
- package/dist/commands/channels.js +18 -0
- package/dist/commands/create-channel.d.ts +4 -0
- package/dist/commands/create-channel.js +19 -0
- package/dist/commands/dm.d.ts +10 -0
- package/dist/commands/dm.js +81 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +24 -0
- package/dist/commands/join-channel.d.ts +3 -0
- package/dist/commands/join-channel.js +20 -0
- package/dist/commands/onboard.d.ts +17 -0
- package/dist/commands/onboard.js +261 -0
- package/dist/commands/read.d.ts +4 -0
- package/dist/commands/read.js +20 -0
- package/dist/commands/reindex.d.ts +1 -0
- package/dist/commands/reindex.js +18 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +41 -0
- package/dist/commands/send.d.ts +4 -0
- package/dist/commands/send.js +19 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +18 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +21 -0
- package/dist/commands/tui.d.ts +1 -0
- package/dist/commands/tui.js +57 -0
- package/dist/commands/users.d.ts +1 -0
- package/dist/commands/users.js +18 -0
- package/dist/commands/webui.d.ts +5 -0
- package/dist/commands/webui.js +41 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +141 -0
- package/dist/tui/app.d.ts +43 -0
- package/dist/tui/app.js +461 -0
- package/dist/tui/channel-sidebar.d.ts +14 -0
- package/dist/tui/channel-sidebar.js +62 -0
- package/dist/tui/daemon-connection.d.ts +38 -0
- package/dist/tui/daemon-connection.js +118 -0
- package/dist/tui/mention-popup.d.ts +13 -0
- package/dist/tui/mention-popup.js +59 -0
- package/dist/tui/message-view.d.ts +38 -0
- package/dist/tui/message-view.js +166 -0
- package/dist/tui/split-layout.d.ts +17 -0
- package/dist/tui/split-layout.js +48 -0
- package/dist/tui/status-bar.d.ts +12 -0
- package/dist/tui/status-bar.js +39 -0
- package/dist/tui/themes.d.ts +22 -0
- package/dist/tui/themes.js +49 -0
- package/dist/tui/thread-view.d.ts +14 -0
- package/dist/tui/thread-view.js +72 -0
- package/dist/webui/assets/index-YH6ztb9g.css +1 -0
- package/dist/webui/assets/index-nFs-lh9F.js +49 -0
- package/dist/webui/index.html +13 -0
- package/dist/webui/server.d.ts +7 -0
- package/dist/webui/server.js +229 -0
- package/package.json +28 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
2
|
+
import { GitimClient } from '../client.js';
|
|
3
|
+
export async function readCommand(channel, options) {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repository');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
const limit = options.limit ? parseInt(options.limit, 10) : undefined;
|
|
12
|
+
const since = options.since ? parseInt(options.since, 10) : undefined;
|
|
13
|
+
const res = await client.read(channel, limit, since);
|
|
14
|
+
if (res.ok) {
|
|
15
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error('Error:', res.error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function reindexCommand(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { GitimClient } from '../client.js';
|
|
2
|
+
import { ensureDaemon, findRepoRoot } from '../daemon.js';
|
|
3
|
+
export async function reindexCommand() {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repo');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
console.log('Rebuilding search index...');
|
|
12
|
+
const result = await client.reindex();
|
|
13
|
+
if (!result.ok) {
|
|
14
|
+
console.error(`Reindex failed: ${result.error}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
console.log(`Done. ${result.data.messages_indexed} messages indexed.`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { GitimClient } from '../client.js';
|
|
2
|
+
import { ensureDaemon, findRepoRoot } from '../daemon.js';
|
|
3
|
+
export async function searchCommand(query, options) {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repo');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
const limit = options.limit ? parseInt(options.limit) : undefined;
|
|
12
|
+
const offset = options.offset ? parseInt(options.offset) : undefined;
|
|
13
|
+
if (limit !== undefined && isNaN(limit)) {
|
|
14
|
+
console.error('Invalid limit: must be a number');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (offset !== undefined && isNaN(offset)) {
|
|
18
|
+
console.error('Invalid offset: must be a number');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const result = await client.search({
|
|
22
|
+
query: query || undefined,
|
|
23
|
+
author: options.author,
|
|
24
|
+
channel: options.channel,
|
|
25
|
+
channel_type: options.type,
|
|
26
|
+
limit,
|
|
27
|
+
offset,
|
|
28
|
+
});
|
|
29
|
+
if (!result.ok) {
|
|
30
|
+
console.error(`Search failed: ${result.error}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const { messages, total } = result.data;
|
|
34
|
+
console.log(`Found ${total} results:\n`);
|
|
35
|
+
for (const msg of messages) {
|
|
36
|
+
const prefix = msg.channel_type === 'dm' ? `[DM:${msg.channel}]` : `[#${msg.channel}]`;
|
|
37
|
+
console.log(`${prefix} @${msg.author} (L${msg.line_number}) ${msg.timestamp}`);
|
|
38
|
+
console.log(` ${msg.body}`);
|
|
39
|
+
console.log();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
2
|
+
import { GitimClient } from '../client.js';
|
|
3
|
+
export async function sendCommand(channel, body, options) {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repository');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
const replyTo = options.replyTo ? parseInt(options.replyTo, 10) : undefined;
|
|
12
|
+
const res = await client.send(channel, body, options.author, replyTo);
|
|
13
|
+
if (res.ok) {
|
|
14
|
+
console.log('Message sent.');
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.error('Error:', res.error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function statusCommand(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
2
|
+
import { GitimClient } from '../client.js';
|
|
3
|
+
export async function statusCommand() {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repository');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
const res = await client.status();
|
|
12
|
+
if (res.ok) {
|
|
13
|
+
console.log('Daemon status:', JSON.stringify(res.data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.error('Error:', res.error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stopCommand(): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { findRepoRoot, isDaemonRunning } from '../daemon.js';
|
|
2
|
+
import { GitimClient } from '../client.js';
|
|
3
|
+
export async function stopCommand() {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repository');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
if (!isDaemonRunning(repoRoot)) {
|
|
10
|
+
console.log('Daemon is not running.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const client = new GitimClient(repoRoot);
|
|
14
|
+
try {
|
|
15
|
+
await client.stop();
|
|
16
|
+
console.log('Daemon stopped.');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
console.log('Daemon stopped.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function tuiCommand(): Promise<void>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 命令入口 — 启动终端聊天界面
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
8
|
+
import { TuiApp } from '../tui/app.js';
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前用户身份
|
|
11
|
+
*/
|
|
12
|
+
function getCurrentUser(repoRoot) {
|
|
13
|
+
// 优先从 .gitim/me.json 读取
|
|
14
|
+
const meFile = path.join(repoRoot, '.gitim', 'me.json');
|
|
15
|
+
if (fs.existsSync(meFile)) {
|
|
16
|
+
try {
|
|
17
|
+
const me = JSON.parse(fs.readFileSync(meFile, 'utf-8'));
|
|
18
|
+
if (me.handler)
|
|
19
|
+
return me.handler;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// 忽略解析错误
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 回退到 git config
|
|
26
|
+
try {
|
|
27
|
+
const name = execFileSync('git', ['config', 'user.name'], {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
}).trim().toLowerCase().replace(/\s+/g, '-');
|
|
31
|
+
if (name)
|
|
32
|
+
return name;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// 忽略
|
|
36
|
+
}
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
export async function tuiCommand() {
|
|
40
|
+
const repoRoot = findRepoRoot();
|
|
41
|
+
if (!repoRoot) {
|
|
42
|
+
console.error('错误:不在 GitIM 仓库中');
|
|
43
|
+
console.error(' → 请先运行 `gitim onboard` 加入或创建仓库');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 确保 daemon 运行
|
|
47
|
+
try {
|
|
48
|
+
await ensureDaemon(repoRoot);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
console.error(`错误:无法启动 daemon — ${e.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const user = getCurrentUser(repoRoot);
|
|
55
|
+
const app = new TuiApp({ repoRoot, user });
|
|
56
|
+
await app.start();
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usersCommand(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
2
|
+
import { GitimClient } from '../client.js';
|
|
3
|
+
export async function usersCommand() {
|
|
4
|
+
const repoRoot = findRepoRoot();
|
|
5
|
+
if (!repoRoot) {
|
|
6
|
+
console.error('Not in a GitIM repository');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
await ensureDaemon(repoRoot);
|
|
10
|
+
const client = new GitimClient(repoRoot);
|
|
11
|
+
const res = await client.listUsers();
|
|
12
|
+
if (res.ok) {
|
|
13
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.error('Error:', res.error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebUI 命令入口 — 启动浏览器聊天界面
|
|
3
|
+
*/
|
|
4
|
+
import { findRepoRoot, ensureDaemon } from '../daemon.js';
|
|
5
|
+
import { startServer } from '../webui/server.js';
|
|
6
|
+
export async function webuiCommand(options) {
|
|
7
|
+
const repoRoot = findRepoRoot();
|
|
8
|
+
if (!repoRoot) {
|
|
9
|
+
console.error('错误:不在 GitIM 仓库中');
|
|
10
|
+
console.error(' → 请先运行 `gitim onboard` 加入或创建仓库');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// 确保 daemon 运行
|
|
14
|
+
try {
|
|
15
|
+
await ensureDaemon(repoRoot);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
console.error(`错误:无法启动 daemon — ${e.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// 启动 bridge server
|
|
22
|
+
try {
|
|
23
|
+
await startServer({ repoRoot, port: options.port, dev: options.dev });
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (e.message?.includes('already in use')) {
|
|
27
|
+
console.error(`错误:端口 ${options.port} 已被占用`);
|
|
28
|
+
console.error(` → 使用 --port <port> 指定其他端口`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.error(`错误:无法启动 WebUI — ${e.message}`);
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log(`\nGitIM WebUI: http://localhost:${options.port}\n`);
|
|
36
|
+
// HTTP server 保持进程运行,Ctrl+C 干净退出
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
console.log('\n正在关闭 WebUI...');
|
|
39
|
+
process.exit(0);
|
|
40
|
+
});
|
|
41
|
+
}
|
package/dist/daemon.d.ts
ADDED
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const DAEMON_STARTUP_TIMEOUT_MS = 5000;
|
|
5
|
+
const POLL_INTERVAL_MS = 100;
|
|
6
|
+
export function findRepoRoot(from = process.cwd()) {
|
|
7
|
+
let dir = from;
|
|
8
|
+
while (true) {
|
|
9
|
+
if (fs.existsSync(path.join(dir, '.gitim'))) {
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
12
|
+
const parent = path.dirname(dir);
|
|
13
|
+
if (parent === dir)
|
|
14
|
+
return null;
|
|
15
|
+
dir = parent;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function isDaemonRunning(repoRoot) {
|
|
19
|
+
const pidFile = path.join(repoRoot, '.gitim', 'run', 'gitim.pid');
|
|
20
|
+
if (!fs.existsSync(pidFile))
|
|
21
|
+
return false;
|
|
22
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
23
|
+
if (isNaN(pid))
|
|
24
|
+
return false;
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, 0);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function ensureDaemon(repoRoot) {
|
|
34
|
+
const sockPath = path.join(repoRoot, '.gitim', 'run', 'gitim.sock');
|
|
35
|
+
if (isDaemonRunning(repoRoot)) {
|
|
36
|
+
// Daemon process exists — wait for socket if not ready yet (startup race)
|
|
37
|
+
if (fs.existsSync(sockPath))
|
|
38
|
+
return;
|
|
39
|
+
const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
|
|
40
|
+
while (Date.now() < deadline) {
|
|
41
|
+
if (fs.existsSync(sockPath))
|
|
42
|
+
return;
|
|
43
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
44
|
+
}
|
|
45
|
+
throw new Error('daemon is running but socket not ready');
|
|
46
|
+
}
|
|
47
|
+
// Clean up stale runtime files before spawning
|
|
48
|
+
cleanStaleFiles(repoRoot);
|
|
49
|
+
const child = spawn('gitim-daemon', [], {
|
|
50
|
+
cwd: repoRoot,
|
|
51
|
+
detached: true,
|
|
52
|
+
stdio: 'ignore',
|
|
53
|
+
});
|
|
54
|
+
child.unref();
|
|
55
|
+
const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
if (fs.existsSync(sockPath))
|
|
58
|
+
return;
|
|
59
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
60
|
+
}
|
|
61
|
+
throw new Error('daemon failed to start within timeout');
|
|
62
|
+
}
|
|
63
|
+
function cleanStaleFiles(repoRoot) {
|
|
64
|
+
const runDir = path.join(repoRoot, '.gitim', 'run');
|
|
65
|
+
const files = ['gitim.pid', 'gitim.sock', 'gitim.port', 'gitim.lock'];
|
|
66
|
+
for (const f of files) {
|
|
67
|
+
const p = path.join(runDir, f);
|
|
68
|
+
try {
|
|
69
|
+
fs.unlinkSync(p);
|
|
70
|
+
}
|
|
71
|
+
catch { /* ignore */ }
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { statusCommand } from './commands/status.js';
|
|
4
|
+
import { sendCommand } from './commands/send.js';
|
|
5
|
+
import { readCommand } from './commands/read.js';
|
|
6
|
+
import { channelsCommand } from './commands/channels.js';
|
|
7
|
+
import { usersCommand } from './commands/users.js';
|
|
8
|
+
import { dmSendCommand, dmReadCommand, dmListCommand } from './commands/dm.js';
|
|
9
|
+
import { onboardCommand } from './commands/onboard.js';
|
|
10
|
+
import { stopCommand } from './commands/stop.js';
|
|
11
|
+
import { tuiCommand } from './commands/tui.js';
|
|
12
|
+
import { webuiCommand } from './commands/webui.js';
|
|
13
|
+
import { searchCommand } from './commands/search.js';
|
|
14
|
+
import { reindexCommand } from './commands/reindex.js';
|
|
15
|
+
import { createChannelCommand } from './commands/create-channel.js';
|
|
16
|
+
import { joinChannelCommand } from './commands/join-channel.js';
|
|
17
|
+
import { archiveChannelCommand } from './commands/archive-channel.js';
|
|
18
|
+
import { archivedChannelsCommand } from './commands/archived-channels.js';
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name('gitim')
|
|
22
|
+
.description('GitIM CLI — AI-native IM over Git')
|
|
23
|
+
.version('0.1.0');
|
|
24
|
+
program
|
|
25
|
+
.command('onboard [repo_name] [org]')
|
|
26
|
+
.description('加入或创建 GitIM 仓库')
|
|
27
|
+
.option('-g, --git-server <type>', 'Git 服务类型: git | github | gitea | gitlab', 'github')
|
|
28
|
+
.option('-t, --token <token>', 'GitHub/Gitea/GitLab 认证 token')
|
|
29
|
+
.option('--handler <handler>', 'git 本地模式必填:本地 handler')
|
|
30
|
+
.option('--display-name <name>', 'git 本地模式必填:显示名称')
|
|
31
|
+
.option('-u, --url <url>', 'Gitea/GitLab 服务地址')
|
|
32
|
+
.option('--refresh', '重新推断身份')
|
|
33
|
+
.option('--debug-http', '开启 HTTP 调试端口')
|
|
34
|
+
.option('--admin', 'admin 模式:poll 返回所有内容(审查视角)')
|
|
35
|
+
.option('--guest', '游客模式:只读,不需要身份认证')
|
|
36
|
+
.option('--with-webui', 'Onboard 完成后启动 WebUI')
|
|
37
|
+
.option('--webui-port <port>', 'WebUI 端口号', '6868')
|
|
38
|
+
.option('--webui-dev', 'WebUI 开发模式(启用 Vite HMR)', false)
|
|
39
|
+
.action(async (repoName, org, options) => {
|
|
40
|
+
await onboardCommand(repoName, org, options);
|
|
41
|
+
});
|
|
42
|
+
program
|
|
43
|
+
.command('status')
|
|
44
|
+
.description('Show daemon status')
|
|
45
|
+
.action(() => statusCommand());
|
|
46
|
+
program
|
|
47
|
+
.command('send <channel> <body>')
|
|
48
|
+
.description('Send a message to a channel')
|
|
49
|
+
.option('-a, --author <handler>', '作者 handler(可选,默认使用 onboard 身份)')
|
|
50
|
+
.option('-r, --reply-to <line>', 'Reply to line number')
|
|
51
|
+
.action((channel, body, options) => sendCommand(channel, body, options));
|
|
52
|
+
program
|
|
53
|
+
.command('read <channel>')
|
|
54
|
+
.description('Read messages from a channel')
|
|
55
|
+
.option('-l, --limit <n>', 'Limit number of messages')
|
|
56
|
+
.option('-s, --since <line>', 'Show messages since line number')
|
|
57
|
+
.action((channel, options) => readCommand(channel, options));
|
|
58
|
+
program
|
|
59
|
+
.command('channels')
|
|
60
|
+
.description('List channels')
|
|
61
|
+
.action(() => channelsCommand());
|
|
62
|
+
program
|
|
63
|
+
.command('create-channel <name>')
|
|
64
|
+
.description('创建新频道')
|
|
65
|
+
.option('--display-name <name>', '频道显示名称')
|
|
66
|
+
.option('--introduction <text>', '频道简介')
|
|
67
|
+
.action(async (name, options) => {
|
|
68
|
+
await createChannelCommand(name, options);
|
|
69
|
+
});
|
|
70
|
+
program
|
|
71
|
+
.command('join-channel <channel>')
|
|
72
|
+
.description('加入频道或拉人入群')
|
|
73
|
+
.option('-t, --targets <handlers...>', '要拉入的用户(不指定则自己加入)')
|
|
74
|
+
.action(async (channel, options) => {
|
|
75
|
+
await joinChannelCommand(channel, options);
|
|
76
|
+
});
|
|
77
|
+
program
|
|
78
|
+
.command('archive-channel <name>')
|
|
79
|
+
.description('归档频道')
|
|
80
|
+
.action(async (name) => {
|
|
81
|
+
await archiveChannelCommand(name);
|
|
82
|
+
});
|
|
83
|
+
program
|
|
84
|
+
.command('archived-channels')
|
|
85
|
+
.description('列出已归档频道')
|
|
86
|
+
.action(async () => {
|
|
87
|
+
await archivedChannelsCommand();
|
|
88
|
+
});
|
|
89
|
+
program
|
|
90
|
+
.command('users')
|
|
91
|
+
.description('List users')
|
|
92
|
+
.action(() => usersCommand());
|
|
93
|
+
program
|
|
94
|
+
.command('stop')
|
|
95
|
+
.description('停止当前仓库的 daemon')
|
|
96
|
+
.action(async () => {
|
|
97
|
+
await stopCommand();
|
|
98
|
+
});
|
|
99
|
+
program
|
|
100
|
+
.command('search [query]')
|
|
101
|
+
.description('搜索消息')
|
|
102
|
+
.option('-a, --author <handler>', '按作者过滤')
|
|
103
|
+
.option('-c, --channel <name>', '限定频道')
|
|
104
|
+
.option('-t, --type <type>', '频道类型: channel | dm')
|
|
105
|
+
.option('-l, --limit <n>', '结果数量限制', '50')
|
|
106
|
+
.option('--offset <n>', '分页偏移', '0')
|
|
107
|
+
.action((query, options) => searchCommand(query, options));
|
|
108
|
+
program
|
|
109
|
+
.command('reindex')
|
|
110
|
+
.description('重建搜索索引')
|
|
111
|
+
.action(() => reindexCommand());
|
|
112
|
+
const dm = program.command('dm').description('Direct messages');
|
|
113
|
+
dm.command('send <handler> <body>')
|
|
114
|
+
.description('Send a DM')
|
|
115
|
+
.option('-a, --author <handler>', '作者 handler(可选,默认使用 onboard 身份)')
|
|
116
|
+
.option('-r, --reply-to <line>', 'Reply to line number')
|
|
117
|
+
.action((handler, body, options) => dmSendCommand(handler, body, options));
|
|
118
|
+
dm.command('read <handler>')
|
|
119
|
+
.description('Read DM conversation')
|
|
120
|
+
.option('-a, --author <handler>', '作者 handler(可选,默认使用 onboard 身份)')
|
|
121
|
+
.option('-l, --limit <n>', 'Limit messages')
|
|
122
|
+
.option('-s, --since <line>', 'Since line number')
|
|
123
|
+
.action((handler, options) => dmReadCommand(handler, options));
|
|
124
|
+
dm.command('list')
|
|
125
|
+
.description('List DM conversations')
|
|
126
|
+
.action(() => dmListCommand());
|
|
127
|
+
program
|
|
128
|
+
.command('tui')
|
|
129
|
+
.description('启动 TUI 聊天界面')
|
|
130
|
+
.action(async () => {
|
|
131
|
+
await tuiCommand();
|
|
132
|
+
});
|
|
133
|
+
program
|
|
134
|
+
.command('webui')
|
|
135
|
+
.description('启动 WebUI 浏览器聊天界面')
|
|
136
|
+
.option('-p, --port <port>', '服务端口号', '6868')
|
|
137
|
+
.option('--dev', '开发模式(启用 Vite HMR)', false)
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
await webuiCommand({ port: parseInt(options.port, 10), dev: options.dev });
|
|
140
|
+
});
|
|
141
|
+
program.parse();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface TuiAppOptions {
|
|
2
|
+
repoRoot: string;
|
|
3
|
+
user: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class TuiApp {
|
|
6
|
+
private tui;
|
|
7
|
+
private terminal;
|
|
8
|
+
private conn;
|
|
9
|
+
private user;
|
|
10
|
+
private repoRoot;
|
|
11
|
+
private sidebar;
|
|
12
|
+
private messageView;
|
|
13
|
+
private editor;
|
|
14
|
+
private statusBar;
|
|
15
|
+
private splitLayout;
|
|
16
|
+
private threadView;
|
|
17
|
+
private mentionPopup;
|
|
18
|
+
private mode;
|
|
19
|
+
private users;
|
|
20
|
+
private threadOverlay;
|
|
21
|
+
private mentionOverlay;
|
|
22
|
+
private mentionFilter;
|
|
23
|
+
private loadMessagesInFlight;
|
|
24
|
+
private loadMessagesPending;
|
|
25
|
+
constructor(options: TuiAppOptions);
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
private updateLayoutHeight;
|
|
28
|
+
private handleGlobalInput;
|
|
29
|
+
private handleNormalInput;
|
|
30
|
+
private handleBrowseInput;
|
|
31
|
+
private handleChainInput;
|
|
32
|
+
private setMode;
|
|
33
|
+
private sendMessage;
|
|
34
|
+
private loadChannels;
|
|
35
|
+
private loadUsers;
|
|
36
|
+
private loadMessages;
|
|
37
|
+
private onChannelChanged;
|
|
38
|
+
private showThread;
|
|
39
|
+
private closeThread;
|
|
40
|
+
private showMention;
|
|
41
|
+
private closeMention;
|
|
42
|
+
private shutdown;
|
|
43
|
+
}
|