ei-tui 1.3.4 → 1.4.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.md +22 -0
- package/package.json +1 -1
- package/src/cli/mcp.ts +2 -2
- package/src/core/orchestrators/human-extraction.ts +2 -0
- package/src/core/processor.ts +61 -0
- package/src/core/prompt-context-builder.ts +17 -8
- package/src/core/tools/builtin/pkce.ts +40 -23
- package/src/core/tools/builtin/slack-auth.ts +117 -0
- package/src/core/tools/index.ts +1 -1
- package/src/core/types/entities.ts +1 -0
- package/src/core/utils/message-id.ts +4 -0
- package/src/integrations/slack/importer.ts +408 -0
- package/src/integrations/slack/reader.ts +416 -0
- package/src/integrations/slack/types.ts +30 -0
- package/src/prompts/human/person-scan.ts +16 -2
- package/src/prompts/human/types.ts +6 -0
- package/src/prompts/response/sections.ts +1 -1
- package/src/prompts/synthesis/index.ts +1 -1
- package/src/templates/slack.ts +17 -0
- package/tui/README.md +27 -0
- package/tui/src/commands/auth.ts +7 -3
- package/tui/src/commands/slack-auth.ts +167 -0
- package/tui/src/util/help-content.ts +1 -0
- package/tui/src/util/logger.ts +3 -2
- package/tui/src/util/yaml-settings.ts +25 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { CommandContext } from "./registry.js";
|
|
2
|
+
import { logger } from "../util/logger.js";
|
|
3
|
+
import {
|
|
4
|
+
generateVerifier,
|
|
5
|
+
generateChallenge,
|
|
6
|
+
buildAuthUrl,
|
|
7
|
+
exchangeCode,
|
|
8
|
+
} from "../../../src/core/tools/builtin/pkce.js";
|
|
9
|
+
import {
|
|
10
|
+
SLACK_CLIENT_ID,
|
|
11
|
+
SLACK_USER_SCOPES,
|
|
12
|
+
SLACK_TUI_REDIRECT_URI,
|
|
13
|
+
SLACK_TUI_PORT,
|
|
14
|
+
clearSlackTokenCache,
|
|
15
|
+
} from "../../../src/core/tools/builtin/slack-auth.js";
|
|
16
|
+
|
|
17
|
+
export async function runSlackAuth(ctx: CommandContext): Promise<void> {
|
|
18
|
+
logger.info("[slack-auth] runSlackAuth() called");
|
|
19
|
+
ctx.showNotification("Starting Slack auth — opening browser…", "info");
|
|
20
|
+
|
|
21
|
+
const verifier = generateVerifier();
|
|
22
|
+
const challenge = await generateChallenge(verifier);
|
|
23
|
+
logger.info("[slack-auth] PKCE verifier + challenge generated");
|
|
24
|
+
|
|
25
|
+
const authUrl = buildAuthUrl({
|
|
26
|
+
clientId: SLACK_CLIENT_ID,
|
|
27
|
+
redirectUri: SLACK_TUI_REDIRECT_URI,
|
|
28
|
+
scopes: [],
|
|
29
|
+
userScopes: SLACK_USER_SCOPES,
|
|
30
|
+
challenge,
|
|
31
|
+
authEndpoint: "https://slack.com/oauth/v2/authorize",
|
|
32
|
+
});
|
|
33
|
+
logger.info("[slack-auth] Auth URL built", { redirectUri: SLACK_TUI_REDIRECT_URI });
|
|
34
|
+
|
|
35
|
+
const codePromise = waitForAuthCode(ctx);
|
|
36
|
+
|
|
37
|
+
const openCmd = process.platform === "darwin"
|
|
38
|
+
? "open"
|
|
39
|
+
: process.platform === "win32"
|
|
40
|
+
? "cmd /c start"
|
|
41
|
+
: "xdg-open";
|
|
42
|
+
|
|
43
|
+
logger.info("[slack-auth] Spawning browser", { openCmd });
|
|
44
|
+
Bun.spawn([openCmd, authUrl], { stdio: ["ignore", "ignore", "ignore"] });
|
|
45
|
+
logger.info("[slack-auth] Browser spawned — awaiting OAuth callback…");
|
|
46
|
+
|
|
47
|
+
const code = await codePromise;
|
|
48
|
+
logger.info("[slack-auth] codePromise resolved", { gotCode: !!code });
|
|
49
|
+
|
|
50
|
+
if (!code) return;
|
|
51
|
+
|
|
52
|
+
ctx.showNotification("Exchanging auth code for tokens…", "info");
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
logger.info("[slack-auth] Exchanging code for tokens");
|
|
56
|
+
const tokens = await exchangeCode({
|
|
57
|
+
code,
|
|
58
|
+
verifier,
|
|
59
|
+
redirectUri: SLACK_TUI_REDIRECT_URI,
|
|
60
|
+
clientId: SLACK_CLIENT_ID,
|
|
61
|
+
tokenEndpoint: "https://slack.com/api/oauth.v2.access",
|
|
62
|
+
tokenResponsePath: ["authed_user"],
|
|
63
|
+
});
|
|
64
|
+
logger.info("[slack-auth] Token exchange succeeded — storing tokens");
|
|
65
|
+
|
|
66
|
+
clearSlackTokenCache();
|
|
67
|
+
|
|
68
|
+
const team = tokens._raw.team as Record<string, string> | undefined;
|
|
69
|
+
const workspaceId = team?.id;
|
|
70
|
+
const workspaceName = team?.name;
|
|
71
|
+
|
|
72
|
+
const human = await ctx.ei.getHuman();
|
|
73
|
+
await ctx.ei.updateSettings({
|
|
74
|
+
slack: {
|
|
75
|
+
...human.settings?.slack,
|
|
76
|
+
auth: {
|
|
77
|
+
type: "pkce",
|
|
78
|
+
token: tokens.access_token,
|
|
79
|
+
refresh_token: tokens.refresh_token,
|
|
80
|
+
workspace_id: workspaceId,
|
|
81
|
+
workspace_name: workspaceName,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
logger.info("[slack-auth] Tokens stored — done!");
|
|
87
|
+
ctx.showNotification("✓ Slack connected successfully!", "info");
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
90
|
+
logger.error("[slack-auth] Token exchange failed", { msg });
|
|
91
|
+
ctx.showNotification(`Slack auth failed: ${msg}`, "error");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function waitForAuthCode(ctx: CommandContext): Promise<string | null> {
|
|
96
|
+
return new Promise<string | null>((resolve) => {
|
|
97
|
+
const TIMEOUT_MS = 120_000;
|
|
98
|
+
|
|
99
|
+
let resolved = false;
|
|
100
|
+
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
101
|
+
|
|
102
|
+
const finish = (code: string | null) => {
|
|
103
|
+
if (resolved) return;
|
|
104
|
+
resolved = true;
|
|
105
|
+
logger.info("[slack-auth] finish() called", { gotCode: !!code });
|
|
106
|
+
try { server?.stop(true); } catch { /* ignore */ }
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
resolve(code);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
logger.warn("[slack-auth] Timed out waiting for callback");
|
|
113
|
+
ctx.showNotification("Slack auth timed out (2 min)", "error");
|
|
114
|
+
finish(null);
|
|
115
|
+
}, TIMEOUT_MS);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
server = Bun.serve({
|
|
119
|
+
port: SLACK_TUI_PORT,
|
|
120
|
+
hostname: "127.0.0.1",
|
|
121
|
+
fetch(req) {
|
|
122
|
+
const url = new URL(req.url);
|
|
123
|
+
logger.info("[slack-auth] Incoming request", { method: req.method, path: url.pathname });
|
|
124
|
+
|
|
125
|
+
if (url.pathname !== "/") {
|
|
126
|
+
return new Response("Not found", { status: 404 });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const code = url.searchParams.get("code");
|
|
130
|
+
const error = url.searchParams.get("error");
|
|
131
|
+
logger.info("[slack-auth] Callback params", { hasCode: !!code, error });
|
|
132
|
+
|
|
133
|
+
if (error || !code) {
|
|
134
|
+
const msg = error ?? "no code in callback";
|
|
135
|
+
logger.error("[slack-auth] Auth denied or missing code", { msg });
|
|
136
|
+
ctx.showNotification(`Slack denied auth: ${msg}`, "error");
|
|
137
|
+
finish(null);
|
|
138
|
+
return new Response(
|
|
139
|
+
"<html><body><h2>Auth failed — return to your terminal.</h2></body></html>",
|
|
140
|
+
{ headers: { "Content-Type": "text/html" } }
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const resp = new Response(
|
|
145
|
+
"<html><head><meta charset=\"utf-8\"></head><body><h2>✓ Slack connected! You can close this tab.</h2></body></html>",
|
|
146
|
+
{ headers: { "Content-Type": "text/html; charset=utf-8" } }
|
|
147
|
+
);
|
|
148
|
+
setTimeout(() => finish(code), 0);
|
|
149
|
+
return resp;
|
|
150
|
+
},
|
|
151
|
+
error(err) {
|
|
152
|
+
logger.error("[slack-auth] Bun.serve error", { msg: err.message });
|
|
153
|
+
ctx.showNotification(`Local auth server error: ${err.message}`, "error");
|
|
154
|
+
finish(null);
|
|
155
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
logger.info("[slack-auth] Bun.serve started", { port: server.port });
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
161
|
+
logger.error("[slack-auth] Bun.serve failed to start", { msg });
|
|
162
|
+
ctx.showNotification(`Failed to start local auth server: ${msg}`, "error");
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
resolve(null);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
package/tui/src/util/logger.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** File-based logger for TUI debugging. Usage: tail -f $EI_DATA_PATH/tui.log */
|
|
2
2
|
|
|
3
|
-
import { appendFileSync, mkdirSync, existsSync, readdirSync, unlinkSync,
|
|
3
|
+
import { appendFileSync, mkdirSync, existsSync, readdirSync, unlinkSync, copyFileSync, truncateSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { resolveDataPath } from "./resolve-data-path.js";
|
|
6
6
|
|
|
@@ -64,7 +64,8 @@ export function rotateLog(): void {
|
|
|
64
64
|
|
|
65
65
|
if (existsSync(logPath)) {
|
|
66
66
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
67
|
-
|
|
67
|
+
copyFileSync(logPath, join(dataDir, `tui-${ts}.log`));
|
|
68
|
+
truncateSync(logPath, 0);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
const rolled = readdirSync(dataDir)
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from "../../../src/core/types.js";
|
|
8
8
|
import type { ClaudeCodeSettings } from "../../../src/integrations/claude-code/types.js";
|
|
9
9
|
import type { CursorSettings } from "../../../src/integrations/cursor/types.js";
|
|
10
|
+
import type { SlackSettings } from "../../../src/integrations/slack/types.js";
|
|
10
11
|
import { modelGuidToDisplay, displayToModelGuid } from "./yaml-shared.js";
|
|
11
12
|
import { parseDuration, formatDuration } from "./duration.js";
|
|
12
13
|
|
|
@@ -46,6 +47,12 @@ interface EditableSettingsData {
|
|
|
46
47
|
last_sync?: string | null;
|
|
47
48
|
extraction_point?: string | null;
|
|
48
49
|
};
|
|
50
|
+
slack?: {
|
|
51
|
+
integration?: boolean | null;
|
|
52
|
+
polling_interval_ms?: string | null;
|
|
53
|
+
last_sync?: string | null;
|
|
54
|
+
extraction_model?: string | null;
|
|
55
|
+
};
|
|
49
56
|
backup?: {
|
|
50
57
|
enabled?: boolean | null;
|
|
51
58
|
max_backups?: number | null;
|
|
@@ -95,6 +102,12 @@ export function settingsToYAML(settings: HumanSettings | undefined, accounts: Pr
|
|
|
95
102
|
last_sync: settings?.cursor?.last_sync ?? null,
|
|
96
103
|
extraction_point: settings?.cursor?.extraction_point ?? null,
|
|
97
104
|
},
|
|
105
|
+
slack: {
|
|
106
|
+
integration: settings?.slack?.integration ?? false,
|
|
107
|
+
polling_interval_ms: formatDuration(settings?.slack?.polling_interval_ms ?? 60000),
|
|
108
|
+
last_sync: settings?.slack?.last_sync ?? null,
|
|
109
|
+
extraction_model: guidToDisplay(settings?.slack?.extraction_model) ?? 'default',
|
|
110
|
+
},
|
|
98
111
|
backup: {
|
|
99
112
|
enabled: settings?.backup?.enabled ?? false,
|
|
100
113
|
max_backups: settings?.backup?.max_backups ?? 24,
|
|
@@ -173,6 +186,17 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
173
186
|
};
|
|
174
187
|
}
|
|
175
188
|
|
|
189
|
+
let slack: SlackSettings | undefined;
|
|
190
|
+
if (data.slack) {
|
|
191
|
+
slack = {
|
|
192
|
+
...original?.slack,
|
|
193
|
+
integration: nullToUndefined(data.slack.integration),
|
|
194
|
+
polling_interval_ms: parseMsDuration(data.slack.polling_interval_ms, 60000),
|
|
195
|
+
last_sync: original?.slack?.last_sync,
|
|
196
|
+
extraction_model: displayToGuid(data.slack.extraction_model),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
176
200
|
let backup: import('../../../src/core/types.js').BackupConfig | undefined;
|
|
177
201
|
if (data.backup) {
|
|
178
202
|
backup = {
|
|
@@ -197,6 +221,7 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
197
221
|
opencode,
|
|
198
222
|
claudeCode,
|
|
199
223
|
cursor,
|
|
224
|
+
slack,
|
|
200
225
|
backup,
|
|
201
226
|
};
|
|
202
227
|
}
|