nomoreide 0.1.26 → 0.1.28
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/core/config-store.d.ts +6 -1
- package/dist/core/config-store.js +108 -0
- package/dist/core/config-store.js.map +1 -1
- package/dist/core/db/driver.d.ts +43 -0
- package/dist/core/db/driver.js +41 -0
- package/dist/core/db/driver.js.map +1 -0
- package/dist/core/db/mysql-driver.d.ts +19 -0
- package/dist/core/db/mysql-driver.js +95 -0
- package/dist/core/db/mysql-driver.js.map +1 -0
- package/dist/core/db/postgres-driver.d.ts +19 -0
- package/dist/core/db/postgres-driver.js +109 -0
- package/dist/core/db/postgres-driver.js.map +1 -0
- package/dist/core/db/sqlite-driver.d.ts +17 -0
- package/dist/core/db/sqlite-driver.js +68 -0
- package/dist/core/db/sqlite-driver.js.map +1 -0
- package/dist/core/db-peek.d.ts +49 -0
- package/dist/core/db-peek.js +150 -0
- package/dist/core/db-peek.js.map +1 -0
- package/dist/core/log-sources.d.ts +21 -0
- package/dist/core/log-sources.js +219 -0
- package/dist/core/log-sources.js.map +1 -0
- package/dist/core/service-log-source.d.ts +9 -0
- package/dist/core/service-log-source.js +49 -0
- package/dist/core/service-log-source.js.map +1 -0
- package/dist/core/terminal-manager.d.ts +40 -0
- package/dist/core/terminal-manager.js +64 -0
- package/dist/core/terminal-manager.js.map +1 -0
- package/dist/core/terminal-session.d.ts +91 -0
- package/dist/core/terminal-session.js +159 -0
- package/dist/core/terminal-session.js.map +1 -0
- package/dist/core/types.d.ts +71 -0
- package/dist/mcp/server.js +3 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/context.d.ts +2 -0
- package/dist/mcp/tools/context.js.map +1 -1
- package/dist/mcp/tools/database.d.ts +8 -0
- package/dist/mcp/tools/database.js +52 -0
- package/dist/mcp/tools/database.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +1 -1
- package/dist/mcp/tools/index.js +3 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/web/client/assets/index-0aS5yzpo.js +40 -0
- package/dist/web/client/assets/index-DIpnQDCK.css +1 -0
- package/dist/web/client/index.html +2 -2
- package/dist/web/routes/context.d.ts +4 -0
- package/dist/web/routes/context.js.map +1 -1
- package/dist/web/routes/database-routes.d.ts +3 -0
- package/dist/web/routes/database-routes.js +78 -0
- package/dist/web/routes/database-routes.js.map +1 -0
- package/dist/web/routes/git-routes.js +9 -1
- package/dist/web/routes/git-routes.js.map +1 -1
- package/dist/web/routes/index.js +6 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/log-sources-routes.d.ts +6 -0
- package/dist/web/routes/log-sources-routes.js +102 -0
- package/dist/web/routes/log-sources-routes.js.map +1 -0
- package/dist/web/routes/service-routes.js +24 -2
- package/dist/web/routes/service-routes.js.map +1 -1
- package/dist/web/routes/shell-routes.js +8 -1
- package/dist/web/routes/shell-routes.js.map +1 -1
- package/dist/web/routes/terminal-routes.d.ts +7 -0
- package/dist/web/routes/terminal-routes.js +25 -0
- package/dist/web/routes/terminal-routes.js.map +1 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +107 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +13 -2
- package/dist/web/client/assets/index-CgtUFH5U.css +0 -1
- package/dist/web/client/assets/index-DQdQ7efQ.js +0 -24
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ConfigStore } from "./config-store.js";
|
|
2
|
+
import type { DatabaseEngine } from "./types.js";
|
|
3
|
+
import type { RowSample, TableRef } from "./db/driver.js";
|
|
4
|
+
export interface MaskedConnection {
|
|
5
|
+
name: string;
|
|
6
|
+
engine: DatabaseEngine;
|
|
7
|
+
/** Connection URL with any password redacted (path left intact for SQLite). */
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
/** A DB connection string discovered in a service's `.env` file. */
|
|
11
|
+
export interface DetectedConnection {
|
|
12
|
+
service: string;
|
|
13
|
+
key: string;
|
|
14
|
+
engine: DatabaseEngine;
|
|
15
|
+
url: string;
|
|
16
|
+
maskedUrl: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DbPeekOptions {
|
|
19
|
+
configStore: ConfigStore;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* DB Peek: a read-only table browser. Owns its own bundled drivers (Postgres,
|
|
23
|
+
* MySQL, SQLite) — it never borrows the agent's MCP database tooling. Drivers
|
|
24
|
+
* are cached per connection URL and reused across requests.
|
|
25
|
+
*/
|
|
26
|
+
export declare class DbPeek {
|
|
27
|
+
private readonly configStore;
|
|
28
|
+
private readonly drivers;
|
|
29
|
+
constructor(options: DbPeekOptions);
|
|
30
|
+
listConnections(): Promise<MaskedConnection[]>;
|
|
31
|
+
listTables(name: string): Promise<TableRef[]>;
|
|
32
|
+
sampleRows(name: string, qualifiedName: string, limit: number): Promise<{
|
|
33
|
+
engine: DatabaseEngine;
|
|
34
|
+
table: TableRef;
|
|
35
|
+
} & RowSample>;
|
|
36
|
+
/** Test an unsaved connection without caching it. */
|
|
37
|
+
test(engine: DatabaseEngine, url: string): Promise<void>;
|
|
38
|
+
/** Scan registered services' `.env` files for usable connection strings. */
|
|
39
|
+
detectFromEnv(): Promise<DetectedConnection[]>;
|
|
40
|
+
closeAll(): Promise<void>;
|
|
41
|
+
private resolve;
|
|
42
|
+
private driverFor;
|
|
43
|
+
/** Only browse tables that actually exist — blocks identifier injection. */
|
|
44
|
+
private resolveTable;
|
|
45
|
+
}
|
|
46
|
+
/** Guess an engine from a connection string or file path; null if unrecognized. */
|
|
47
|
+
export declare function engineFromUrl(value: string): DatabaseEngine | null;
|
|
48
|
+
/** Redact the password from a URL; SQLite paths are returned unchanged. */
|
|
49
|
+
export declare function maskConnectionUrl(engine: DatabaseEngine, url: string): string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readEnvFile, entriesFromLines } from "./env-file.js";
|
|
3
|
+
import { MysqlDriver } from "./db/mysql-driver.js";
|
|
4
|
+
import { PostgresDriver } from "./db/postgres-driver.js";
|
|
5
|
+
import { SqliteDriver } from "./db/sqlite-driver.js";
|
|
6
|
+
/**
|
|
7
|
+
* DB Peek: a read-only table browser. Owns its own bundled drivers (Postgres,
|
|
8
|
+
* MySQL, SQLite) — it never borrows the agent's MCP database tooling. Drivers
|
|
9
|
+
* are cached per connection URL and reused across requests.
|
|
10
|
+
*/
|
|
11
|
+
export class DbPeek {
|
|
12
|
+
configStore;
|
|
13
|
+
drivers = new Map();
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.configStore = options.configStore;
|
|
16
|
+
}
|
|
17
|
+
async listConnections() {
|
|
18
|
+
const config = await this.configStore.load();
|
|
19
|
+
return config.databases.map((connection) => ({
|
|
20
|
+
name: connection.name,
|
|
21
|
+
engine: connection.engine,
|
|
22
|
+
url: maskConnectionUrl(connection.engine, connection.url),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
async listTables(name) {
|
|
26
|
+
const driver = await this.driverFor(await this.resolve(name));
|
|
27
|
+
return driver.listTables();
|
|
28
|
+
}
|
|
29
|
+
async sampleRows(name, qualifiedName, limit) {
|
|
30
|
+
const connection = await this.resolve(name);
|
|
31
|
+
const driver = await this.driverFor(connection);
|
|
32
|
+
const table = await this.resolveTable(driver, qualifiedName);
|
|
33
|
+
const sample = await driver.sampleRows(table, limit);
|
|
34
|
+
return { engine: connection.engine, table, ...sample };
|
|
35
|
+
}
|
|
36
|
+
/** Test an unsaved connection without caching it. */
|
|
37
|
+
async test(engine, url) {
|
|
38
|
+
const driver = createDriver(engine, url);
|
|
39
|
+
try {
|
|
40
|
+
await driver.testConnection();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await driver.close().catch(() => { });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Scan registered services' `.env` files for usable connection strings. */
|
|
47
|
+
async detectFromEnv() {
|
|
48
|
+
const config = await this.configStore.load();
|
|
49
|
+
const found = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
for (const service of config.services) {
|
|
52
|
+
if (!service.cwd)
|
|
53
|
+
continue;
|
|
54
|
+
const { exists, lines } = await readEnvFile(join(service.cwd, ".env"));
|
|
55
|
+
if (!exists)
|
|
56
|
+
continue;
|
|
57
|
+
for (const { key, value } of entriesFromLines(lines)) {
|
|
58
|
+
const engine = engineFromUrl(value);
|
|
59
|
+
if (!engine)
|
|
60
|
+
continue;
|
|
61
|
+
const dedupe = `${engine}:${value}`;
|
|
62
|
+
if (seen.has(dedupe))
|
|
63
|
+
continue;
|
|
64
|
+
seen.add(dedupe);
|
|
65
|
+
found.push({
|
|
66
|
+
service: service.name,
|
|
67
|
+
key,
|
|
68
|
+
engine,
|
|
69
|
+
url: value,
|
|
70
|
+
maskedUrl: maskConnectionUrl(engine, value),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
async closeAll() {
|
|
77
|
+
await Promise.all([...this.drivers.values()].map((driver) => driver.close().catch(() => { })));
|
|
78
|
+
this.drivers.clear();
|
|
79
|
+
}
|
|
80
|
+
async resolve(name) {
|
|
81
|
+
const config = await this.configStore.load();
|
|
82
|
+
const connection = config.databases.find((item) => item.name === name);
|
|
83
|
+
if (!connection) {
|
|
84
|
+
throw new Error(`Database connection "${name}" is not registered.`);
|
|
85
|
+
}
|
|
86
|
+
return connection;
|
|
87
|
+
}
|
|
88
|
+
async driverFor(connection) {
|
|
89
|
+
const key = `${connection.engine}::${connection.url}`;
|
|
90
|
+
let driver = this.drivers.get(key);
|
|
91
|
+
if (!driver) {
|
|
92
|
+
driver = createDriver(connection.engine, connection.url);
|
|
93
|
+
this.drivers.set(key, driver);
|
|
94
|
+
}
|
|
95
|
+
return driver;
|
|
96
|
+
}
|
|
97
|
+
/** Only browse tables that actually exist — blocks identifier injection. */
|
|
98
|
+
async resolveTable(driver, qualifiedName) {
|
|
99
|
+
const tables = await driver.listTables();
|
|
100
|
+
const table = tables.find((item) => item.qualifiedName === qualifiedName);
|
|
101
|
+
if (!table) {
|
|
102
|
+
throw new Error(`Table "${qualifiedName}" not found.`);
|
|
103
|
+
}
|
|
104
|
+
return table;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createDriver(engine, url) {
|
|
108
|
+
switch (engine) {
|
|
109
|
+
case "postgres":
|
|
110
|
+
return new PostgresDriver(url);
|
|
111
|
+
case "mysql":
|
|
112
|
+
return new MysqlDriver(url);
|
|
113
|
+
case "sqlite":
|
|
114
|
+
return new SqliteDriver(url);
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`Unsupported database engine: ${engine}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Guess an engine from a connection string or file path; null if unrecognized. */
|
|
120
|
+
export function engineFromUrl(value) {
|
|
121
|
+
const v = value.trim();
|
|
122
|
+
if (/^postgres(ql)?:\/\//i.test(v))
|
|
123
|
+
return "postgres";
|
|
124
|
+
if (/^mysql:\/\//i.test(v) || /^mariadb:\/\//i.test(v))
|
|
125
|
+
return "mysql";
|
|
126
|
+
if (/^sqlite:\/\//i.test(v) || /^file:.+\.(db|sqlite|sqlite3)/i.test(v)) {
|
|
127
|
+
return "sqlite";
|
|
128
|
+
}
|
|
129
|
+
if (/\.(db|sqlite|sqlite3)$/i.test(v))
|
|
130
|
+
return "sqlite";
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/** Redact the password from a URL; SQLite paths are returned unchanged. */
|
|
134
|
+
export function maskConnectionUrl(engine, url) {
|
|
135
|
+
if (engine === "sqlite")
|
|
136
|
+
return url;
|
|
137
|
+
try {
|
|
138
|
+
const parsed = new URL(url);
|
|
139
|
+
if (parsed.password)
|
|
140
|
+
parsed.password = "****";
|
|
141
|
+
return parsed.toString();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Not a parseable URL — mask the middle defensively.
|
|
145
|
+
if (url.length <= 8)
|
|
146
|
+
return "****";
|
|
147
|
+
return `${url.slice(0, 4)}****${url.slice(-4)}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=db-peek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-peek.js","sourceRoot":"","sources":["../../src/core/db-peek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAsBrD;;;;GAIG;AACH,MAAM,OAAO,MAAM;IACA,WAAW,CAAc;IACzB,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEvD,YAAY,OAAsB;QAChC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC7C,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG,EAAE,iBAAiB,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC;SAC1D,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,UAAU,CACd,IAAY,EACZ,aAAqB,EACrB,KAAa;QAEb,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC;IACzD,CAAC;IAED,qDAAqD;IACrD,KAAK,CAAC,IAAI,CAAC,MAAsB,EAAE,GAAW;QAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;QAChC,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAyB,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAE/B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,SAAS;YAC3B,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;YACvE,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrD,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;gBACpC,IAAI,CAAC,MAAM;oBAAE,SAAS;gBACtB,MAAM,MAAM,GAAG,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;gBACpC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;oBAAE,SAAS;gBAC/B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACjB,KAAK,CAAC,IAAI,CAAC;oBACT,OAAO,EAAE,OAAO,CAAC,IAAI;oBACrB,GAAG;oBACH,MAAM;oBACN,GAAG,EAAE,KAAK;oBACV,SAAS,EAAE,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC;iBAC5C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAC3E,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,IAAY;QAChC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACvE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,sBAAsB,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,UAA8B;QACpD,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,GAAG,EAAE,CAAC;QACtD,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;YACzD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,4EAA4E;IACpE,KAAK,CAAC,YAAY,CACxB,MAAgB,EAChB,aAAqB;QAErB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,KAAK,aAAa,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,aAAa,cAAc,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,SAAS,YAAY,CAAC,MAAsB,EAAE,GAAW;IACvD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC;QACjC,KAAK,OAAO;YACV,OAAO,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;QAC9B,KAAK,QAAQ;YACX,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;QAC/B;YACE,MAAM,IAAI,KAAK,CAAC,gCAAgC,MAAsB,EAAE,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IACtD,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IACvE,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB,CAAC,MAAsB,EAAE,GAAW;IACnE,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ;YAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;QAC9C,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;QACrD,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC;QACnC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LogEntry, LogQuery, LogSourceDefinition } from "./types.js";
|
|
2
|
+
export declare class LogSourceError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Reads a slice of a log source on demand. Never spawns a long-lived process —
|
|
6
|
+
* each call is a one-shot read, so it works for files written out-of-band
|
|
7
|
+
* (UAT/PROD) rather than streamed through a managed service.
|
|
8
|
+
*
|
|
9
|
+
* When the source has a `driver`, the query (time range, text, level) is pushed
|
|
10
|
+
* down to the host; otherwise it tails and filters client-side.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readLogSource(source: LogSourceDefinition, query?: LogQuery): Promise<LogEntry[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Builds the journalctl argv for a unit + query. JSON output carries real
|
|
15
|
+
* timestamps, priority, and cursors so we don't have to guess severity.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildJournaldArgs(unit: string, query: LogQuery, count: number): string[];
|
|
18
|
+
/** Builds the `docker logs` argv for a container + query (time window only). */
|
|
19
|
+
export declare function buildDockerArgs(container: string, query: LogQuery, count: number): string[];
|
|
20
|
+
/** Applies `grep`/`level` to already-fetched entries (for non-journald sources). */
|
|
21
|
+
export declare function applyClientFilters(entries: LogEntry[], query: LogQuery): LogEntry[];
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
const DEFAULT_LINES = 500;
|
|
6
|
+
const MAX_LINES = 5000;
|
|
7
|
+
const MAX_BUFFER = 8 * 1024 * 1024; // 8 MB ceiling on a single read
|
|
8
|
+
const READ_TIMEOUT_MS = 15_000;
|
|
9
|
+
/** Heuristic: lines that look like errors render red and survive the "errors only" filter. */
|
|
10
|
+
const ERROR_PATTERN = /\b(error|fatal|exception|panic|traceback|fail(?:ed|ure)?)\b/i;
|
|
11
|
+
const WARN_PATTERN = /\b(warn|warning|deprecated)\b/i;
|
|
12
|
+
export class LogSourceError extends Error {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads a slice of a log source on demand. Never spawns a long-lived process —
|
|
16
|
+
* each call is a one-shot read, so it works for files written out-of-band
|
|
17
|
+
* (UAT/PROD) rather than streamed through a managed service.
|
|
18
|
+
*
|
|
19
|
+
* When the source has a `driver`, the query (time range, text, level) is pushed
|
|
20
|
+
* down to the host; otherwise it tails and filters client-side.
|
|
21
|
+
*/
|
|
22
|
+
export async function readLogSource(source, query = {}) {
|
|
23
|
+
const count = clampLines(query.lines ?? DEFAULT_LINES);
|
|
24
|
+
if (source.driver === "journald") {
|
|
25
|
+
if (!source.unit)
|
|
26
|
+
throw new LogSourceError("journald log source is missing a unit.");
|
|
27
|
+
const argv = buildJournaldArgs(source.unit, query, count);
|
|
28
|
+
const { stdout } = await runInvocation(argv, source.host);
|
|
29
|
+
// journald filtered server-side already; just parse structured output.
|
|
30
|
+
let entries = parseJournaldJson(source.name, stdout);
|
|
31
|
+
if (query.before) {
|
|
32
|
+
// `--reverse` returned newest-first and includes the boundary cursor;
|
|
33
|
+
// restore chronological order and drop the (inclusive) boundary entry.
|
|
34
|
+
entries = entries.reverse().filter((entry) => entry.cursor !== query.before);
|
|
35
|
+
}
|
|
36
|
+
return entries;
|
|
37
|
+
}
|
|
38
|
+
if (source.driver === "docker") {
|
|
39
|
+
if (!source.container)
|
|
40
|
+
throw new LogSourceError("docker log source is missing a container.");
|
|
41
|
+
const argv = buildDockerArgs(source.container, query, count);
|
|
42
|
+
const { stdout, stderr } = await runInvocation(argv, source.host);
|
|
43
|
+
// docker logs has no native text/level filter, so apply those client-side.
|
|
44
|
+
const entries = [
|
|
45
|
+
...toEntries(source.name, stdout, "stdout"),
|
|
46
|
+
...toEntries(source.name, stderr, "stderr"),
|
|
47
|
+
];
|
|
48
|
+
return applyClientFilters(entries, query).slice(-count);
|
|
49
|
+
}
|
|
50
|
+
if (source.kind === "file") {
|
|
51
|
+
if (!source.path)
|
|
52
|
+
throw new LogSourceError("File log source is missing a path.");
|
|
53
|
+
return applyClientFilters(await tailLocalFile(source.name, source.path, count), query);
|
|
54
|
+
}
|
|
55
|
+
if (source.kind === "ssh") {
|
|
56
|
+
if (!source.host || !source.path) {
|
|
57
|
+
throw new LogSourceError("SSH log source is missing host or path.");
|
|
58
|
+
}
|
|
59
|
+
const { stdout } = await execFileAsync("ssh", [source.host, "tail", "-n", String(count), source.path], { maxBuffer: MAX_BUFFER, timeout: READ_TIMEOUT_MS });
|
|
60
|
+
return applyClientFilters(toEntries(source.name, stdout, "stdout"), query);
|
|
61
|
+
}
|
|
62
|
+
// command
|
|
63
|
+
if (!source.command)
|
|
64
|
+
throw new LogSourceError("Command log source is missing a command.");
|
|
65
|
+
const { stdout, stderr } = await execFileAsync("sh", ["-c", source.command], {
|
|
66
|
+
cwd: source.cwd,
|
|
67
|
+
maxBuffer: MAX_BUFFER,
|
|
68
|
+
timeout: READ_TIMEOUT_MS,
|
|
69
|
+
});
|
|
70
|
+
const entries = [
|
|
71
|
+
...toEntries(source.name, stdout, "stdout"),
|
|
72
|
+
...toEntries(source.name, stderr, "stderr"),
|
|
73
|
+
];
|
|
74
|
+
return applyClientFilters(entries, query).slice(-count);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Builds the journalctl argv for a unit + query. JSON output carries real
|
|
78
|
+
* timestamps, priority, and cursors so we don't have to guess severity.
|
|
79
|
+
*/
|
|
80
|
+
export function buildJournaldArgs(unit, query, count) {
|
|
81
|
+
const args = ["journalctl", "-u", unit, "--no-pager", "-o", "json"];
|
|
82
|
+
if (query.since)
|
|
83
|
+
args.push("--since", query.since);
|
|
84
|
+
if (query.until)
|
|
85
|
+
args.push("--until", query.until);
|
|
86
|
+
if (query.grep)
|
|
87
|
+
args.push("--grep", query.grep);
|
|
88
|
+
if (query.level === "error")
|
|
89
|
+
args.push("-p", "err");
|
|
90
|
+
else if (query.level === "warn")
|
|
91
|
+
args.push("-p", "warning");
|
|
92
|
+
if (query.before) {
|
|
93
|
+
// Page older: start at the cursor and walk backwards `count` lines.
|
|
94
|
+
args.push("--cursor", query.before, "--reverse", "-n", String(count));
|
|
95
|
+
}
|
|
96
|
+
else if (query.cursor) {
|
|
97
|
+
// Page newer: everything after the cursor.
|
|
98
|
+
args.push("--after-cursor", query.cursor, "-n", String(count));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
args.push("-n", String(count));
|
|
102
|
+
}
|
|
103
|
+
return args;
|
|
104
|
+
}
|
|
105
|
+
/** Builds the `docker logs` argv for a container + query (time window only). */
|
|
106
|
+
export function buildDockerArgs(container, query, count) {
|
|
107
|
+
const args = ["docker", "logs", "--tail", String(count), "--timestamps"];
|
|
108
|
+
if (query.since)
|
|
109
|
+
args.push("--since", query.since);
|
|
110
|
+
if (query.until)
|
|
111
|
+
args.push("--until", query.until);
|
|
112
|
+
args.push(container);
|
|
113
|
+
return args;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Runs an argv locally, or over ssh when `host` is set. For ssh the argv is
|
|
117
|
+
* escaped and joined into a single remote command so values with spaces
|
|
118
|
+
* (e.g. --since "1 hour ago") survive the remote shell.
|
|
119
|
+
*/
|
|
120
|
+
function runInvocation(argv, host) {
|
|
121
|
+
const [file, args] = host
|
|
122
|
+
? ["ssh", [host, argv.map(shellEscape).join(" ")]]
|
|
123
|
+
: [argv[0], argv.slice(1)];
|
|
124
|
+
return execFileAsync(file, args, { maxBuffer: MAX_BUFFER, timeout: READ_TIMEOUT_MS });
|
|
125
|
+
}
|
|
126
|
+
function shellEscape(value) {
|
|
127
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
128
|
+
}
|
|
129
|
+
/** Parses journalctl `-o json` lines into entries with real timestamps + levels. */
|
|
130
|
+
function parseJournaldJson(service, stdout) {
|
|
131
|
+
const entries = [];
|
|
132
|
+
for (const line of stdout.split("\n")) {
|
|
133
|
+
if (!line.trim())
|
|
134
|
+
continue;
|
|
135
|
+
let record;
|
|
136
|
+
try {
|
|
137
|
+
record = JSON.parse(line);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
continue; // tolerate the occasional non-JSON line
|
|
141
|
+
}
|
|
142
|
+
const text = decodeJournaldMessage(record.MESSAGE);
|
|
143
|
+
const priority = Number(record.PRIORITY);
|
|
144
|
+
// syslog priority: 0-3 = err/crit/alert/emerg → render as error (red).
|
|
145
|
+
const stream = Number.isFinite(priority) && priority <= 3 ? "stderr" : "stdout";
|
|
146
|
+
const micros = Number(record.__REALTIME_TIMESTAMP);
|
|
147
|
+
const timestamp = Number.isFinite(micros) ? new Date(micros / 1000).toISOString() : "";
|
|
148
|
+
const cursor = typeof record.__CURSOR === "string" ? record.__CURSOR : undefined;
|
|
149
|
+
entries.push({ service, stream, text, timestamp, cursor });
|
|
150
|
+
}
|
|
151
|
+
return entries;
|
|
152
|
+
}
|
|
153
|
+
/** journald MESSAGE is usually a string, but a byte array for non-UTF8 payloads. */
|
|
154
|
+
function decodeJournaldMessage(message) {
|
|
155
|
+
if (typeof message === "string")
|
|
156
|
+
return message;
|
|
157
|
+
if (Array.isArray(message)) {
|
|
158
|
+
return String.fromCharCode(...message.filter((b) => typeof b === "number"));
|
|
159
|
+
}
|
|
160
|
+
return message == null ? "" : String(message);
|
|
161
|
+
}
|
|
162
|
+
/** Applies `grep`/`level` to already-fetched entries (for non-journald sources). */
|
|
163
|
+
export function applyClientFilters(entries, query) {
|
|
164
|
+
let result = entries;
|
|
165
|
+
if (query.grep) {
|
|
166
|
+
const matcher = compileGrep(query.grep);
|
|
167
|
+
result = result.filter((entry) => matcher.test(entry.text));
|
|
168
|
+
}
|
|
169
|
+
if (query.level === "error") {
|
|
170
|
+
result = result.filter((entry) => entry.stream === "stderr" || ERROR_PATTERN.test(entry.text));
|
|
171
|
+
}
|
|
172
|
+
else if (query.level === "warn") {
|
|
173
|
+
result = result.filter((entry) => entry.stream === "stderr" || ERROR_PATTERN.test(entry.text) || WARN_PATTERN.test(entry.text));
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
/** Treats the term as a regex, falling back to a literal match if it's invalid. */
|
|
178
|
+
function compileGrep(term) {
|
|
179
|
+
try {
|
|
180
|
+
return new RegExp(term, "i");
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function tailLocalFile(name, path, count) {
|
|
187
|
+
let contents;
|
|
188
|
+
try {
|
|
189
|
+
contents = await readFile(path, "utf8");
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const code = error.code;
|
|
193
|
+
if (code === "ENOENT")
|
|
194
|
+
throw new LogSourceError(`File not found: ${path}`);
|
|
195
|
+
if (code === "EACCES")
|
|
196
|
+
throw new LogSourceError(`Permission denied: ${path}`);
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
return toEntries(name, contents, "stdout").slice(-count);
|
|
200
|
+
}
|
|
201
|
+
function toEntries(service, output, defaultStream) {
|
|
202
|
+
const text = output.replace(/\n$/, "");
|
|
203
|
+
if (!text)
|
|
204
|
+
return [];
|
|
205
|
+
return text.split("\n").map((line) => ({
|
|
206
|
+
service,
|
|
207
|
+
// File/ssh tails arrive as one stream; flag error-looking lines so they
|
|
208
|
+
// still render red and survive the "errors only" filter.
|
|
209
|
+
stream: defaultStream === "stdout" && ERROR_PATTERN.test(line) ? "stderr" : defaultStream,
|
|
210
|
+
text: line,
|
|
211
|
+
timestamp: "",
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
function clampLines(lines) {
|
|
215
|
+
if (!Number.isFinite(lines) || lines <= 0)
|
|
216
|
+
return DEFAULT_LINES;
|
|
217
|
+
return Math.min(Math.floor(lines), MAX_LINES);
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=log-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-sources.js","sourceRoot":"","sources":["../../src/core/log-sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LogSourceDefinition, ServiceDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Derives a queryable log source from a service that streams its logs over ssh
|
|
4
|
+
* (journald or docker). This lets a managed service's log view re-query the host
|
|
5
|
+
* for full history — search, time range, paging — instead of being limited to
|
|
6
|
+
* its live in-memory buffer. Returns null when the service has no remote,
|
|
7
|
+
* queryable backend (e.g. a local `npm run dev`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function deriveServiceLogSource(service: ServiceDefinition): LogSourceDefinition | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a queryable log source from a service that streams its logs over ssh
|
|
3
|
+
* (journald or docker). This lets a managed service's log view re-query the host
|
|
4
|
+
* for full history — search, time range, paging — instead of being limited to
|
|
5
|
+
* its live in-memory buffer. Returns null when the service has no remote,
|
|
6
|
+
* queryable backend (e.g. a local `npm run dev`).
|
|
7
|
+
*/
|
|
8
|
+
export function deriveServiceLogSource(service) {
|
|
9
|
+
if (service.kind !== "ssh" || !service.host || !service.command)
|
|
10
|
+
return null;
|
|
11
|
+
const unit = matchJournaldUnit(service.command);
|
|
12
|
+
if (unit) {
|
|
13
|
+
return { name: service.name, kind: "command", driver: "journald", unit, host: service.host };
|
|
14
|
+
}
|
|
15
|
+
const container = matchDockerContainer(service.command);
|
|
16
|
+
if (container) {
|
|
17
|
+
return { name: service.name, kind: "command", driver: "docker", container, host: service.host };
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
/** Pulls the unit out of a `journalctl … -u <unit>` / `--unit <unit>` command. */
|
|
22
|
+
function matchJournaldUnit(command) {
|
|
23
|
+
if (!/\bjournalctl\b/.test(command))
|
|
24
|
+
return null;
|
|
25
|
+
const match = command.match(/(?:-u|--unit)[=\s]+(\S+)/);
|
|
26
|
+
return match ? stripQuotes(match[1]) : null;
|
|
27
|
+
}
|
|
28
|
+
/** Pulls the container out of a `docker logs [flags] <container>` command. */
|
|
29
|
+
function matchDockerContainer(command) {
|
|
30
|
+
const parts = command.split(/\bdocker\s+logs\b/);
|
|
31
|
+
if (parts.length < 2)
|
|
32
|
+
return null;
|
|
33
|
+
const tokens = parts[1].trim().split(/\s+/).filter(Boolean);
|
|
34
|
+
const flagsTakingValue = new Set(["--tail", "--since", "--until", "-n"]);
|
|
35
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
36
|
+
const token = tokens[i];
|
|
37
|
+
if (token.startsWith("-")) {
|
|
38
|
+
if (flagsTakingValue.has(token))
|
|
39
|
+
i++; // skip the flag's value
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
return stripQuotes(token); // first bare token is the container
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function stripQuotes(value) {
|
|
47
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=service-log-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-log-source.js","sourceRoot":"","sources":["../../src/core/service-log-source.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA0B;IAC/D,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE7E,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/F,CAAC;IACD,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IAClG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,SAAS,iBAAiB,CAAC,OAAe;IACxC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACxD,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,SAAS,oBAAoB,CAAC,OAAe;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IACzE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,CAAC,EAAE,CAAC,CAAC,wBAAwB;YAC9D,SAAS;QACX,CAAC;QACD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,oCAAoC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type PtyAdapter, type TerminalSessionLike, type TerminalSize, type TerminalSnapshot } from "./terminal-session.js";
|
|
2
|
+
/** A session's snapshot plus the id the manager tracks it under. */
|
|
3
|
+
export interface TerminalSessionInfo extends TerminalSnapshot {
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
/** The subset of the manager the web layer depends on (so tests can fake it). */
|
|
7
|
+
export interface TerminalSessionManagerLike {
|
|
8
|
+
list(): TerminalSessionInfo[];
|
|
9
|
+
create(size?: Partial<TerminalSize>): TerminalSessionInfo;
|
|
10
|
+
get(id: string): TerminalSessionLike | undefined;
|
|
11
|
+
ensure(id: string, size?: Partial<TerminalSize>): TerminalSessionLike;
|
|
12
|
+
close(id: string): boolean;
|
|
13
|
+
disposeAll(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface TerminalSessionManagerOptions {
|
|
16
|
+
cwd: string;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
shell?: string;
|
|
19
|
+
/** Injectable per-session adapter factory; defaults to the real node-pty adapter. */
|
|
20
|
+
adapterFactory?: () => PtyAdapter | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Owns the live terminal sessions keyed by id so the web UI can run several
|
|
24
|
+
* tabs at once. The PTY lifecycle stays in {@link TerminalSession}; this layer
|
|
25
|
+
* only adds the keyed map, id generation, and fan-out helpers.
|
|
26
|
+
*/
|
|
27
|
+
export declare class TerminalSessionManager implements TerminalSessionManagerLike {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private readonly sessions;
|
|
30
|
+
private counter;
|
|
31
|
+
constructor(options: TerminalSessionManagerOptions);
|
|
32
|
+
list(): TerminalSessionInfo[];
|
|
33
|
+
create(size?: Partial<TerminalSize>): TerminalSessionInfo;
|
|
34
|
+
get(id: string): TerminalSessionLike | undefined;
|
|
35
|
+
/** Returns the session for `id`, creating and starting one if it is unknown. */
|
|
36
|
+
ensure(id: string, size?: Partial<TerminalSize>): TerminalSessionLike;
|
|
37
|
+
close(id: string): boolean;
|
|
38
|
+
disposeAll(): void;
|
|
39
|
+
private spawn;
|
|
40
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { TerminalSession, } from "./terminal-session.js";
|
|
2
|
+
/**
|
|
3
|
+
* Owns the live terminal sessions keyed by id so the web UI can run several
|
|
4
|
+
* tabs at once. The PTY lifecycle stays in {@link TerminalSession}; this layer
|
|
5
|
+
* only adds the keyed map, id generation, and fan-out helpers.
|
|
6
|
+
*/
|
|
7
|
+
export class TerminalSessionManager {
|
|
8
|
+
options;
|
|
9
|
+
sessions = new Map();
|
|
10
|
+
counter = 0;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
list() {
|
|
15
|
+
return [...this.sessions.entries()].map(([id, session]) => ({
|
|
16
|
+
id,
|
|
17
|
+
...session.snapshot(),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
create(size = {}) {
|
|
21
|
+
const id = `term_${++this.counter}`;
|
|
22
|
+
const session = this.spawn(size);
|
|
23
|
+
this.sessions.set(id, session);
|
|
24
|
+
return { id, ...session.snapshot() };
|
|
25
|
+
}
|
|
26
|
+
get(id) {
|
|
27
|
+
return this.sessions.get(id);
|
|
28
|
+
}
|
|
29
|
+
/** Returns the session for `id`, creating and starting one if it is unknown. */
|
|
30
|
+
ensure(id, size = {}) {
|
|
31
|
+
const existing = this.sessions.get(id);
|
|
32
|
+
if (existing) {
|
|
33
|
+
existing.start(size);
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
const session = this.spawn(size);
|
|
37
|
+
this.sessions.set(id, session);
|
|
38
|
+
return session;
|
|
39
|
+
}
|
|
40
|
+
close(id) {
|
|
41
|
+
const session = this.sessions.get(id);
|
|
42
|
+
if (!session)
|
|
43
|
+
return false;
|
|
44
|
+
session.dispose();
|
|
45
|
+
this.sessions.delete(id);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
disposeAll() {
|
|
49
|
+
for (const session of this.sessions.values())
|
|
50
|
+
session.dispose();
|
|
51
|
+
this.sessions.clear();
|
|
52
|
+
}
|
|
53
|
+
spawn(size) {
|
|
54
|
+
const session = new TerminalSession({
|
|
55
|
+
adapter: this.options.adapterFactory?.(),
|
|
56
|
+
cwd: this.options.cwd,
|
|
57
|
+
env: this.options.env,
|
|
58
|
+
shell: this.options.shell,
|
|
59
|
+
});
|
|
60
|
+
session.start(size);
|
|
61
|
+
return session;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=terminal-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-manager.js","sourceRoot":"","sources":["../../src/core/terminal-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,GAIhB,MAAM,uBAAuB,CAAC;AAyB/B;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAChB,OAAO,CAAgC;IACvC,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC3D,OAAO,GAAG,CAAC,CAAC;IAEpB,YAAY,OAAsC;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1D,EAAE;YACF,GAAG,OAAO,CAAC,QAAQ,EAAE;SACtB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,MAAM,CAAC,OAA8B,EAAE;QACrC,MAAM,EAAE,GAAG,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;IACvC,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,gFAAgF;IAChF,MAAM,CAAC,EAAU,EAAE,OAA8B,EAAE;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAChE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,IAA2B;QACvC,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC;YAClC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE;YACxC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;YACrB,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;YACrB,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;SAC1B,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
|