symmetry-cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WsServer = void 0;
4
+ const http_1 = require("http");
5
+ const ws_1 = require("ws");
6
+ class WsServer {
7
+ constructor(port, peerRepository, swarm) {
8
+ this.wssListeners = () => {
9
+ var _a;
10
+ (_a = this._wss) === null || _a === void 0 ? void 0 : _a.on("connection", (ws) => {
11
+ this.sendStats(ws);
12
+ const interval = setInterval(() => this.sendStats(ws), 5000);
13
+ ws.on("close", () => {
14
+ clearInterval(interval);
15
+ });
16
+ });
17
+ };
18
+ this._peerRepository = peerRepository;
19
+ this._swarm = swarm;
20
+ this.start(port);
21
+ }
22
+ start(port) {
23
+ const server = (0, http_1.createServer)();
24
+ this._wss = new ws_1.WebSocketServer({ server });
25
+ server.listen(port);
26
+ this.wssListeners();
27
+ }
28
+ async sendStats(ws) {
29
+ const stats = await this.getStats(this._swarm);
30
+ ws.send(JSON.stringify(stats));
31
+ }
32
+ async broadcastStats() {
33
+ var _a;
34
+ const stats = await this.getStats(this._swarm);
35
+ (_a = this._wss) === null || _a === void 0 ? void 0 : _a.clients.forEach((client) => {
36
+ if (client.readyState === ws_1.WebSocket.OPEN) {
37
+ client.send(JSON.stringify(stats));
38
+ }
39
+ });
40
+ }
41
+ async getStats(swarm) {
42
+ const activePeers = await this._peerRepository.getActivePeerCount();
43
+ const activeModels = await this._peerRepository.getActiveModelCount();
44
+ const swarmConnections = swarm.connections.size;
45
+ return {
46
+ activePeers,
47
+ activeModels,
48
+ swarmConnections,
49
+ };
50
+ }
51
+ }
52
+ exports.WsServer = WsServer;
package/global.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ declare module "b4a"
3
+
4
+ declare module "hyperswarm" {
5
+ import { EventEmitter } from "events";
6
+
7
+ export interface Swarm {
8
+ flushed(): Promise<void>;
9
+ }
10
+
11
+ export interface JoinOptions {
12
+ client?: boolean;
13
+ server?: boolean;
14
+ }
15
+
16
+ export interface SwarmOptions {
17
+ keyPair?: any;
18
+ seed?: Buffer;
19
+ maxPeers?: number;
20
+ firewall?: (remotePublicKey: string) => boolean;
21
+ dht?: any;
22
+ maxConnections: number;
23
+ }
24
+
25
+ export default class Hyperswarm extends EventEmitter {
26
+ constructor(opts?: SwarmOptions);
27
+ join(topic: string | Buffer, opts?: JoinOptions): Swarm;
28
+ on: (key: string, cb: (data: any) => void) => void;
29
+ once: (key: string, cb: (data: any) => void) => void;
30
+ flush: () => void;
31
+ leave(topic: Buffer): void;
32
+ destroy(): Promise<void>;
33
+ peers: Map<string, any>;
34
+ connecting: boolean;
35
+ }
36
+ }
37
+
38
+ declare module "hypercore-crypto" {
39
+ const hyperCoreCrypto: {
40
+ keyPair: (seed?: Buffer) => { publicKey: Buffer; secretKey: Buffer };
41
+ discoveryKey: (publicKey: Buffer) => Buffer;
42
+ randomBytes: (n?: number) => Buffer;
43
+ verify: (
44
+ challenge: Buffer,
45
+ signature: Buffer,
46
+ publicKey: Buffer
47
+ ) => boolean;
48
+ };
49
+
50
+ export = hyperCoreCrypto;
51
+ }
package/install.ps1 ADDED
@@ -0,0 +1,88 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ # Colors for output
4
+ $RED = "`e[31m"
5
+ $GREEN = "`e[32m"
6
+ $YELLOW = "`e[33m"
7
+ $NC = "`e[0m"
8
+
9
+ function Print-Color($color, $message) {
10
+ Write-Host "$color$message$NC"
11
+ }
12
+
13
+ # Check if npm is installed
14
+ if (!(Get-Command npm -ErrorAction SilentlyContinue)) {
15
+ Print-Color $RED "Error: npm is not installed. Please install Node.js and npm first."
16
+ exit 1
17
+ }
18
+
19
+ # Install symmetry-cli globally
20
+ Print-Color $YELLOW "Installing symmetry-cli globally..."
21
+ try {
22
+ npm install -g symmetry-cli
23
+ Print-Color $GREEN "symmetry-cli installed successfully!"
24
+ }
25
+ catch {
26
+ Print-Color $RED "Failed to install symmetry-cli. Please check your npm configuration and try again."
27
+ exit 1
28
+ }
29
+
30
+ # Prompt for API provider
31
+ Print-Color $YELLOW "Please select an API provider:"
32
+ $providers = @{
33
+ 1 = @{name = "LiteLLM"; value = "litellm"}
34
+ 2 = @{name = "LlamaCpp"; value = "llamacpp"}
35
+ 3 = @{name = "LMStudio"; value = "lmstudio"}
36
+ 4 = @{name = "Ollama"; value = "ollama"}
37
+ 5 = @{name = "Oobabooga"; value = "oobabooga"}
38
+ 6 = @{name = "OpenWebUI"; value = "openwebui"}
39
+ }
40
+
41
+ $providers.GetEnumerator() | ForEach-Object {
42
+ Write-Host "$($_.Key): $($_.Value.name)"
43
+ }
44
+
45
+ do {
46
+ $selection = Read-Host "Enter the number of your choice"
47
+ $api_provider = $providers[$selection].value
48
+ } while (-not $api_provider)
49
+
50
+ # Prompt for model name
51
+ Print-Color $YELLOW "Please enter the model name you want to use:"
52
+ $model_name = Read-Host
53
+
54
+ # Create config directory and provider.yaml file
55
+ $config_dir = "$env:USERPROFILE\.config\symmetry"
56
+ $provider_yaml = "$config_dir\provider.yaml"
57
+
58
+ New-Item -ItemType Directory -Force -Path $config_dir | Out-Null
59
+
60
+ if (!(Test-Path $provider_yaml)) {
61
+ Print-Color $YELLOW "Creating provider.yaml file..."
62
+ @"
63
+ # Symmetry Configuration
64
+ apiHostname: localhost
65
+ apiKey:
66
+ apiPath: /v1/chat/completions
67
+ apiPort: 11434
68
+ apiProtocol: http
69
+ apiProvider: $api_provider
70
+ dataCollectionEnabled: true
71
+ maxConnections: 10
72
+ modelName: $model_name
73
+ name: $env:USERNAME
74
+ path: $config_dir
75
+ public: true
76
+ serverKey: 4b4a9cc325d134dee6679e9407420023531fd7e96c563f6c5d00fd5549b77435
77
+ "@ | Set-Content $provider_yaml
78
+ Print-Color $GREEN "provider.yaml created successfully at $provider_yaml"
79
+ }
80
+ else {
81
+ Print-Color $YELLOW "provider.yaml already exists at $provider_yaml"
82
+ }
83
+
84
+ Print-Color $GREEN "Installation complete! You can now run 'symmetry-cli' to start your node."
85
+ Print-Color $YELLOW "Please edit $provider_yaml to customize your provider settings, especially:"
86
+ Print-Color $YELLOW " - apiKey: Add your API key if required"
87
+ Print-Color $YELLOW " - name: Currently set to your system username, change if needed"
88
+ Print-Color $YELLOW " - public: Set to true by default, change to false if you don't want to be publicly accessible"
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} **/
2
+ module.exports = {
3
+ testEnvironment: "node",
4
+ transform: {
5
+ "^.+.tsx?$": ["ts-jest", {}],
6
+ },
7
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "symmetry-cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/symmetry.js",
6
+ "bin": "dist/symmetry.js",
7
+ "author": "rjmacarthy",
8
+ "license": "ISC",
9
+ "scripts": {
10
+ "test": "jest",
11
+ "build": "tsc",
12
+ "package": "pkg ./dist/symmetry.js",
13
+ "start": "node dist/symmetry.js",
14
+ "dev": "ts-node src/symmetry.ts",
15
+ "debug": "ts-node --inspect src/symmetry.ts",
16
+ "lint": "eslint . --ext .ts,.js --fix"
17
+ },
18
+ "dependencies": {
19
+ "chalk": "^4.1.2",
20
+ "commander": "^12.1.0",
21
+ "hypercore-crypto": "^3.4.2",
22
+ "hyperdht": "^6.15.4",
23
+ "hyperswarm": "^4.8.0",
24
+ "js-yaml": "^4.1.0",
25
+ "sqlite3": "^5.1.7",
26
+ "ws": "^8.18.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/jest": "^29.5.12",
30
+ "@types/js-yaml": "^4.0.9",
31
+ "@types/node": "^20.14.10",
32
+ "@types/ws": "^8.5.11",
33
+ "@typescript-eslint/eslint-plugin": "^7.16.1",
34
+ "@typescript-eslint/parser": "^7.16.1",
35
+ "eslint": "^8.57.0",
36
+ "globals": "^15.8.0",
37
+ "jest": "^29.7.0",
38
+ "ts-jest": "^29.2.2",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.5.4"
41
+ },
42
+ "types": "types/twinnymind.d.ts"
43
+ }
package/readme.md ADDED
@@ -0,0 +1,167 @@
1
+ # Symmetry
2
+
3
+ In the vast lands of the Internet, where bits and bytes flow like the rivers of old, there arose a tool of great power and purpose. Symmetry the enchanted mirror. With Symmetry, users of the digital realm could offer their computational strength to others, allowing seekers of knowledge to tap into the wisdom of distant machines.
4
+
5
+ ## Features
6
+
7
+ - **Network**: A decentralized network, of interconnected peers.
8
+ - **Configuration**: A scroll of YAML, easily inscribed to bend Symmetry to one's will.
9
+ - **Privacy**: A choice between openness and secrecy.
10
+ - **Data collection**: An option to gather tales of interactions, should the provider wish it so.
11
+
12
+ ## Installation
13
+
14
+ To call forth Symmetry into your realm, speak these words of power:
15
+
16
+ Linux and MacOs
17
+ ```
18
+ curl -fsSL https://raw.githubusercontent.com/twinnydotdev/symmetry/main/install.sh | sh
19
+ ```
20
+
21
+ Windows
22
+ ```
23
+ iwr -useb https://raw.githubusercontent.com/twinnydotdev/symmetry/main/install.ps1 | iex
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ To awaken Symmetry, utter this command:
29
+
30
+ ```
31
+ symmetry-cli
32
+ ```
33
+
34
+ Symmetry will seek its tome of knowledge in the halls of `~/.config/symmetry/provider.yaml`. Should you wish to direct it to another scroll, speak thus:
35
+
36
+ ```
37
+ symmetry -c /path/to/your/sacred/provider.yaml
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ Inscribe your `provider.yaml` with these mystical runes:
43
+
44
+ ```yaml
45
+ apiHostname: localhost # The dwelling of your local oracle
46
+ apiKey: # The secret word to commune with your oracle
47
+ apiPath: /v1/chat/completions # The path to wisdom
48
+ apiPort: 11434 # The gate through which knowledge flows
49
+ apiProtocol: http # The method of communion
50
+ apiProvider: ollama # The lineage of your oracle
51
+ dataCollectionEnabled: true # Whether to gather lore
52
+ maxConnections: 10 # The limit of simultaneous seekers
53
+ modelName: llama3:8b # The true name of your oracle
54
+ name: # Your title in this realm
55
+ path: /home/richard/.config/symmetry/default # The dwelling for data
56
+ public: true # Whether your services are open to all
57
+ serverKey: 4b4a9cc325d134dee6679e9407420023531fd7e96c563f6c5d00fd5549b77435 # The key of the central tower
58
+ ```
59
+
60
+ Adjust these runes to align with your own preferences.
61
+
62
+ ## Architecture
63
+
64
+ ```mermaid
65
+ graph TB
66
+ S[Symmetry Server]
67
+
68
+ subgraph "Inference Providers"
69
+ P1[Provider 1]
70
+ P2[Provider 2]
71
+ P3[Provider 3]
72
+ end
73
+
74
+ subgraph "Clients"
75
+ C1[Client 1<br/>Requests Model B]
76
+ C2[Client 2<br/>Requests Model C]
77
+ end
78
+
79
+ P1 <--> |"1. Connect & Auth & Register Model"| S
80
+ P2 <--> |"1. Connect & Auth & Register Model"| S
81
+ P3 <--> |"1. Connect & Auth & Register Model"| S
82
+
83
+ C1 -- "2. Connect" --> S
84
+ C2 -- "2. Connect" --> S
85
+
86
+ S -- "3. Assign Provider<br/>based on Model" --> C1
87
+ S -- "3. Assign Provider<br/>based on Model" --> C2
88
+
89
+ C1 <--> |"4. Inference<br/>Request/Response"| P2
90
+ C2 <--> |"4. Inference<br/>Request/Response"| P3
91
+
92
+ style S fill:#ff9900,stroke:#333,stroke-width:4px
93
+ style P1 fill:#00ccff,stroke:#333,stroke-width:2px
94
+ style P2 fill:#00ccff,stroke:#333,stroke-width:2px
95
+ style P3 fill:#00ccff,stroke:#333,stroke-width:2px
96
+ style C1 fill:#333,stroke:#fff,stroke-width:2px
97
+ style C2 fill:#333,stroke:#fff,stroke-width:2px
98
+ ```
99
+
100
+ ### Server, Providers and Clients:
101
+ - **Central Server**: Symmetry Server, a beacon of order in the chaotic digital winds.
102
+ - **Providers**: Inference Providers, keepers of knowledge and computational might.
103
+ - **Client**: Clients, adventurers in search of answers from the oracles of data.
104
+
105
+ ### Connection:
106
+
107
+ 1. The Providers approach the Central Tower, proving their worth and declaring their specialties.
108
+ 2. Seekers call out to the Tower, their questions echoing in the digital void.
109
+ 3. The Tower, in its wisdom, guides each Seeker to a suitable Provider.
110
+ 4. Seeker and Provider join in a dance of query and response, their connection direct and true.
111
+
112
+ ### Communication:
113
+
114
+ - All messages between Seekers and Providers are cloaked in unbreakable encryption, safe from prying eyes.
115
+ - The Providers may choose to remember the tales told to them, a choice they must declare openly.
116
+ - Seekers are granted the wisdom to choose their Providers based on these declarations of memory.
117
+
118
+ ### Features:
119
+
120
+ - **Adaptability**: As changeable as the seasons, ready for new knowledge and new seekers.
121
+ - **Swiftness**: Direct connections ensure that wisdom flows like the rapids of a mountain stream.
122
+ - **Guardianship**: Only the worthy may enter this circle of trust.
123
+ - **Balance**: The Tower ensures no single Provider bears too heavy a burden.
124
+ - **Growth**: New Providers may join the circle, expanding the realm of possibility.
125
+
126
+ Thus does Symmetry weave its web of connections, bringing together the seekers and keepers of digital wisdom in a grand tapestry of knowledge and computation.
127
+
128
+ ## Forging of Symmetry
129
+
130
+ For those who wish to shape Symmetry with their own hands:
131
+
132
+ 1. Summon the essence:
133
+ ```
134
+ git clone https://github.com/twinnydotdev/symmetry.git
135
+ cd symmetry
136
+ ```
137
+
138
+ 2. Gather the necessary elements:
139
+ ```
140
+ npm install
141
+ ```
142
+
143
+ 3. Forge the tool:
144
+ ```
145
+ npm run build
146
+ ```
147
+
148
+ 4. Awaken:
149
+ ```
150
+ npm start
151
+ ```
152
+
153
+ ## Call for Heroes
154
+
155
+ Brave souls willing to improve upon this grand design are most welcome. Present your [scrolls of change](https://github.com/twinnydotdev/symmetry/pulls) for consideration.
156
+
157
+ ## Covenant
158
+
159
+ This work is protected under the [Oath of MIT](https://github.com/twinnydotdev/symmetry/blob/main/LICENSE), a sacred bond between creator and user.
160
+
161
+ ## Council of Elders
162
+
163
+ Should you face trials or seek guidance, bring your [queries](https://github.com/twinnydotdev/symmetry/issues) before the Council on GitHub.
164
+
165
+ ## A Nod to the Ancients
166
+
167
+ We pay homage to [Hyperswarm](https://github.com/holepunchto/hyperswarm), the tool that makes our connections possible.
package/src/config.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { ProviderConfig } from "./types";
2
+ import fs from "fs";
3
+ import yaml from "js-yaml";
4
+
5
+ export class ConfigManager {
6
+ private config: ProviderConfig;
7
+
8
+ constructor(configPath: string) {
9
+ const configFile = fs.readFileSync(configPath, "utf8");
10
+ const config = yaml.load(configFile) as ProviderConfig;
11
+ this.config = config
12
+ this.validate();
13
+ }
14
+
15
+ public getAll () {
16
+ return this.config;
17
+ }
18
+
19
+ private validate(): void {
20
+ const requiredFields: (keyof ProviderConfig)[] = [
21
+ "apiHostname",
22
+ "apiPath",
23
+ "apiPort",
24
+ "apiProtocol",
25
+ "apiProvider",
26
+ "modelName",
27
+ "path",
28
+ "public",
29
+ "serverKey",
30
+ ];
31
+
32
+ for (const field of requiredFields) {
33
+ if (!(field in this.config)) {
34
+ throw new Error(
35
+ `Missing required field in client configuration: ${field}`
36
+ );
37
+ }
38
+ }
39
+
40
+ if (typeof this.config.public !== "boolean") {
41
+ throw new Error(
42
+ 'The "public" field in client configuration must be a boolean'
43
+ );
44
+ }
45
+ }
46
+
47
+ get<K extends keyof ProviderConfig>(key: K): ProviderConfig[K];
48
+ get(key: string): unknown {
49
+ return this.config[key as keyof ProviderConfig];
50
+ }
51
+ }
@@ -0,0 +1,29 @@
1
+ export const NORMALIZE_REGEX = /\s*\r?\n|\r/g;
2
+
3
+ export const serverMessageKeys = {
4
+ challenge: "challenge",
5
+ conectionSize: "conectionSize",
6
+ heartbeat: "heartbeat",
7
+ inference: "inference",
8
+ inferenceEnded: "inferenceEnded",
9
+ join: "join",
10
+ joinAck: "joinAck",
11
+ leave: "leave",
12
+ newConversation: "newConversation",
13
+ ping: "ping",
14
+ pong: "pong",
15
+ providerDetails: "providerDetails",
16
+ reportCompletion: "reportCompletion",
17
+ requestProvider: "requestProvider",
18
+ sessionValid: "sessionValid",
19
+ verifySession: "verifySession",
20
+ } as const;
21
+
22
+ export const apiProviders = {
23
+ LiteLLM: 'litellm',
24
+ LlamaCpp: 'llamacpp',
25
+ LMStudio: 'lmstudio',
26
+ Ollama: 'ollama',
27
+ Oobabooga: 'oobabooga',
28
+ OpenWebUI: 'openwebui',
29
+ } as const
package/src/logger.ts ADDED
@@ -0,0 +1,47 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import chalk from "chalk";
3
+
4
+ export enum LogLevel {
5
+ DEBUG,
6
+ ERROR,
7
+ INFO,
8
+ WARNING,
9
+ }
10
+
11
+ export class Logger {
12
+ private static instance: Logger;
13
+ private logLevel: LogLevel = LogLevel.INFO;
14
+
15
+ private constructor() {}
16
+
17
+ public static getInstance(): Logger {
18
+ if (!Logger.instance) {
19
+ Logger.instance = new Logger();
20
+ }
21
+ return Logger.instance;
22
+ }
23
+
24
+ public setLogLevel(level: LogLevel): void {
25
+ this.logLevel = level;
26
+ }
27
+
28
+ public info(message: string, ...args: any[]): void {
29
+ if (this.logLevel <= LogLevel.INFO) {
30
+ console.log(chalk.blue("ℹ️ INFO:"), message, ...args);
31
+ }
32
+ }
33
+
34
+ public warning(message: string, ...args: any[]): void {
35
+ console.log(chalk.yellow("⚠️ WARNING:"), message, ...args);
36
+ }
37
+
38
+ public error(message: string, ...args: any[]): void {
39
+ console.error(chalk.red("❌ ERROR:"), message, ...args);
40
+ }
41
+
42
+ public debug(message: string, ...args: any[]): void {
43
+ console.log(chalk.gray("🐛 DEBUG:"), message, ...args);
44
+ }
45
+ }
46
+
47
+ export const logger = Logger.getInstance();