@loghead/core 0.1.8 β 0.1.11
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/api/server.js +1 -1
- package/dist/cli_main.js +11 -5
- package/dist/db/client.js +2 -0
- package/dist/services/auth.js +5 -0
- package/dist/services/db.js +2 -1
- package/dist/ui/main.js +142 -32
- package/loghead.db +0 -0
- package/package.json +1 -1
- package/src/api/server.ts +1 -1
- package/src/cli_main.ts +12 -4
- package/src/db/client.ts +1 -0
- package/src/services/auth.ts +5 -0
- package/src/services/db.ts +2 -1
- package/src/ui/main.ts +156 -37
package/dist/api/server.js
CHANGED
|
@@ -15,7 +15,7 @@ async function startApiServer(db) {
|
|
|
15
15
|
app.use((0, cors_1.default)());
|
|
16
16
|
app.use(express_1.default.json());
|
|
17
17
|
await auth.initialize();
|
|
18
|
-
console.log(chalk_1.default.bold.green(
|
|
18
|
+
console.log(chalk_1.default.bold.green(`\nπ» MCP server running on:`));
|
|
19
19
|
console.log(chalk_1.default.green(`http://localhost:${port}`));
|
|
20
20
|
app.post("/api/ingest", async (req, res) => {
|
|
21
21
|
try {
|
package/dist/cli_main.js
CHANGED
|
@@ -12,7 +12,6 @@ const migrate_1 = require("./db/migrate");
|
|
|
12
12
|
// import { ensureInfrastructure } from "./utils/startup"; // Might need adjustment
|
|
13
13
|
const main_1 = require("./ui/main");
|
|
14
14
|
const auth_1 = require("./services/auth");
|
|
15
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
16
15
|
const db = new db_1.DbService();
|
|
17
16
|
const auth = new auth_1.AuthService();
|
|
18
17
|
async function main() {
|
|
@@ -25,13 +24,16 @@ async function main() {
|
|
|
25
24
|
console.log("Ensuring database is initialized...");
|
|
26
25
|
await (0, migrate_1.migrate)(false); // Run migrations silently
|
|
27
26
|
const token = await auth.getOrCreateMcpToken();
|
|
28
|
-
|
|
29
|
-
console.log(chalk_1.default.dim("\nUse this token for the MCP Server or other admin integrations.\n"));
|
|
30
|
-
console.log(chalk_1.default.yellow(`${token}`));
|
|
27
|
+
// Start API Server (this sets up express listen)
|
|
31
28
|
await (0, server_1.startApiServer)(db);
|
|
29
|
+
// Start TUI (this will clear screen and take over)
|
|
30
|
+
await (0, main_1.startTui)(db, token);
|
|
31
|
+
process.exit(0);
|
|
32
32
|
})
|
|
33
33
|
.command("ui", "Start Terminal UI", {}, async () => {
|
|
34
|
-
await
|
|
34
|
+
const token = await auth.getOrCreateMcpToken();
|
|
35
|
+
await (0, main_1.startTui)(db, token);
|
|
36
|
+
process.exit(0);
|
|
35
37
|
})
|
|
36
38
|
.command("projects <cmd> [name]", "Manage projects", (yargs) => {
|
|
37
39
|
yargs
|
|
@@ -63,6 +65,10 @@ async function main() {
|
|
|
63
65
|
const s = await db.createStream(argv.project, argv.type, argv.name, config);
|
|
64
66
|
console.log(`Stream created: ${s.id}`);
|
|
65
67
|
console.log(`Token: ${s.token}`);
|
|
68
|
+
})
|
|
69
|
+
.command("token <streamId>", "Get token for stream", {}, async (argv) => {
|
|
70
|
+
const token = await auth.createStreamToken(argv.streamId);
|
|
71
|
+
console.log(`Token: ${token}`);
|
|
66
72
|
});
|
|
67
73
|
})
|
|
68
74
|
.demandCommand(1)
|
package/dist/db/client.js
CHANGED
|
@@ -39,9 +39,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.db = void 0;
|
|
40
40
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
41
41
|
const sqliteVec = __importStar(require("sqlite-vec"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
42
43
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
43
44
|
dotenv_1.default.config();
|
|
44
45
|
const dbPath = process.env.LOGHEAD_DB_PATH || "loghead.db";
|
|
46
|
+
console.log(`[DB] Using database at: ${path_1.default.resolve(dbPath)}`);
|
|
45
47
|
const db = new better_sqlite3_1.default(dbPath);
|
|
46
48
|
exports.db = db;
|
|
47
49
|
// Load sqlite-vec extension
|
package/dist/services/auth.js
CHANGED
|
@@ -49,6 +49,7 @@ class AuthService {
|
|
|
49
49
|
if (!this.secretKey)
|
|
50
50
|
throw new Error("Auth not initialized");
|
|
51
51
|
try {
|
|
52
|
+
// console.log(`[Auth] Verifying token with secret: ${this.secretKey.substring(0, 10)}...`);
|
|
52
53
|
const payload = jsonwebtoken_1.default.verify(token, this.secretKey, { issuer: "loghead", algorithms: ["HS512"] });
|
|
53
54
|
if (!payload.sub)
|
|
54
55
|
return null;
|
|
@@ -56,6 +57,10 @@ class AuthService {
|
|
|
56
57
|
}
|
|
57
58
|
catch (e) {
|
|
58
59
|
console.error("Token verification failed:", e);
|
|
60
|
+
if (e instanceof Error && e.message === "invalid signature") {
|
|
61
|
+
console.error("[Auth] Secret key mismatch. Ensure the server is using the same database (and secret) as when the token was generated.");
|
|
62
|
+
console.error(`[Auth] Current secret starts with: ${this.secretKey?.substring(0, 8)}...`);
|
|
63
|
+
}
|
|
59
64
|
return null;
|
|
60
65
|
}
|
|
61
66
|
}
|
package/dist/services/db.js
CHANGED
|
@@ -89,7 +89,8 @@ class DbService {
|
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
const vectorJson = JSON.stringify(embedding);
|
|
92
|
-
|
|
92
|
+
// Explicitly cast rowid to BigInt to ensure better-sqlite3 binds it as an INTEGER
|
|
93
|
+
client_1.db.prepare("INSERT INTO vec_logs(rowid, embedding) VALUES (?, ?)").run(BigInt(rowid), vectorJson);
|
|
93
94
|
}
|
|
94
95
|
});
|
|
95
96
|
try {
|
package/dist/ui/main.js
CHANGED
|
@@ -4,68 +4,178 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.startTui = startTui;
|
|
7
|
+
const auth_1 = require("../services/auth");
|
|
7
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
let title = `
|
|
11
|
+
βββββ βββββ βββββ
|
|
12
|
+
βββββ βββββ βββββ
|
|
13
|
+
ββββ ββββββ βββββββ ββββββββ ββββββ ββββββ βββββββ
|
|
14
|
+
ββββ ββββββββ ββββββββ βββββββββ ββββββββ ββββββββ ββββββββ
|
|
15
|
+
ββββ ββββ ββββββββ ββββ ββββ ββββ ββββββββ βββββββ ββββ ββββ
|
|
16
|
+
ββββ βββββ ββββββββ ββββ ββββ ββββ βββββββ ββββββββ ββββ ββββ
|
|
17
|
+
βββββββββββββββββββ βββββββββ ββββ βββββββββββββ ββββββββββββββββββββ
|
|
18
|
+
βββββββββββ ββββββ ββββββββββββ βββββ ββββββ ββββββββ ββββββββ
|
|
19
|
+
βββ ββββ
|
|
20
|
+
ββββββββ
|
|
21
|
+
ββββββ `;
|
|
22
|
+
async function startTui(db, token) {
|
|
23
|
+
const port = process.env.PORT || 4567;
|
|
24
|
+
const showHeader = () => {
|
|
25
|
+
console.clear();
|
|
26
|
+
console.log(chalk_1.default.bold(title));
|
|
27
|
+
console.log(chalk_1.default.bold(`\nServer URL :`) + " " + chalk_1.default.dim(`http://localhost:${port}`));
|
|
28
|
+
console.log(chalk_1.default.bold(`MCP Token :`) + " " + chalk_1.default.dim(token) + "\n");
|
|
29
|
+
};
|
|
12
30
|
while (true) {
|
|
31
|
+
showHeader();
|
|
13
32
|
const projects = db.listProjects();
|
|
14
|
-
if (projects.length === 0) {
|
|
15
|
-
console.log("No projects found. Use 'npx loghead projects add <name>' to create one.");
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
33
|
const projectChoices = projects.map(p => ({ name: p.name, value: p.id }));
|
|
34
|
+
projectChoices.push(new inquirer_1.default.Separator());
|
|
35
|
+
projectChoices.push({ name: chalk_1.default.green("+ Create Project"), value: "create_project" });
|
|
19
36
|
projectChoices.push({ name: chalk_1.default.red("Exit"), value: "exit" });
|
|
20
37
|
const { projectId } = await inquirer_1.default.prompt([{
|
|
21
38
|
type: "list",
|
|
22
39
|
name: "projectId",
|
|
23
|
-
message: "Select a
|
|
24
|
-
choices: projectChoices
|
|
40
|
+
message: "Select a project",
|
|
41
|
+
choices: projectChoices,
|
|
42
|
+
pageSize: 10,
|
|
43
|
+
prefix: "π‘"
|
|
25
44
|
}]);
|
|
26
45
|
if (projectId === "exit")
|
|
27
46
|
break;
|
|
47
|
+
if (projectId === "create_project") {
|
|
48
|
+
const { name } = await inquirer_1.default.prompt([{
|
|
49
|
+
type: "input",
|
|
50
|
+
name: "name",
|
|
51
|
+
message: "Project Name:",
|
|
52
|
+
prefix: "π‘"
|
|
53
|
+
}]);
|
|
54
|
+
if (name) {
|
|
55
|
+
db.createProject(name);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
28
59
|
// List streams for project
|
|
29
60
|
while (true) {
|
|
30
|
-
|
|
61
|
+
showHeader();
|
|
31
62
|
const project = projects.find(p => p.id === projectId);
|
|
32
63
|
console.log(chalk_1.default.bold.blue(`Project: ${project?.name}\n`));
|
|
33
64
|
const streams = db.listStreams(projectId);
|
|
34
|
-
if (streams.length === 0) {
|
|
35
|
-
console.log("No streams found.");
|
|
36
|
-
}
|
|
37
65
|
const streamChoices = streams.map(s => ({
|
|
38
66
|
name: `${s.name} (${s.type})`,
|
|
39
67
|
value: s.id
|
|
40
68
|
}));
|
|
69
|
+
streamChoices.push(new inquirer_1.default.Separator());
|
|
70
|
+
streamChoices.push({ name: chalk_1.default.green("+ Create Stream"), value: "create_stream" });
|
|
41
71
|
streamChoices.push({ name: chalk_1.default.yellow("Back"), value: "back" });
|
|
42
72
|
const { streamId } = await inquirer_1.default.prompt([{
|
|
43
73
|
type: "list",
|
|
44
74
|
name: "streamId",
|
|
45
|
-
message: "Select a
|
|
46
|
-
choices: streamChoices
|
|
75
|
+
message: "Select a stream",
|
|
76
|
+
choices: streamChoices,
|
|
77
|
+
pageSize: 10,
|
|
78
|
+
prefix: "π‘"
|
|
47
79
|
}]);
|
|
48
80
|
if (streamId === "back")
|
|
49
81
|
break;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
if (streamId === "create_stream") {
|
|
83
|
+
showHeader();
|
|
84
|
+
console.log(chalk_1.default.bold.blue(`Project: ${project?.name}`));
|
|
85
|
+
console.log(chalk_1.default.bold.blue(` ββ Create Stream\n`));
|
|
86
|
+
const { name, type } = await inquirer_1.default.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: "input",
|
|
89
|
+
name: "name",
|
|
90
|
+
message: "Stream name:",
|
|
91
|
+
prefix: "π‘"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "list",
|
|
95
|
+
name: "type",
|
|
96
|
+
message: "Stream type:",
|
|
97
|
+
choices: ["browser", "terminal", "docker"],
|
|
98
|
+
prefix: "π‘"
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
if (name && type) {
|
|
102
|
+
// For now, empty config
|
|
103
|
+
const s = await db.createStream(projectId, type, name, {});
|
|
104
|
+
console.log(chalk_1.default.green(`\nStream created!`));
|
|
105
|
+
console.log(chalk_1.default.bold.yellow(`Token: ${s.token}\n`));
|
|
106
|
+
await inquirer_1.default.prompt([{
|
|
107
|
+
type: "input",
|
|
108
|
+
name: "continue",
|
|
109
|
+
message: "Press enter to continue...",
|
|
110
|
+
prefix: "π‘"
|
|
111
|
+
}]);
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
57
114
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
115
|
+
// Stream Actions
|
|
116
|
+
while (true) {
|
|
117
|
+
showHeader();
|
|
118
|
+
const stream = streams.find(s => s.id === streamId);
|
|
119
|
+
console.log(chalk_1.default.bold.blue(`Project: ${project?.name}`));
|
|
120
|
+
console.log(chalk_1.default.bold.blue(` ββ Stream: ${stream?.name} (${stream?.type})\n`));
|
|
121
|
+
const { action } = await inquirer_1.default.prompt([{
|
|
122
|
+
type: "list",
|
|
123
|
+
name: "action",
|
|
124
|
+
message: "Action",
|
|
125
|
+
choices: [
|
|
126
|
+
{ name: "View logs", value: "view_logs" },
|
|
127
|
+
{ name: "Get token", value: "get_token" },
|
|
128
|
+
{ name: "Delete stream", value: "delete_stream" },
|
|
129
|
+
{ name: chalk_1.default.yellow("Back"), value: "back" }
|
|
130
|
+
],
|
|
131
|
+
prefix: "π‘"
|
|
132
|
+
}]);
|
|
133
|
+
if (action === "back")
|
|
134
|
+
break;
|
|
135
|
+
if (action === "get_token") {
|
|
136
|
+
const auth = new auth_1.AuthService();
|
|
137
|
+
const token = await auth.createStreamToken(streamId);
|
|
138
|
+
console.log(chalk_1.default.green(`\nToken for ${stream?.name}:`));
|
|
139
|
+
console.log(chalk_1.default.bold.yellow(`${token}\n`));
|
|
140
|
+
await inquirer_1.default.prompt([{
|
|
141
|
+
type: "input",
|
|
142
|
+
name: "continue",
|
|
143
|
+
message: "Press enter to continue...",
|
|
144
|
+
prefix: "π‘"
|
|
145
|
+
}]);
|
|
146
|
+
}
|
|
147
|
+
if (action === "delete_stream") {
|
|
148
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
149
|
+
type: "confirm",
|
|
150
|
+
name: "confirm",
|
|
151
|
+
message: `Are you sure you want to delete stream ${stream?.name}?`
|
|
152
|
+
}]);
|
|
153
|
+
if (confirm) {
|
|
154
|
+
db.deleteStream(streamId);
|
|
155
|
+
break; // Go back to stream list
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (action === "view_logs") {
|
|
159
|
+
console.clear();
|
|
160
|
+
console.log(chalk_1.default.bold.green(`Logs for ${stream?.name}:\n`));
|
|
161
|
+
const logs = db.getRecentLogs(streamId, 20);
|
|
162
|
+
if (logs.length === 0) {
|
|
163
|
+
console.log("No logs recorded yet.");
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
[...logs].reverse().forEach(log => {
|
|
167
|
+
console.log(`${chalk_1.default.dim(log.timestamp)} ${log.content}`);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
console.log("\n");
|
|
171
|
+
await inquirer_1.default.prompt([{
|
|
172
|
+
type: "input",
|
|
173
|
+
name: "return",
|
|
174
|
+
message: "Press enter to return...",
|
|
175
|
+
prefix: "π‘"
|
|
176
|
+
}]);
|
|
177
|
+
}
|
|
63
178
|
}
|
|
64
|
-
console.log("\nPress Enter to return...");
|
|
65
|
-
await new Promise(resolve => {
|
|
66
|
-
process.stdin.once('data', () => resolve());
|
|
67
|
-
});
|
|
68
179
|
}
|
|
69
|
-
console.clear();
|
|
70
180
|
}
|
|
71
181
|
}
|
package/loghead.db
ADDED
|
Binary file
|
package/package.json
CHANGED
package/src/api/server.ts
CHANGED
|
@@ -15,7 +15,7 @@ export async function startApiServer(db: DbService) {
|
|
|
15
15
|
|
|
16
16
|
await auth.initialize();
|
|
17
17
|
|
|
18
|
-
console.log(chalk.bold.green(
|
|
18
|
+
console.log(chalk.bold.green(`\nπ» MCP server running on:`));
|
|
19
19
|
console.log(chalk.green(`http://localhost:${port}`));
|
|
20
20
|
|
|
21
21
|
app.post("/api/ingest", async (req, res) => {
|
package/src/cli_main.ts
CHANGED
|
@@ -23,14 +23,18 @@ async function main() {
|
|
|
23
23
|
await migrate(false); // Run migrations silently
|
|
24
24
|
|
|
25
25
|
const token = await auth.getOrCreateMcpToken();
|
|
26
|
-
console.log(chalk.bold.yellow(`\nπ MCP Server Token:`));
|
|
27
|
-
console.log(chalk.dim("\nUse this token for the MCP Server or other admin integrations.\n"));
|
|
28
|
-
console.log(chalk.yellow(`${token}`));
|
|
29
26
|
|
|
27
|
+
// Start API Server (this sets up express listen)
|
|
30
28
|
await startApiServer(db);
|
|
29
|
+
|
|
30
|
+
// Start TUI (this will clear screen and take over)
|
|
31
|
+
await startTui(db, token);
|
|
32
|
+
process.exit(0);
|
|
31
33
|
})
|
|
32
34
|
.command("ui", "Start Terminal UI", {}, async () => {
|
|
33
|
-
await
|
|
35
|
+
const token = await auth.getOrCreateMcpToken();
|
|
36
|
+
await startTui(db, token);
|
|
37
|
+
process.exit(0);
|
|
34
38
|
})
|
|
35
39
|
.command("projects <cmd> [name]", "Manage projects", (yargs) => {
|
|
36
40
|
yargs
|
|
@@ -62,6 +66,10 @@ async function main() {
|
|
|
62
66
|
const s = await db.createStream(argv.project, argv.type as string, argv.name as string, config);
|
|
63
67
|
console.log(`Stream created: ${s.id}`);
|
|
64
68
|
console.log(`Token: ${s.token}`);
|
|
69
|
+
})
|
|
70
|
+
.command("token <streamId>", "Get token for stream", {}, async (argv) => {
|
|
71
|
+
const token = await auth.createStreamToken(argv.streamId as string);
|
|
72
|
+
console.log(`Token: ${token}`);
|
|
65
73
|
});
|
|
66
74
|
})
|
|
67
75
|
.demandCommand(1)
|
package/src/db/client.ts
CHANGED
package/src/services/auth.ts
CHANGED
|
@@ -58,11 +58,16 @@ export class AuthService {
|
|
|
58
58
|
if (!this.secretKey) throw new Error("Auth not initialized");
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
+
// console.log(`[Auth] Verifying token with secret: ${this.secretKey.substring(0, 10)}...`);
|
|
61
62
|
const payload = jwt.verify(token, this.secretKey, { issuer: "loghead", algorithms: ["HS512"] }) as jwt.JwtPayload;
|
|
62
63
|
if (!payload.sub) return null;
|
|
63
64
|
return { streamId: payload.sub };
|
|
64
65
|
} catch (e) {
|
|
65
66
|
console.error("Token verification failed:", e);
|
|
67
|
+
if (e instanceof Error && e.message === "invalid signature") {
|
|
68
|
+
console.error("[Auth] Secret key mismatch. Ensure the server is using the same database (and secret) as when the token was generated.");
|
|
69
|
+
console.error(`[Auth] Current secret starts with: ${this.secretKey?.substring(0, 8)}...`);
|
|
70
|
+
}
|
|
66
71
|
return null;
|
|
67
72
|
}
|
|
68
73
|
}
|
package/src/services/db.ts
CHANGED
|
@@ -102,7 +102,8 @@ export class DbService {
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
const vectorJson = JSON.stringify(embedding);
|
|
105
|
-
|
|
105
|
+
// Explicitly cast rowid to BigInt to ensure better-sqlite3 binds it as an INTEGER
|
|
106
|
+
(db.prepare("INSERT INTO vec_logs(rowid, embedding) VALUES (?, ?)") as unknown as DbAny).run(BigInt(rowid), vectorJson);
|
|
106
107
|
}
|
|
107
108
|
});
|
|
108
109
|
|
package/src/ui/main.ts
CHANGED
|
@@ -1,78 +1,197 @@
|
|
|
1
1
|
import { DbService } from "../services/db";
|
|
2
|
+
import { AuthService } from "../services/auth";
|
|
2
3
|
import inquirer from "inquirer";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
let title = `
|
|
7
|
+
βββββ βββββ βββββ
|
|
8
|
+
βββββ βββββ βββββ
|
|
9
|
+
ββββ ββββββ βββββββ ββββββββ ββββββ ββββββ βββββββ
|
|
10
|
+
ββββ ββββββββ ββββββββ βββββββββ ββββββββ ββββββββ ββββββββ
|
|
11
|
+
ββββ ββββ ββββββββ ββββ ββββ ββββ ββββββββ βββββββ ββββ ββββ
|
|
12
|
+
ββββ βββββ ββββββββ ββββ ββββ ββββ βββββββ ββββββββ ββββ ββββ
|
|
13
|
+
βββββββββββββββββββ βββββββββ ββββ βββββββββββββ ββββββββββββββββββββ
|
|
14
|
+
βββββββββββ ββββββ ββββββββββββ βββββ ββββββ ββββββββ ββββββββ
|
|
15
|
+
βββ ββββ
|
|
16
|
+
ββββββββ
|
|
17
|
+
ββββββ `;
|
|
18
|
+
|
|
19
|
+
export async function startTui(db: DbService, token: string) {
|
|
20
|
+
const port = process.env.PORT || 4567;
|
|
21
|
+
const showHeader = () => {
|
|
22
|
+
console.clear();
|
|
23
|
+
console.log(chalk.bold(title));
|
|
24
|
+
console.log(chalk.bold(`\nServer URL :`) + " " + chalk.dim(`http://localhost:${port}`));
|
|
25
|
+
console.log(chalk.bold(`MCP Token :`) + " " + chalk.dim(token) + "\n");
|
|
26
|
+
};
|
|
8
27
|
|
|
9
28
|
while (true) {
|
|
29
|
+
showHeader();
|
|
10
30
|
const projects = db.listProjects();
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const projectChoices = projects.map(p => ({ name: p.name, value: p.id }));
|
|
32
|
+
const projectChoices: (inquirer.Separator | { name: string; value: string })[] = projects.map(p => ({ name: p.name, value: p.id }));
|
|
33
|
+
projectChoices.push(new inquirer.Separator());
|
|
34
|
+
projectChoices.push({ name: chalk.green("+ Create Project"), value: "create_project" });
|
|
18
35
|
projectChoices.push({ name: chalk.red("Exit"), value: "exit" });
|
|
19
36
|
|
|
20
37
|
const { projectId } = await inquirer.prompt([{
|
|
21
38
|
type: "list",
|
|
22
39
|
name: "projectId",
|
|
23
|
-
message: "Select a
|
|
24
|
-
choices: projectChoices
|
|
40
|
+
message: "Select a project",
|
|
41
|
+
choices: projectChoices,
|
|
42
|
+
pageSize: 10,
|
|
43
|
+
prefix: "π‘"
|
|
25
44
|
}]);
|
|
26
45
|
|
|
27
46
|
if (projectId === "exit") break;
|
|
28
47
|
|
|
48
|
+
if (projectId === "create_project") {
|
|
49
|
+
const { name } = await inquirer.prompt([{
|
|
50
|
+
type: "input",
|
|
51
|
+
name: "name",
|
|
52
|
+
message: "Project Name:",
|
|
53
|
+
prefix: "π‘"
|
|
54
|
+
}]);
|
|
55
|
+
if (name) {
|
|
56
|
+
db.createProject(name);
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
// List streams for project
|
|
30
62
|
while (true) {
|
|
31
|
-
|
|
63
|
+
showHeader();
|
|
32
64
|
const project = projects.find(p => p.id === projectId);
|
|
33
65
|
console.log(chalk.bold.blue(`Project: ${project?.name}\n`));
|
|
34
66
|
|
|
35
67
|
const streams = db.listStreams(projectId);
|
|
36
68
|
|
|
37
|
-
|
|
38
|
-
console.log("No streams found.");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const streamChoices = streams.map(s => ({
|
|
69
|
+
const streamChoices: (inquirer.Separator | { name: string; value: string })[] = streams.map(s => ({
|
|
42
70
|
name: `${s.name} (${s.type})`,
|
|
43
71
|
value: s.id
|
|
44
72
|
}));
|
|
73
|
+
streamChoices.push(new inquirer.Separator());
|
|
74
|
+
streamChoices.push({ name: chalk.green("+ Create Stream"), value: "create_stream" });
|
|
45
75
|
streamChoices.push({ name: chalk.yellow("Back"), value: "back" });
|
|
46
76
|
|
|
47
77
|
const { streamId } = await inquirer.prompt([{
|
|
48
78
|
type: "list",
|
|
49
79
|
name: "streamId",
|
|
50
|
-
message: "Select a
|
|
51
|
-
choices: streamChoices
|
|
80
|
+
message: "Select a stream",
|
|
81
|
+
choices: streamChoices,
|
|
82
|
+
pageSize: 10,
|
|
83
|
+
prefix: "π‘"
|
|
52
84
|
}]);
|
|
53
85
|
|
|
54
86
|
if (streamId === "back") break;
|
|
55
87
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
if (streamId === "create_stream") {
|
|
89
|
+
showHeader();
|
|
90
|
+
console.log(chalk.bold.blue(`Project: ${project?.name}`));
|
|
91
|
+
console.log(chalk.bold.blue(` ββ Create Stream\n`));
|
|
92
|
+
|
|
93
|
+
const { name, type } = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: "input",
|
|
96
|
+
name: "name",
|
|
97
|
+
message: "Stream name:",
|
|
98
|
+
prefix: "π‘"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: "list",
|
|
102
|
+
name: "type",
|
|
103
|
+
message: "Stream type:",
|
|
104
|
+
choices: ["browser", "terminal", "docker"],
|
|
105
|
+
prefix: "π‘"
|
|
106
|
+
}
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
if (name && type) {
|
|
110
|
+
// For now, empty config
|
|
111
|
+
const s = await db.createStream(projectId, type, name, {});
|
|
112
|
+
console.log(chalk.green(`\nStream created!`));
|
|
113
|
+
console.log(chalk.bold.yellow(`Token: ${s.token}\n`));
|
|
114
|
+
|
|
115
|
+
await inquirer.prompt([{
|
|
116
|
+
type: "input",
|
|
117
|
+
name: "continue",
|
|
118
|
+
message: "Press enter to continue...",
|
|
119
|
+
prefix: "π‘"
|
|
120
|
+
}]);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
69
123
|
}
|
|
70
124
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
125
|
+
// Stream Actions
|
|
126
|
+
while (true) {
|
|
127
|
+
showHeader();
|
|
128
|
+
const stream = streams.find(s => s.id === streamId);
|
|
129
|
+
console.log(chalk.bold.blue(`Project: ${project?.name}`));
|
|
130
|
+
console.log(chalk.bold.blue(` ββ Stream: ${stream?.name} (${stream?.type})\n`));
|
|
131
|
+
|
|
132
|
+
const { action } = await inquirer.prompt([{
|
|
133
|
+
type: "list",
|
|
134
|
+
name: "action",
|
|
135
|
+
message: "Action",
|
|
136
|
+
choices: [
|
|
137
|
+
{ name: "View logs", value: "view_logs" },
|
|
138
|
+
{ name: "Get token", value: "get_token" },
|
|
139
|
+
{ name: "Delete stream", value: "delete_stream" },
|
|
140
|
+
{ name: chalk.yellow("Back"), value: "back" }
|
|
141
|
+
],
|
|
142
|
+
prefix: "π‘"
|
|
143
|
+
}]);
|
|
144
|
+
|
|
145
|
+
if (action === "back") break;
|
|
146
|
+
|
|
147
|
+
if (action === "get_token") {
|
|
148
|
+
const auth = new AuthService();
|
|
149
|
+
const token = await auth.createStreamToken(streamId);
|
|
150
|
+
console.log(chalk.green(`\nToken for ${stream?.name}:`));
|
|
151
|
+
console.log(chalk.bold.yellow(`${token}\n`));
|
|
152
|
+
|
|
153
|
+
await inquirer.prompt([{
|
|
154
|
+
type: "input",
|
|
155
|
+
name: "continue",
|
|
156
|
+
message: "Press enter to continue...",
|
|
157
|
+
prefix: "π‘"
|
|
158
|
+
}]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (action === "delete_stream") {
|
|
162
|
+
const { confirm } = await inquirer.prompt([{
|
|
163
|
+
type: "confirm",
|
|
164
|
+
name: "confirm",
|
|
165
|
+
message: `Are you sure you want to delete stream ${stream?.name}?`
|
|
166
|
+
}]);
|
|
167
|
+
if (confirm) {
|
|
168
|
+
db.deleteStream(streamId);
|
|
169
|
+
break; // Go back to stream list
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (action === "view_logs") {
|
|
174
|
+
console.clear();
|
|
175
|
+
console.log(chalk.bold.green(`Logs for ${stream?.name}:\n`));
|
|
176
|
+
|
|
177
|
+
const logs = db.getRecentLogs(streamId, 20);
|
|
178
|
+
if (logs.length === 0) {
|
|
179
|
+
console.log("No logs recorded yet.");
|
|
180
|
+
} else {
|
|
181
|
+
[...logs].reverse().forEach(log => {
|
|
182
|
+
console.log(`${chalk.dim(log.timestamp)} ${log.content}`);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log("\n");
|
|
187
|
+
await inquirer.prompt([{
|
|
188
|
+
type: "input",
|
|
189
|
+
name: "return",
|
|
190
|
+
message: "Press enter to return...",
|
|
191
|
+
prefix: "π‘"
|
|
192
|
+
}]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
75
195
|
}
|
|
76
|
-
console.clear();
|
|
77
196
|
}
|
|
78
197
|
}
|