kontexted 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/README.md +75 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +48 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +33 -0
- package/dist/commands/mcp.d.ts +15 -0
- package/dist/commands/mcp.js +65 -0
- package/dist/commands/server/doctor.d.ts +4 -0
- package/dist/commands/server/doctor.js +33 -0
- package/dist/commands/server/index.d.ts +6 -0
- package/dist/commands/server/index.js +100 -0
- package/dist/commands/server/init.d.ts +6 -0
- package/dist/commands/server/init.js +81 -0
- package/dist/commands/server/logs.d.ts +7 -0
- package/dist/commands/server/logs.js +39 -0
- package/dist/commands/server/start.d.ts +6 -0
- package/dist/commands/server/start.js +44 -0
- package/dist/commands/server/status.d.ts +4 -0
- package/dist/commands/server/status.js +23 -0
- package/dist/commands/server/stop.d.ts +6 -0
- package/dist/commands/server/stop.js +32 -0
- package/dist/commands/show-config.d.ts +5 -0
- package/dist/commands/show-config.js +19 -0
- package/dist/commands/skill.d.ts +5 -0
- package/dist/commands/skill.js +211 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25 -0
- package/dist/lib/api-client.d.ts +36 -0
- package/dist/lib/api-client.js +130 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/logger.d.ts +24 -0
- package/dist/lib/logger.js +76 -0
- package/dist/lib/mcp-client.d.ts +14 -0
- package/dist/lib/mcp-client.js +62 -0
- package/dist/lib/oauth.d.ts +42 -0
- package/dist/lib/oauth.js +383 -0
- package/dist/lib/profile.d.ts +37 -0
- package/dist/lib/profile.js +49 -0
- package/dist/lib/proxy-server.d.ts +12 -0
- package/dist/lib/proxy-server.js +131 -0
- package/dist/lib/server/binary.d.ts +27 -0
- package/dist/lib/server/binary.js +100 -0
- package/dist/lib/server/config.d.ts +45 -0
- package/dist/lib/server/config.js +97 -0
- package/dist/lib/server/constants.d.ts +29 -0
- package/dist/lib/server/constants.js +36 -0
- package/dist/lib/server/daemon.d.ts +34 -0
- package/dist/lib/server/daemon.js +200 -0
- package/dist/lib/server/index.d.ts +4 -0
- package/dist/lib/server/index.js +4 -0
- package/dist/lib/server-url.d.ts +8 -0
- package/dist/lib/server-url.js +25 -0
- package/dist/skill-init/index.d.ts +3 -0
- package/dist/skill-init/index.js +3 -0
- package/dist/skill-init/providers/base.d.ts +26 -0
- package/dist/skill-init/providers/base.js +1 -0
- package/dist/skill-init/providers/index.d.ts +13 -0
- package/dist/skill-init/providers/index.js +17 -0
- package/dist/skill-init/providers/opencode.d.ts +5 -0
- package/dist/skill-init/providers/opencode.js +48 -0
- package/dist/skill-init/templates/index.d.ts +3 -0
- package/dist/skill-init/templates/index.js +3 -0
- package/dist/skill-init/templates/kontexted-cli.d.ts +2 -0
- package/dist/skill-init/templates/kontexted-cli.js +169 -0
- package/dist/skill-init/utils.d.ts +66 -0
- package/dist/skill-init/utils.js +122 -0
- package/dist/types/index.d.ts +67 -0
- package/dist/types/index.js +4 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Kontexted CLI
|
|
2
|
+
|
|
3
|
+
A command-line tool for Kontexted that provides MCP proxy functionality and workspace management.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g kontexted
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Authentication
|
|
14
|
+
|
|
15
|
+
Log in to a Kontexted server:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
kontexted login --url https://app.example.com --workspace my-workspace --alias prod
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
With an alias and write permissions:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
kontexted login --url https://app.example.com --workspace my-workspace --alias prod --write
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### MCP Proxy Mode
|
|
28
|
+
|
|
29
|
+
Start the MCP proxy server (for use with Claude Desktop or other MCP clients):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Using alias
|
|
33
|
+
kontexted --alias prod
|
|
34
|
+
|
|
35
|
+
# Using workspace name
|
|
36
|
+
kontexted --workspace my-workspace
|
|
37
|
+
|
|
38
|
+
# Enable write mode for this session
|
|
39
|
+
kontexted --alias prod --write
|
|
40
|
+
|
|
41
|
+
# Disable write mode for this session
|
|
42
|
+
kontexted --alias prod --write-off
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Manage Profiles
|
|
46
|
+
|
|
47
|
+
Show stored profiles:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
kontexted show-config
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Remove a profile:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
kontexted logout --alias prod
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Remove all profiles:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
kontexted logout
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
Profiles are stored in `~/.kontexted/profile.json` with OAuth tokens.
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- Node.js 18 or higher
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readConfig, writeConfig } from "../lib/config.js";
|
|
2
|
+
import { addProfile } from "../lib/profile.js";
|
|
3
|
+
import { resolveServerUrl } from "../lib/server-url.js";
|
|
4
|
+
import { createOAuthProvider, waitForOAuthCallback } from "../lib/oauth.js";
|
|
5
|
+
import { logDebug } from "../lib/logger.js";
|
|
6
|
+
export function registerLoginCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("login")
|
|
9
|
+
.description("Authenticate and store a new profile")
|
|
10
|
+
.requiredOption("--url <url>", "Kontexted server URL (e.g. https://app.example.com or app.example.com)")
|
|
11
|
+
.requiredOption("--alias <name>", "Profile alias")
|
|
12
|
+
.requiredOption("--workspace <slug>", "Workspace slug")
|
|
13
|
+
.option("--write", "Enable write operations for this profile by default", false)
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const baseUrl = resolveServerUrl(options.url);
|
|
16
|
+
logDebug(`[LOGIN] Starting login flow - URL: ${baseUrl}, Alias: ${options.alias}, Workspace: ${options.workspace}`);
|
|
17
|
+
const config = await readConfig();
|
|
18
|
+
const oauth = {};
|
|
19
|
+
const persist = async () => {
|
|
20
|
+
await writeConfig(config);
|
|
21
|
+
};
|
|
22
|
+
// Create OAuth provider
|
|
23
|
+
const provider = createOAuthProvider(oauth, persist, baseUrl);
|
|
24
|
+
// 1. Register OAuth client
|
|
25
|
+
logDebug(`[LOGIN] Registering OAuth client...`);
|
|
26
|
+
await provider.registerClient();
|
|
27
|
+
// 2. Get authorization URL and open browser
|
|
28
|
+
const authUrl = await provider.getAuthorizationUrl();
|
|
29
|
+
logDebug(`[LOGIN] Opening browser for authorization...`);
|
|
30
|
+
provider.redirectToAuthorization(authUrl);
|
|
31
|
+
// 3. Wait for callback with auth code
|
|
32
|
+
logDebug(`[LOGIN] Waiting for authorization callback...`);
|
|
33
|
+
const authCode = await waitForOAuthCallback();
|
|
34
|
+
// 4. Exchange code for tokens
|
|
35
|
+
logDebug(`[LOGIN] Exchanging authorization code for tokens...`);
|
|
36
|
+
await provider.exchangeCodeForToken(authCode);
|
|
37
|
+
// Store the profile
|
|
38
|
+
const profile = {
|
|
39
|
+
serverUrl: baseUrl,
|
|
40
|
+
workspace: options.workspace,
|
|
41
|
+
write: options.write ?? false,
|
|
42
|
+
oauth,
|
|
43
|
+
};
|
|
44
|
+
addProfile(config, options.alias, profile);
|
|
45
|
+
await writeConfig(config);
|
|
46
|
+
console.log(`✓ Login successful. Profile stored as: ${options.alias}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readConfig, writeConfig, removeConfig } from "../lib/config.js";
|
|
2
|
+
import { profileExists, removeProfile } from "../lib/profile.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register the logout command
|
|
5
|
+
*/
|
|
6
|
+
export function registerLogoutCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("logout")
|
|
9
|
+
.description("Remove stored profiles")
|
|
10
|
+
.option("--alias <name>", "Remove specific profile by alias")
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const config = await readConfig();
|
|
13
|
+
// No alias specified: remove all profiles
|
|
14
|
+
if (!options.alias) {
|
|
15
|
+
await removeConfig();
|
|
16
|
+
console.log("✓ Removed all profiles.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const profileKey = options.alias;
|
|
20
|
+
if (!profileExists(config, profileKey)) {
|
|
21
|
+
throw new Error(`Profile not found: ${profileKey}`);
|
|
22
|
+
}
|
|
23
|
+
removeProfile(config, profileKey);
|
|
24
|
+
if (Object.keys(config.profiles).length === 0) {
|
|
25
|
+
await removeConfig();
|
|
26
|
+
console.log("✓ Removed last profile. Config file removed.");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await writeConfig(config);
|
|
30
|
+
console.log(`✓ Removed profile: ${profileKey}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
interface McpOptions {
|
|
3
|
+
alias?: string;
|
|
4
|
+
write?: boolean;
|
|
5
|
+
writeOff?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Start the MCP proxy server
|
|
9
|
+
*/
|
|
10
|
+
declare function startMcpProxy(options: McpOptions): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Register the MCP proxy command
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerMcpCommand(program: Command): void;
|
|
15
|
+
export { startMcpProxy };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readConfig, writeConfig } from "../lib/config.js";
|
|
2
|
+
import { getProfile } from "../lib/profile.js";
|
|
3
|
+
import { connectRemoteClient, UnauthorizedError } from "../lib/mcp-client.js";
|
|
4
|
+
import { startProxyServer } from "../lib/proxy-server.js";
|
|
5
|
+
/**
|
|
6
|
+
* Start the MCP proxy server
|
|
7
|
+
*/
|
|
8
|
+
async function startMcpProxy(options) {
|
|
9
|
+
const config = await readConfig();
|
|
10
|
+
const profileKey = options.alias;
|
|
11
|
+
if (!profileKey) {
|
|
12
|
+
throw new Error("Missing --alias. Required for MCP proxy mode.\n" +
|
|
13
|
+
"Run 'kontexted mcp --help' for usage information.");
|
|
14
|
+
}
|
|
15
|
+
if (options.write && options.writeOff) {
|
|
16
|
+
throw new Error("Cannot specify both --write and --write-off");
|
|
17
|
+
}
|
|
18
|
+
const profile = getProfile(config, profileKey);
|
|
19
|
+
if (!profile) {
|
|
20
|
+
throw new Error(`Profile not found: ${profileKey}. Run 'kontexted login' first.`);
|
|
21
|
+
}
|
|
22
|
+
// Determine write mode
|
|
23
|
+
let writeEnabled = profile.write ?? false;
|
|
24
|
+
if (options.write) {
|
|
25
|
+
writeEnabled = true;
|
|
26
|
+
}
|
|
27
|
+
else if (options.writeOff) {
|
|
28
|
+
writeEnabled = false;
|
|
29
|
+
}
|
|
30
|
+
const persist = async () => {
|
|
31
|
+
await writeConfig(config);
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
const { client } = await connectRemoteClient(profile.serverUrl, profile.oauth, persist, { allowInteractive: false });
|
|
35
|
+
const toolList = await client.listTools();
|
|
36
|
+
await startProxyServer({
|
|
37
|
+
client,
|
|
38
|
+
workspaceSlug: profile.workspace,
|
|
39
|
+
tools: toolList.tools,
|
|
40
|
+
writeEnabled,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof UnauthorizedError) {
|
|
45
|
+
console.error("Error: Unauthorized. Run 'kontexted login' first.");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register the MCP proxy command
|
|
53
|
+
*/
|
|
54
|
+
export function registerMcpCommand(program) {
|
|
55
|
+
program
|
|
56
|
+
.command("mcp")
|
|
57
|
+
.description("Start MCP proxy server")
|
|
58
|
+
.requiredOption("--alias <name>", "Profile alias to use")
|
|
59
|
+
.option("--write", "Override to enable write operations")
|
|
60
|
+
.option("--write-off", "Override to disable write operations")
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
await startMcpProxy(options);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export { startMcpProxy };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { isPlatformSupported, getPlatform, getBinaryPath, configExists, loadConfig, getServerStatus, } from '../../lib/server/index.js';
|
|
3
|
+
import { CONFIG_FILE, DATA_DIR } from '../../lib/server/constants.js';
|
|
4
|
+
const DOCKER_URL = 'https://hub.docker.com/r/kontexted/kontexted';
|
|
5
|
+
// ============ Yargs Command Module ============
|
|
6
|
+
export const command = 'doctor';
|
|
7
|
+
export const desc = 'Run diagnostic checks';
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export const builder = (yargs) => yargs;
|
|
10
|
+
export const handler = async () => {
|
|
11
|
+
console.log('--- Server Diagnostics ---\n');
|
|
12
|
+
const platform = getPlatform();
|
|
13
|
+
const platformOk = isPlatformSupported();
|
|
14
|
+
console.log(`${platformOk ? '✓' : '✗'} Platform: ${platform} ${platformOk ? '(supported)' : '(not supported)'}`);
|
|
15
|
+
if (!platformOk)
|
|
16
|
+
console.log(` → Consider using Docker: ${DOCKER_URL}`);
|
|
17
|
+
const binaryPath = getBinaryPath();
|
|
18
|
+
console.log(`${binaryPath ? '✓' : '✗'} Binary: ${binaryPath || 'not found'}`);
|
|
19
|
+
if (!binaryPath)
|
|
20
|
+
console.log(' → Reinstall @kontexted/cli');
|
|
21
|
+
const configOk = configExists();
|
|
22
|
+
console.log(`${configOk ? '✓' : '✗'} Config: ${configOk ? CONFIG_FILE : 'not found'}`);
|
|
23
|
+
if (!configOk)
|
|
24
|
+
console.log(' → Run: kontexted server init');
|
|
25
|
+
if (configOk) {
|
|
26
|
+
const config = loadConfig();
|
|
27
|
+
const dbPath = config?.database?.url || `${DATA_DIR}/kontexted.db`;
|
|
28
|
+
const dbOk = existsSync(dbPath);
|
|
29
|
+
console.log(`${dbOk ? '✓' : '⚠'} Database: ${dbPath}`);
|
|
30
|
+
}
|
|
31
|
+
const serverStatus = getServerStatus();
|
|
32
|
+
console.log(`Server: ${serverStatus.running ? `Running (PID: ${serverStatus.pid})` : 'Not running'}`);
|
|
33
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export declare const command = "server";
|
|
3
|
+
export declare const desc = "Manage Kontexted server";
|
|
4
|
+
export declare const builder: (yargs: any) => any;
|
|
5
|
+
export declare const handler: () => void;
|
|
6
|
+
export declare function registerServerCommand(program: Command): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import * as initCmd from './init.js';
|
|
3
|
+
import * as startCmd from './start.js';
|
|
4
|
+
import * as stopCmd from './stop.js';
|
|
5
|
+
import * as statusCmd from './status.js';
|
|
6
|
+
import * as logsCmd from './logs.js';
|
|
7
|
+
import * as doctorCmd from './doctor.js';
|
|
8
|
+
// Yargs-style exports (as requested by user)
|
|
9
|
+
export const command = 'server';
|
|
10
|
+
export const desc = 'Manage Kontexted server';
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
export const builder = (yargs) => {
|
|
13
|
+
return yargs
|
|
14
|
+
.command(initCmd)
|
|
15
|
+
.command(startCmd)
|
|
16
|
+
.command(stopCmd)
|
|
17
|
+
.command(statusCmd)
|
|
18
|
+
.command(logsCmd)
|
|
19
|
+
.command(doctorCmd)
|
|
20
|
+
.demandCommand()
|
|
21
|
+
.help();
|
|
22
|
+
};
|
|
23
|
+
export const handler = () => { };
|
|
24
|
+
// ============ Register with Commander ============
|
|
25
|
+
export function registerServerCommand(program) {
|
|
26
|
+
const serverCmd = program.command('server').description('Manage Kontexted server');
|
|
27
|
+
// init
|
|
28
|
+
serverCmd
|
|
29
|
+
.command('init')
|
|
30
|
+
.description(initCmd.desc)
|
|
31
|
+
.option('-i, --interactive', 'Interactive mode to customize configuration')
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
await yargs(['init'])
|
|
35
|
+
.command(initCmd)
|
|
36
|
+
.option('interactive', { type: 'boolean', default: false })
|
|
37
|
+
.parse();
|
|
38
|
+
});
|
|
39
|
+
// start
|
|
40
|
+
serverCmd
|
|
41
|
+
.command('start')
|
|
42
|
+
.description(startCmd.desc)
|
|
43
|
+
.option('-f, --foreground', 'Run server in foreground (blocking)')
|
|
44
|
+
.action(async (opts) => {
|
|
45
|
+
const args = opts.foreground ? ['start', '--foreground'] : ['start'];
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
await yargs(args)
|
|
48
|
+
.command(startCmd)
|
|
49
|
+
.parse();
|
|
50
|
+
});
|
|
51
|
+
// stop
|
|
52
|
+
serverCmd
|
|
53
|
+
.command('stop')
|
|
54
|
+
.description(stopCmd.desc)
|
|
55
|
+
.option('--force', 'Force kill the server (SIGKILL)')
|
|
56
|
+
.action(async (opts) => {
|
|
57
|
+
const args = opts.force ? ['stop', '--force'] : ['stop'];
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
await yargs(args)
|
|
60
|
+
.command(stopCmd)
|
|
61
|
+
.parse();
|
|
62
|
+
});
|
|
63
|
+
// status
|
|
64
|
+
serverCmd
|
|
65
|
+
.command('status')
|
|
66
|
+
.description(statusCmd.desc)
|
|
67
|
+
.action(async () => {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
await yargs(['status'])
|
|
70
|
+
.command(statusCmd)
|
|
71
|
+
.parse();
|
|
72
|
+
});
|
|
73
|
+
// logs
|
|
74
|
+
serverCmd
|
|
75
|
+
.command('logs')
|
|
76
|
+
.description(logsCmd.desc)
|
|
77
|
+
.option('-f, --follow', 'Follow log output in real-time')
|
|
78
|
+
.option('-n, --lines <number>', 'Number of lines to show', '50')
|
|
79
|
+
.action(async (opts) => {
|
|
80
|
+
const args = ['logs'];
|
|
81
|
+
if (opts.follow)
|
|
82
|
+
args.push('--follow');
|
|
83
|
+
if (opts.lines)
|
|
84
|
+
args.push('--lines', opts.lines.toString());
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
await yargs(args)
|
|
87
|
+
.command(logsCmd)
|
|
88
|
+
.parse();
|
|
89
|
+
});
|
|
90
|
+
// doctor
|
|
91
|
+
serverCmd
|
|
92
|
+
.command('doctor')
|
|
93
|
+
.description(doctorCmd.desc)
|
|
94
|
+
.action(async () => {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
await yargs(['doctor'])
|
|
97
|
+
.command(doctorCmd)
|
|
98
|
+
.parse();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { configExists, getDefaultConfig, saveConfig, } from '../../lib/server/index.js';
|
|
2
|
+
import { CONFIG_FILE, DATA_DIR } from '../../lib/server/constants.js';
|
|
3
|
+
import * as readline from 'readline';
|
|
4
|
+
// ============ Helper Functions ============
|
|
5
|
+
function createPrompt() {
|
|
6
|
+
return readline.createInterface({
|
|
7
|
+
input: process.stdin,
|
|
8
|
+
output: process.stdout,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function promptQuestion(rl, question) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
rl.question(question, (answer) => resolve(answer));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function runInit(interactive) {
|
|
17
|
+
if (interactive) {
|
|
18
|
+
const rl = createPrompt();
|
|
19
|
+
try {
|
|
20
|
+
if (configExists()) {
|
|
21
|
+
const answer = await promptQuestion(rl, 'Config already exists. Overwrite? (y/N): ');
|
|
22
|
+
if (answer.trim().toLowerCase() !== 'y') {
|
|
23
|
+
console.log('Initialization cancelled.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log('\n--- Server Configuration ---');
|
|
28
|
+
const dialectAnswer = await promptQuestion(rl, 'Database dialect (sqlite/postgresql) [sqlite]: ');
|
|
29
|
+
const dialect = (dialectAnswer.trim().toLowerCase() || 'sqlite');
|
|
30
|
+
if (dialect !== 'sqlite' && dialect !== 'postgresql') {
|
|
31
|
+
console.log('Invalid dialect. Using sqlite.');
|
|
32
|
+
}
|
|
33
|
+
const defaultUrl = dialect === 'sqlite' ? `${DATA_DIR}/kontexted.db` : 'postgresql://localhost:5432/kontexted';
|
|
34
|
+
const urlAnswer = await promptQuestion(rl, `Database URL [${defaultUrl}]: `);
|
|
35
|
+
const databaseUrl = urlAnswer.trim() || defaultUrl;
|
|
36
|
+
const portAnswer = await promptQuestion(rl, 'Server port [3000]: ');
|
|
37
|
+
const port = parseInt(portAnswer.trim(), 10) || 3000;
|
|
38
|
+
const hostAnswer = await promptQuestion(rl, 'Server host [127.0.0.1]: ');
|
|
39
|
+
const host = hostAnswer.trim() || '127.0.0.1';
|
|
40
|
+
const levelAnswer = await promptQuestion(rl, 'Log level (debug/info/warn/error) [info]: ');
|
|
41
|
+
const level = (levelAnswer.trim().toLowerCase() || 'info');
|
|
42
|
+
const config = {
|
|
43
|
+
database: { dialect, url: databaseUrl },
|
|
44
|
+
server: { port, host },
|
|
45
|
+
logging: { level },
|
|
46
|
+
collab: { tokenSecret: getDefaultConfig().collab.tokenSecret },
|
|
47
|
+
};
|
|
48
|
+
saveConfig(config);
|
|
49
|
+
console.log('\n✓ Configuration saved to:', CONFIG_FILE);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
rl.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (configExists()) {
|
|
57
|
+
console.error('Error: Configuration already exists.');
|
|
58
|
+
console.log(' Run with --interactive to reinitialize.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const config = getDefaultConfig();
|
|
62
|
+
saveConfig(config);
|
|
63
|
+
console.log('✓ Configuration created:', CONFIG_FILE);
|
|
64
|
+
console.log(' Database:', config.database.url);
|
|
65
|
+
console.log(' Server:', `${config.server.host}:${config.server.port}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ============ Yargs Command Module ============
|
|
69
|
+
export const command = 'init';
|
|
70
|
+
export const desc = 'Initialize server configuration';
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
export const builder = (yargs) => {
|
|
73
|
+
return yargs.option('interactive', {
|
|
74
|
+
alias: 'i',
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description: 'Interactive mode to customize configuration',
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
export const handler = async (argv) => {
|
|
80
|
+
await runInit(argv.interactive ?? false);
|
|
81
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { LOG_FILE } from '../../lib/server/constants.js';
|
|
4
|
+
// ============ Yargs Command Module ============
|
|
5
|
+
export const command = 'logs';
|
|
6
|
+
export const desc = 'View server logs';
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
export const builder = (yargs) => {
|
|
9
|
+
return yargs
|
|
10
|
+
.option('follow', {
|
|
11
|
+
alias: 'f',
|
|
12
|
+
type: 'boolean',
|
|
13
|
+
description: 'Follow log output in real-time',
|
|
14
|
+
})
|
|
15
|
+
.option('lines', {
|
|
16
|
+
alias: 'n',
|
|
17
|
+
type: 'number',
|
|
18
|
+
description: 'Number of lines to show',
|
|
19
|
+
default: 50,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
export const handler = async (argv) => {
|
|
23
|
+
if (!existsSync(LOG_FILE)) {
|
|
24
|
+
console.log('No logs available');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (argv.follow) {
|
|
28
|
+
console.log(`Following ${LOG_FILE}... (Ctrl+C to exit)`);
|
|
29
|
+
const tail = spawn('tail', ['-f', LOG_FILE]);
|
|
30
|
+
tail.stdout.on('data', (data) => process.stdout.write(data));
|
|
31
|
+
tail.stderr.on('data', (data) => process.stderr.write(data));
|
|
32
|
+
process.on('SIGINT', () => { tail.kill(); process.exit(0); });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const lines = argv.lines ?? 50;
|
|
36
|
+
const tail = spawn('tail', ['-n', lines.toString(), LOG_FILE]);
|
|
37
|
+
tail.stdout.on('data', (data) => process.stdout.write(data));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isPlatformSupported, getPlatform, getBinaryPath, configExists, getServerStatus, startServer, } from '../../lib/server/index.js';
|
|
2
|
+
const DOCKER_URL = 'https://hub.docker.com/r/kontexted/kontexted';
|
|
3
|
+
function checkPrerequisites() {
|
|
4
|
+
if (!isPlatformSupported()) {
|
|
5
|
+
return { valid: false, error: `Platform not supported: ${getPlatform()}. Consider using Docker: ${DOCKER_URL}` };
|
|
6
|
+
}
|
|
7
|
+
if (!getBinaryPath()) {
|
|
8
|
+
return { valid: false, error: 'Server binary not found. Run `kontexted server install` first.' };
|
|
9
|
+
}
|
|
10
|
+
if (!configExists()) {
|
|
11
|
+
return { valid: false, error: 'Configuration not found. Run `kontexted server init` first.' };
|
|
12
|
+
}
|
|
13
|
+
const status = getServerStatus();
|
|
14
|
+
if (status.running && status.pid) {
|
|
15
|
+
return { valid: false, error: `Server is already running (PID: ${status.pid})` };
|
|
16
|
+
}
|
|
17
|
+
return { valid: true };
|
|
18
|
+
}
|
|
19
|
+
// ============ Yargs Command Module ============
|
|
20
|
+
export const command = 'start';
|
|
21
|
+
export const desc = 'Start the Kontexted server';
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
export const builder = (yargs) => {
|
|
24
|
+
return yargs.option('foreground', {
|
|
25
|
+
alias: 'f',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
description: 'Run server in foreground (blocking)',
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
export const handler = async (argv) => {
|
|
31
|
+
const check = checkPrerequisites();
|
|
32
|
+
if (!check.valid) {
|
|
33
|
+
console.error('Error:', check.error);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const pid = await startServer({ foreground: argv.foreground });
|
|
38
|
+
console.log(argv.foreground ? `Server running (PID: ${pid})` : `Server started (PID: ${pid})`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getServerStatus, loadConfig } from '../../lib/server/index.js';
|
|
2
|
+
import { CONFIG_FILE, LOG_FILE } from '../../lib/server/constants.js';
|
|
3
|
+
// ============ Yargs Command Module ============
|
|
4
|
+
export const command = 'status';
|
|
5
|
+
export const desc = 'Show server status';
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
export const builder = (yargs) => yargs;
|
|
8
|
+
export const handler = async () => {
|
|
9
|
+
const status = getServerStatus();
|
|
10
|
+
if (!status.running) {
|
|
11
|
+
console.log('Server Status: Not running');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
console.log('Server Status: Running');
|
|
16
|
+
console.log(` PID: ${status.pid}`);
|
|
17
|
+
if (config) {
|
|
18
|
+
console.log(` Port: ${config.server.port}`);
|
|
19
|
+
console.log(` Host: ${config.server.host}`);
|
|
20
|
+
}
|
|
21
|
+
console.log(` Config: ${CONFIG_FILE}`);
|
|
22
|
+
console.log(` Logs: ${LOG_FILE}`);
|
|
23
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getServerStatus, stopServer } from '../../lib/server/index.js';
|
|
2
|
+
// ============ Yargs Command Module ============
|
|
3
|
+
export const command = 'stop';
|
|
4
|
+
export const desc = 'Stop the Kontexted server';
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
export const builder = (yargs) => {
|
|
7
|
+
return yargs.option('force', {
|
|
8
|
+
type: 'boolean',
|
|
9
|
+
description: 'Force kill the server (SIGKILL)',
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
export const handler = async (argv) => {
|
|
13
|
+
const status = getServerStatus();
|
|
14
|
+
if (!status.running) {
|
|
15
|
+
console.log('Server is not running');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const stopped = await stopServer({ force: argv.force ?? false });
|
|
20
|
+
if (stopped) {
|
|
21
|
+
console.log('Server stopped');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.error('Error: Failed to stop server');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
};
|