chainlesschain 0.37.6

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 ADDED
@@ -0,0 +1,182 @@
1
+ # chainlesschain CLI
2
+
3
+ Command-line interface for installing, configuring, and managing [ChainlessChain](https://www.chainlesschain.com) — a decentralized personal AI management system with hardware-level security.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm install -g chainlesschain
9
+ chainlesschain setup
10
+ ```
11
+
12
+ ## Requirements
13
+
14
+ - **Node.js** >= 22.12.0
15
+ - **Docker** (optional, for backend services)
16
+
17
+ ## Commands
18
+
19
+ ### `chainlesschain setup`
20
+
21
+ Interactive setup wizard. Checks prerequisites, configures LLM provider, downloads the desktop binary, and optionally starts Docker services.
22
+
23
+ ```bash
24
+ chainlesschain setup
25
+ chainlesschain setup --skip-download # Skip binary download
26
+ chainlesschain setup --skip-services # Skip Docker setup
27
+ ```
28
+
29
+ ### `chainlesschain start`
30
+
31
+ Launch the ChainlessChain desktop application.
32
+
33
+ ```bash
34
+ chainlesschain start # Launch GUI app
35
+ chainlesschain start --headless # Start backend services only (no GUI)
36
+ chainlesschain start --services # Also start Docker services
37
+ ```
38
+
39
+ ### `chainlesschain stop`
40
+
41
+ Stop ChainlessChain.
42
+
43
+ ```bash
44
+ chainlesschain stop # Stop desktop app
45
+ chainlesschain stop --services # Stop Docker services only
46
+ chainlesschain stop --all # Stop app + Docker services
47
+ ```
48
+
49
+ ### `chainlesschain status`
50
+
51
+ Show status of the desktop app, Docker services, and port availability.
52
+
53
+ ```bash
54
+ chainlesschain status
55
+ ```
56
+
57
+ ### `chainlesschain services <action>`
58
+
59
+ Manage Docker backend services (Ollama, Qdrant, PostgreSQL, Redis, etc.).
60
+
61
+ ```bash
62
+ chainlesschain services up # Start all services
63
+ chainlesschain services up ollama redis # Start specific services
64
+ chainlesschain services down # Stop all services
65
+ chainlesschain services logs # View logs
66
+ chainlesschain services logs -f # Follow logs
67
+ chainlesschain services pull # Pull latest images
68
+ ```
69
+
70
+ ### `chainlesschain config <action>`
71
+
72
+ Manage configuration.
73
+
74
+ ```bash
75
+ chainlesschain config list # Show all config values
76
+ chainlesschain config get llm.provider # Get a specific value
77
+ chainlesschain config set llm.provider openai
78
+ chainlesschain config set llm.apiKey sk-...
79
+ chainlesschain config edit # Open in $EDITOR
80
+ chainlesschain config reset # Reset to defaults
81
+ ```
82
+
83
+ ### `chainlesschain update`
84
+
85
+ Check for and install updates.
86
+
87
+ ```bash
88
+ chainlesschain update # Update to latest stable
89
+ chainlesschain update --check # Check only, don't download
90
+ chainlesschain update --channel beta # Use beta channel
91
+ chainlesschain update --channel dev # Use dev channel
92
+ chainlesschain update --force # Re-download even if exists
93
+ ```
94
+
95
+ ### `chainlesschain doctor`
96
+
97
+ Diagnose your environment.
98
+
99
+ ```bash
100
+ chainlesschain doctor
101
+ ```
102
+
103
+ Checks: Node.js version, npm, Docker, Docker Compose, Git, config directory, binary installation, setup status, port availability, disk space.
104
+
105
+ ## Global Options
106
+
107
+ ```bash
108
+ chainlesschain --version # Show version
109
+ chainlesschain --help # Show help
110
+ chainlesschain --verbose # Enable verbose output
111
+ chainlesschain --quiet # Suppress non-essential output
112
+ ```
113
+
114
+ ## Configuration
115
+
116
+ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and manages this file automatically during setup.
117
+
118
+ ### Config Schema
119
+
120
+ ```json
121
+ {
122
+ "setupCompleted": true,
123
+ "completedAt": "2026-03-11T00:00:00.000Z",
124
+ "edition": "personal",
125
+ "llm": {
126
+ "provider": "ollama",
127
+ "apiKey": null,
128
+ "baseUrl": "http://localhost:11434",
129
+ "model": "qwen2:7b"
130
+ },
131
+ "enterprise": {
132
+ "serverUrl": null,
133
+ "apiKey": null,
134
+ "tenantId": null
135
+ },
136
+ "services": {
137
+ "autoStart": false,
138
+ "dockerComposePath": null
139
+ },
140
+ "update": {
141
+ "channel": "stable",
142
+ "autoCheck": true
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### Supported LLM Providers
148
+
149
+ | Provider | Default Model | API Key Required |
150
+ | ------------------- | ------------- | ---------------- |
151
+ | Ollama (Local) | qwen2:7b | No |
152
+ | OpenAI | gpt-4o | Yes |
153
+ | DashScope (Alibaba) | qwen-max | Yes |
154
+ | DeepSeek | deepseek-chat | Yes |
155
+ | Custom | — | Yes |
156
+
157
+ ## File Structure
158
+
159
+ ```
160
+ ~/.chainlesschain/
161
+ ├── config.json # Configuration
162
+ ├── bin/ # Downloaded binaries
163
+ ├── state/ # Runtime state (PID files)
164
+ ├── services/ # Service configurations
165
+ ├── logs/ # CLI logs
166
+ └── cache/ # Download cache
167
+ ```
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ cd packages/cli
173
+ npm install
174
+ npm test # Run all tests
175
+ npm run test:unit # Unit tests only
176
+ npm run test:integration # Integration tests
177
+ npm run test:e2e # End-to-end tests
178
+ ```
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createProgram } from "../src/index.js";
4
+
5
+ const program = createProgram();
6
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "chainlesschain",
3
+ "version": "0.37.6",
4
+ "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
+ "type": "module",
6
+ "bin": {
7
+ "chainlesschain": "./bin/chainlesschain.js"
8
+ },
9
+ "main": "src/index.js",
10
+ "scripts": {
11
+ "test": "vitest run",
12
+ "test:unit": "vitest run __tests__/unit/",
13
+ "test:integration": "vitest run __tests__/integration/",
14
+ "test:e2e": "vitest run __tests__/e2e/",
15
+ "test:watch": "vitest watch",
16
+ "lint": "eslint src/ bin/ --ext .js",
17
+ "format": "prettier --write \"src/**/*.js\" \"bin/**/*.js\" \"__tests__/**/*.js\""
18
+ },
19
+ "engines": {
20
+ "node": ">=22.12.0"
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "src/",
25
+ "README.md"
26
+ ],
27
+ "keywords": [
28
+ "chainlesschain",
29
+ "ai",
30
+ "cli",
31
+ "electron",
32
+ "decentralized",
33
+ "knowledge-base"
34
+ ],
35
+ "author": "ChainlessChain Team",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/yourname/chainlesschain.git",
40
+ "directory": "packages/cli"
41
+ },
42
+ "homepage": "https://www.chainlesschain.com",
43
+ "dependencies": {
44
+ "commander": "^12.1.0",
45
+ "@inquirer/prompts": "^7.2.0",
46
+ "chalk": "^5.4.1",
47
+ "ora": "^8.1.1",
48
+ "semver": "^7.6.3"
49
+ },
50
+ "devDependencies": {
51
+ "vitest": "^3.1.1"
52
+ }
53
+ }
@@ -0,0 +1,105 @@
1
+ import chalk from "chalk";
2
+ import {
3
+ loadConfig,
4
+ getConfigValue,
5
+ setConfigValue,
6
+ resetConfig,
7
+ saveConfig,
8
+ } from "../lib/config-manager.js";
9
+ import { getConfigPath } from "../lib/paths.js";
10
+ import logger from "../lib/logger.js";
11
+
12
+ export function registerConfigCommand(program) {
13
+ const cmd = program
14
+ .command("config")
15
+ .description("Manage ChainlessChain configuration");
16
+
17
+ cmd
18
+ .command("list")
19
+ .description("Show all configuration values")
20
+ .action(() => {
21
+ const config = loadConfig();
22
+ logger.log(chalk.bold(`\n Config: ${getConfigPath()}\n`));
23
+ printConfig(config, " ");
24
+ logger.newline();
25
+ });
26
+
27
+ cmd
28
+ .command("get")
29
+ .description("Get a configuration value")
30
+ .argument("<key>", "Config key (dot-notation, e.g. llm.provider)")
31
+ .action((key) => {
32
+ const value = getConfigValue(key);
33
+ if (value === undefined) {
34
+ logger.error(`Key not found: ${key}`);
35
+ process.exit(1);
36
+ }
37
+ if (typeof value === "object") {
38
+ logger.log(JSON.stringify(value, null, 2));
39
+ } else {
40
+ logger.log(String(value));
41
+ }
42
+ });
43
+
44
+ cmd
45
+ .command("set")
46
+ .description("Set a configuration value")
47
+ .argument("<key>", "Config key (dot-notation)")
48
+ .argument("<value>", "Value to set")
49
+ .action((key, value) => {
50
+ setConfigValue(key, value);
51
+ logger.success(`Set ${key} = ${value}`);
52
+ });
53
+
54
+ cmd
55
+ .command("edit")
56
+ .description("Open config file in default editor")
57
+ .action(async () => {
58
+ const configPath = getConfigPath();
59
+ const editor =
60
+ process.env.EDITOR ||
61
+ process.env.VISUAL ||
62
+ (process.platform === "win32" ? "notepad" : "vi");
63
+ const { execSync } = await import("node:child_process");
64
+ try {
65
+ execSync(`${editor} "${configPath}"`, { stdio: "inherit" });
66
+ } catch (err) {
67
+ logger.error(`Failed to open editor: ${err.message}`);
68
+ logger.info(`Config file is at: ${configPath}`);
69
+ }
70
+ });
71
+
72
+ cmd
73
+ .command("reset")
74
+ .description("Reset configuration to defaults")
75
+ .action(async () => {
76
+ const { askConfirm } = await import("../lib/prompts.js");
77
+ const confirmed = await askConfirm(
78
+ "Reset all configuration to defaults?",
79
+ false,
80
+ );
81
+ if (confirmed) {
82
+ resetConfig();
83
+ logger.success("Configuration reset to defaults");
84
+ } else {
85
+ logger.info("Reset cancelled");
86
+ }
87
+ });
88
+ }
89
+
90
+ function printConfig(obj, indent = "") {
91
+ for (const [key, value] of Object.entries(obj)) {
92
+ if (value && typeof value === "object" && !Array.isArray(value)) {
93
+ logger.log(`${indent}${chalk.cyan(key)}:`);
94
+ printConfig(value, indent + " ");
95
+ } else {
96
+ const displayValue =
97
+ value === null
98
+ ? chalk.gray("null")
99
+ : key.toLowerCase().includes("key") && value
100
+ ? chalk.yellow("****")
101
+ : String(value);
102
+ logger.log(`${indent}${chalk.cyan(key)}: ${displayValue}`);
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,178 @@
1
+ import chalk from "chalk";
2
+ import { execSync } from "node:child_process";
3
+ import { existsSync, readdirSync } from "node:fs";
4
+ import { createConnection } from "node:net";
5
+ import semver from "semver";
6
+ import { MIN_NODE_VERSION, DEFAULT_PORTS, VERSION } from "../constants.js";
7
+ import { getHomeDir, getConfigPath, getBinDir } from "../lib/paths.js";
8
+ import {
9
+ isDockerAvailable,
10
+ isDockerComposeAvailable,
11
+ } from "../lib/service-manager.js";
12
+ import { loadConfig } from "../lib/config-manager.js";
13
+ import logger from "../lib/logger.js";
14
+
15
+ export function registerDoctorCommand(program) {
16
+ program
17
+ .command("doctor")
18
+ .description("Diagnose your ChainlessChain environment")
19
+ .action(async () => {
20
+ logger.log(chalk.bold("\n ChainlessChain Doctor\n"));
21
+
22
+ const checks = [];
23
+
24
+ // Node.js
25
+ const nodeVersion = process.versions.node;
26
+ const nodeOk = semver.gte(nodeVersion, MIN_NODE_VERSION);
27
+ checks.push({
28
+ name: `Node.js ${nodeVersion}`,
29
+ ok: nodeOk,
30
+ detail: nodeOk ? "" : `Requires >=${MIN_NODE_VERSION}`,
31
+ });
32
+
33
+ // npm
34
+ try {
35
+ const npmVersion = execSync("npm --version", {
36
+ encoding: "utf-8",
37
+ }).trim();
38
+ checks.push({ name: `npm ${npmVersion}`, ok: true });
39
+ } catch {
40
+ checks.push({ name: "npm", ok: false, detail: "Not found" });
41
+ }
42
+
43
+ // Docker
44
+ checks.push({
45
+ name: "Docker",
46
+ ok: isDockerAvailable(),
47
+ detail: isDockerAvailable() ? "" : "Not installed (optional)",
48
+ });
49
+ checks.push({
50
+ name: "Docker Compose",
51
+ ok: isDockerComposeAvailable(),
52
+ detail: isDockerComposeAvailable() ? "" : "Not installed (optional)",
53
+ });
54
+
55
+ // Git
56
+ try {
57
+ const gitVersion = execSync("git --version", {
58
+ encoding: "utf-8",
59
+ }).trim();
60
+ checks.push({ name: gitVersion, ok: true });
61
+ } catch {
62
+ checks.push({ name: "Git", ok: false, detail: "Not found" });
63
+ }
64
+
65
+ // Config directory
66
+ const homeDir = getHomeDir();
67
+ checks.push({
68
+ name: `Config dir: ${homeDir}`,
69
+ ok: existsSync(homeDir),
70
+ detail: existsSync(homeDir) ? "" : 'Run "chainlesschain setup"',
71
+ });
72
+
73
+ // Config file
74
+ const configPath = getConfigPath();
75
+ checks.push({
76
+ name: "Config file",
77
+ ok: existsSync(configPath),
78
+ detail: existsSync(configPath) ? "" : 'Run "chainlesschain setup"',
79
+ });
80
+
81
+ // Binary
82
+ const binDir = getBinDir();
83
+ const hasBin = existsSync(binDir) && readdirSafe(binDir).length > 0;
84
+ checks.push({
85
+ name: "Desktop binary",
86
+ ok: hasBin,
87
+ detail: hasBin
88
+ ? ""
89
+ : 'Run "chainlesschain setup" or "chainlesschain update"',
90
+ });
91
+
92
+ // Setup completed
93
+ const config = loadConfig();
94
+ checks.push({
95
+ name: "Setup completed",
96
+ ok: config.setupCompleted,
97
+ detail: config.setupCompleted ? "" : 'Run "chainlesschain setup"',
98
+ });
99
+
100
+ // Print results
101
+ for (const check of checks) {
102
+ const icon = check.ok ? chalk.green("✔") : chalk.red("✖");
103
+ const detail = check.detail ? chalk.gray(` (${check.detail})`) : "";
104
+ logger.log(` ${icon} ${check.name}${detail}`);
105
+ }
106
+
107
+ // Port scan
108
+ logger.log(chalk.bold("\n Port Status\n"));
109
+ for (const [name, port] of Object.entries(DEFAULT_PORTS)) {
110
+ const open = await checkPort(port);
111
+ const icon = open ? chalk.green("●") : chalk.gray("○");
112
+ logger.log(` ${icon} ${name}: ${port}`);
113
+ }
114
+
115
+ // Disk space (basic)
116
+ try {
117
+ const { statfsSync } = await import("node:fs");
118
+ // statfsSync available in Node 22+
119
+ if (statfsSync) {
120
+ const stats = statfsSync(homeDir);
121
+ const freeGB = (stats.bavail * stats.bsize) / (1024 * 1024 * 1024);
122
+ const ok = freeGB > 2;
123
+ logger.log(chalk.bold("\n Disk\n"));
124
+ const icon = ok ? chalk.green("✔") : chalk.yellow("⚠");
125
+ logger.log(` ${icon} Free space: ${freeGB.toFixed(1)} GB`);
126
+ }
127
+ } catch {
128
+ // statfsSync not available on all platforms
129
+ }
130
+
131
+ // Summary
132
+ const failures = checks.filter((c) => !c.ok);
133
+ logger.newline();
134
+ if (failures.length === 0) {
135
+ logger.log(chalk.bold.green(" All checks passed!\n"));
136
+ } else {
137
+ const critical = failures.filter(
138
+ (c) => !c.detail?.includes("optional"),
139
+ );
140
+ if (critical.length > 0) {
141
+ logger.log(
142
+ chalk.bold.red(
143
+ ` ${critical.length} issue(s) found. See details above.\n`,
144
+ ),
145
+ );
146
+ } else {
147
+ logger.log(
148
+ chalk.bold.yellow(
149
+ ` ${failures.length} optional component(s) missing.\n`,
150
+ ),
151
+ );
152
+ }
153
+ }
154
+ });
155
+ }
156
+
157
+ function checkPort(port, host = "127.0.0.1") {
158
+ return new Promise((resolve) => {
159
+ const socket = createConnection({ port, host, timeout: 1000 });
160
+ socket.on("connect", () => {
161
+ socket.destroy();
162
+ resolve(true);
163
+ });
164
+ socket.on("error", () => resolve(false));
165
+ socket.on("timeout", () => {
166
+ socket.destroy();
167
+ resolve(false);
168
+ });
169
+ });
170
+ }
171
+
172
+ function readdirSafe(dir) {
173
+ try {
174
+ return readdirSync(dir);
175
+ } catch {
176
+ return [];
177
+ }
178
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ isDockerAvailable,
3
+ isDockerComposeAvailable,
4
+ servicesUp,
5
+ servicesDown,
6
+ servicesLogs,
7
+ servicesPull,
8
+ findComposeFile,
9
+ } from "../lib/service-manager.js";
10
+ import logger from "../lib/logger.js";
11
+
12
+ export function registerServicesCommand(program) {
13
+ const cmd = program
14
+ .command("services")
15
+ .description("Manage Docker backend services");
16
+
17
+ cmd
18
+ .command("up")
19
+ .description("Start Docker services")
20
+ .argument("[services...]", "Specific services to start")
21
+ .action(async (services) => {
22
+ await withCompose((composePath) => {
23
+ logger.info("Starting services...");
24
+ servicesUp(composePath, {
25
+ services: services.length ? services : undefined,
26
+ });
27
+ logger.success("Services started");
28
+ });
29
+ });
30
+
31
+ cmd
32
+ .command("down")
33
+ .description("Stop Docker services")
34
+ .action(async () => {
35
+ await withCompose((composePath) => {
36
+ logger.info("Stopping services...");
37
+ servicesDown(composePath);
38
+ logger.success("Services stopped");
39
+ });
40
+ });
41
+
42
+ cmd
43
+ .command("logs")
44
+ .description("View service logs")
45
+ .option("-f, --follow", "Follow log output")
46
+ .option("--tail <lines>", "Number of lines to show", "100")
47
+ .argument("[services...]", "Specific services")
48
+ .action(async (services, options) => {
49
+ await withCompose(async (composePath) => {
50
+ await servicesLogs(composePath, {
51
+ follow: options.follow,
52
+ tail: options.tail,
53
+ services: services.length ? services : undefined,
54
+ });
55
+ });
56
+ });
57
+
58
+ cmd
59
+ .command("pull")
60
+ .description("Pull latest service images")
61
+ .action(async () => {
62
+ await withCompose((composePath) => {
63
+ logger.info("Pulling images...");
64
+ servicesPull(composePath);
65
+ logger.success("Images updated");
66
+ });
67
+ });
68
+ }
69
+
70
+ async function withCompose(fn) {
71
+ try {
72
+ if (!isDockerAvailable()) {
73
+ logger.error("Docker is not installed or not running");
74
+ process.exit(1);
75
+ }
76
+ if (!isDockerComposeAvailable()) {
77
+ logger.error("Docker Compose is not available");
78
+ process.exit(1);
79
+ }
80
+
81
+ const composePath = findComposeFile([process.cwd(), "backend/docker"]);
82
+ if (!composePath) {
83
+ logger.error(
84
+ "docker-compose.yml not found. Run from the project root directory.",
85
+ );
86
+ process.exit(1);
87
+ }
88
+
89
+ await fn(composePath);
90
+ } catch (err) {
91
+ logger.error(`Service operation failed: ${err.message}`);
92
+ process.exit(1);
93
+ }
94
+ }