chainlesschain 0.38.1 → 0.40.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.38.1",
3
+ "version": "0.40.1",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,8 @@
50
50
  "@chainlesschain/shared-logger": "0.1.0",
51
51
  "@chainlesschain/core-db": "0.1.0",
52
52
  "@chainlesschain/core-config": "0.1.0",
53
- "@chainlesschain/core-infra": "0.1.0"
53
+ "@chainlesschain/core-infra": "0.1.0",
54
+ "ws": "^8.14.2"
54
55
  },
55
56
  "devDependencies": {
56
57
  "vitest": "^3.1.1"
@@ -16,7 +16,11 @@ export function registerAgentCommand(program) {
16
16
  "Start an agentic AI session (reads/writes files, runs commands)",
17
17
  )
18
18
  .option("--model <model>", "Model name", "qwen2:7b")
19
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
19
+ .option(
20
+ "--provider <provider>",
21
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
22
+ "ollama",
23
+ )
20
24
  .option("--base-url <url>", "API base URL")
21
25
  .option("--api-key <key>", "API key")
22
26
  .option("--session <id>", "Resume a previous agent session")
@@ -6,6 +6,7 @@
6
6
  import ora from "ora";
7
7
  import chalk from "chalk";
8
8
  import { logger } from "../lib/logger.js";
9
+ import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
9
10
 
10
11
  /**
11
12
  * Send a single question to an LLM provider
@@ -37,12 +38,24 @@ async function queryLLM(question, options = {}) {
37
38
 
38
39
  const data = await response.json();
39
40
  return data.response;
40
- } else if (provider === "openai") {
41
- const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
41
+ } else {
42
+ // OpenAI-compatible providers (openai, volcengine, deepseek, dashscope, mistral, gemini)
43
+ const providerDef = BUILT_IN_PROVIDERS[provider];
44
+ if (!providerDef) {
45
+ throw new Error(
46
+ `Unsupported provider: ${provider}. Supported: ollama, openai, volcengine, deepseek, dashscope, gemini, mistral, anthropic`,
47
+ );
48
+ }
49
+
50
+ const apiKey =
51
+ options.apiKey ||
52
+ (providerDef.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
42
53
  if (!apiKey)
43
- throw new Error("OpenAI API key required (--api-key or OPENAI_API_KEY)");
54
+ throw new Error(
55
+ `API key required for ${provider} (--api-key or ${providerDef.apiKeyEnv})`,
56
+ );
44
57
 
45
- const apiBase = options.baseUrl || "https://api.openai.com/v1";
58
+ const apiBase = options.baseUrl || providerDef.baseUrl;
46
59
  const response = await fetch(`${apiBase}/chat/completions`, {
47
60
  method: "POST",
48
61
  headers: {
@@ -50,22 +63,20 @@ async function queryLLM(question, options = {}) {
50
63
  Authorization: `Bearer ${apiKey}`,
51
64
  },
52
65
  body: JSON.stringify({
53
- model: model || "gpt-4o-mini",
66
+ model: model || providerDef.models[0],
54
67
  messages: [{ role: "user", content: question }],
55
68
  }),
56
69
  });
57
70
 
58
71
  if (!response.ok) {
59
72
  throw new Error(
60
- `OpenAI error: ${response.status} ${response.statusText}`,
73
+ `${provider} error: ${response.status} ${response.statusText}`,
61
74
  );
62
75
  }
63
76
 
64
77
  const data = await response.json();
65
78
  return data.choices[0].message.content;
66
79
  }
67
-
68
- throw new Error(`Unsupported provider: ${provider}`);
69
80
  }
70
81
 
71
82
  export function registerAskCommand(program) {
@@ -74,7 +85,11 @@ export function registerAskCommand(program) {
74
85
  .description("Ask a question to the AI (single-shot)")
75
86
  .argument("<question>", "The question to ask")
76
87
  .option("--model <model>", "Model name", "qwen2:7b")
77
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
88
+ .option(
89
+ "--provider <provider>",
90
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
91
+ "ollama",
92
+ )
78
93
  .option("--base-url <url>", "API base URL")
79
94
  .option("--api-key <key>", "API key")
80
95
  .option("--json", "Output as JSON")
@@ -11,7 +11,11 @@ export function registerChatCommand(program) {
11
11
  .command("chat")
12
12
  .description("Start an interactive AI chat session")
13
13
  .option("--model <model>", "Model name", "qwen2:7b")
14
- .option("--provider <provider>", "LLM provider (ollama, openai)", "ollama")
14
+ .option(
15
+ "--provider <provider>",
16
+ "LLM provider (ollama, openai, volcengine, deepseek, ...)",
17
+ "ollama",
18
+ )
15
19
  .option("--base-url <url>", "API base URL")
16
20
  .option("--api-key <key>", "API key")
17
21
  .option(
@@ -0,0 +1,266 @@
1
+ /**
2
+ * CLI-Anything commands — discover & register CLI-Anything generated tools
3
+ * chainlesschain cli-anything doctor|scan|register|list|remove
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
9
+ import {
10
+ ensureCliAnythingTables,
11
+ detectPython,
12
+ detectCliAnything,
13
+ scanPathForTools,
14
+ parseToolHelp,
15
+ registerTool,
16
+ removeTool,
17
+ listTools,
18
+ } from "../lib/cli-anything-bridge.js";
19
+
20
+ export function registerCliAnythingCommand(program) {
21
+ const cliAny = program
22
+ .command("cli-anything")
23
+ .description(
24
+ "CLI-Anything — discover and register Agent-native CLI tools as skills",
25
+ );
26
+
27
+ /* ---- doctor ---- */
28
+ cliAny
29
+ .command("doctor")
30
+ .description("Check Python & CLI-Anything environment")
31
+ .option("--json", "Output as JSON")
32
+ .action(async (opts) => {
33
+ const py = detectPython();
34
+ const clia = py.found ? detectCliAnything() : { installed: false };
35
+ const tools = scanPathForTools();
36
+
37
+ const report = {
38
+ python: py,
39
+ cliAnything: clia,
40
+ toolsOnPath: tools.length,
41
+ };
42
+
43
+ if (opts.json) {
44
+ console.log(JSON.stringify(report, null, 2));
45
+ return;
46
+ }
47
+
48
+ logger.log("");
49
+ logger.log(chalk.bold(" CLI-Anything Environment"));
50
+ logger.log("");
51
+
52
+ // Python
53
+ if (py.found) {
54
+ logger.log(
55
+ ` ${chalk.green("✓")} Python ${chalk.cyan(py.version)} (${py.command})`,
56
+ );
57
+ } else {
58
+ logger.log(` ${chalk.red("✗")} Python not found`);
59
+ }
60
+
61
+ // CLI-Anything
62
+ if (clia.installed) {
63
+ logger.log(
64
+ ` ${chalk.green("✓")} CLI-Anything ${chalk.cyan(clia.version)}`,
65
+ );
66
+ } else {
67
+ logger.log(` ${chalk.red("✗")} CLI-Anything not installed`);
68
+ if (py.found) {
69
+ logger.log(
70
+ ` ${chalk.gray(`Install: ${py.command} -m pip install cli-anything`)}`,
71
+ );
72
+ }
73
+ }
74
+
75
+ // Tools
76
+ logger.log(
77
+ ` ${tools.length > 0 ? chalk.green("✓") : chalk.yellow("○")} ${tools.length} tool(s) on PATH`,
78
+ );
79
+ for (const t of tools) {
80
+ logger.log(
81
+ ` ${chalk.gray(`cli-anything-${t.name}`)} → ${chalk.gray(t.path)}`,
82
+ );
83
+ }
84
+ logger.log("");
85
+ });
86
+
87
+ /* ---- scan ---- */
88
+ cliAny
89
+ .command("scan")
90
+ .description("Scan PATH for cli-anything-* tools")
91
+ .option("--json", "Output as JSON")
92
+ .action(async (opts) => {
93
+ const tools = scanPathForTools();
94
+
95
+ if (opts.json) {
96
+ console.log(JSON.stringify(tools, null, 2));
97
+ return;
98
+ }
99
+
100
+ if (tools.length === 0) {
101
+ logger.info("No cli-anything-* tools found on PATH.");
102
+ logger.log(
103
+ chalk.gray(
104
+ " Use CLI-Anything to generate tools first: /cli-anything <software>",
105
+ ),
106
+ );
107
+ return;
108
+ }
109
+
110
+ logger.log("");
111
+ logger.log(chalk.bold(` Found ${tools.length} tool(s):`));
112
+ logger.log("");
113
+ for (const t of tools) {
114
+ const help = parseToolHelp(t.command);
115
+ logger.log(` ${chalk.cyan(t.name)}`);
116
+ logger.log(` Command: ${chalk.gray(t.command)}`);
117
+ logger.log(` Path: ${chalk.gray(t.path)}`);
118
+ if (help.description) {
119
+ logger.log(` Desc: ${chalk.gray(help.description)}`);
120
+ }
121
+ if (help.subcommands.length > 0) {
122
+ logger.log(
123
+ ` Subs: ${chalk.gray(help.subcommands.map((s) => s.name).join(", "))}`,
124
+ );
125
+ }
126
+ }
127
+ logger.log("");
128
+ });
129
+
130
+ /* ---- register ---- */
131
+ cliAny
132
+ .command("register <name>")
133
+ .description("Register a cli-anything-* tool as a ChainlessChain skill")
134
+ .option("--force", "Overwrite existing registration")
135
+ .option("--json", "Output as JSON")
136
+ .action(async (name, opts) => {
137
+ try {
138
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
139
+ if (!ctx.db) {
140
+ logger.error(
141
+ "Database not available. Run `chainlesschain setup` first.",
142
+ );
143
+ process.exit(1);
144
+ }
145
+ const db = ctx.db.getDatabase();
146
+ ensureCliAnythingTables(db);
147
+
148
+ const command = `cli-anything-${name}`;
149
+ const helpData = parseToolHelp(command);
150
+
151
+ const result = registerTool(db, name, {
152
+ command,
153
+ helpData,
154
+ force: opts.force,
155
+ });
156
+
157
+ await shutdown();
158
+
159
+ if (opts.json) {
160
+ console.log(JSON.stringify(result, null, 2));
161
+ return;
162
+ }
163
+
164
+ logger.success(
165
+ `Registered ${chalk.cyan(name)} as skill ${chalk.bold(result.skillName)}`,
166
+ );
167
+ logger.log(` Skill dir: ${chalk.gray(result.dir)}`);
168
+ if (result.subcommands.length > 0) {
169
+ logger.log(
170
+ ` Subcommands: ${chalk.gray(result.subcommands.map((s) => s.name).join(", "))}`,
171
+ );
172
+ }
173
+ logger.log(
174
+ chalk.gray(
175
+ ` Use in Agent: /skill ${result.skillName} <subcommand> [args]`,
176
+ ),
177
+ );
178
+ } catch (err) {
179
+ logger.error(`Register failed: ${err.message}`);
180
+ process.exit(1);
181
+ }
182
+ });
183
+
184
+ /* ---- list (default) ---- */
185
+ cliAny
186
+ .command("list", { isDefault: true })
187
+ .description("List registered CLI-Anything tools")
188
+ .option("--json", "Output as JSON")
189
+ .action(async (opts) => {
190
+ try {
191
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
192
+ if (!ctx.db) {
193
+ logger.error("Database not available.");
194
+ process.exit(1);
195
+ }
196
+ const db = ctx.db.getDatabase();
197
+ ensureCliAnythingTables(db);
198
+
199
+ const tools = listTools(db);
200
+ await shutdown();
201
+
202
+ if (opts.json) {
203
+ console.log(JSON.stringify(tools, null, 2));
204
+ return;
205
+ }
206
+
207
+ if (tools.length === 0) {
208
+ logger.info("No CLI-Anything tools registered.");
209
+ logger.log(
210
+ chalk.gray(
211
+ " Run `chainlesschain cli-anything scan` to discover tools.",
212
+ ),
213
+ );
214
+ return;
215
+ }
216
+
217
+ logger.log("");
218
+ logger.log(chalk.bold(` ${tools.length} registered tool(s):`));
219
+ logger.log("");
220
+ for (const t of tools) {
221
+ const statusColor =
222
+ t.status === "registered" ? chalk.green : chalk.yellow;
223
+ logger.log(
224
+ ` ${chalk.cyan(t.name)} ${statusColor(`[${t.status}]`)} → ${chalk.gray(t.skill_name)}`,
225
+ );
226
+ if (t.description) {
227
+ logger.log(` ${chalk.gray(t.description)}`);
228
+ }
229
+ }
230
+ logger.log("");
231
+ } catch (err) {
232
+ logger.error(`List failed: ${err.message}`);
233
+ process.exit(1);
234
+ }
235
+ });
236
+
237
+ /* ---- remove ---- */
238
+ cliAny
239
+ .command("remove <name>")
240
+ .description("Remove a registered CLI-Anything tool")
241
+ .option("--json", "Output as JSON")
242
+ .action(async (name, opts) => {
243
+ try {
244
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
245
+ if (!ctx.db) {
246
+ logger.error("Database not available.");
247
+ process.exit(1);
248
+ }
249
+ const db = ctx.db.getDatabase();
250
+ ensureCliAnythingTables(db);
251
+
252
+ const result = removeTool(db, name);
253
+ await shutdown();
254
+
255
+ if (opts.json) {
256
+ console.log(JSON.stringify(result, null, 2));
257
+ return;
258
+ }
259
+
260
+ logger.success(`Removed tool ${chalk.cyan(name)}`);
261
+ } catch (err) {
262
+ logger.error(`Remove failed: ${err.message}`);
263
+ process.exit(1);
264
+ }
265
+ });
266
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * serve command — start a WebSocket server for remote CLI access
3
+ * chainlesschain serve [--port] [--host] [--token] [--max-connections] [--timeout] [--allow-remote]
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { ChainlessChainWSServer } from "../lib/ws-server.js";
9
+
10
+ export function registerServeCommand(program) {
11
+ program
12
+ .command("serve")
13
+ .description("Start WebSocket server for remote CLI access")
14
+ .option("-p, --port <port>", "Port number", "18800")
15
+ .option("-H, --host <host>", "Bind host", "127.0.0.1")
16
+ .option(
17
+ "--token <token>",
18
+ "Authentication token (required for remote access)",
19
+ )
20
+ .option("--max-connections <n>", "Maximum concurrent connections", "10")
21
+ .option(
22
+ "--timeout <ms>",
23
+ "Command execution timeout in milliseconds",
24
+ "30000",
25
+ )
26
+ .option(
27
+ "--allow-remote",
28
+ "Allow non-localhost connections (requires --token)",
29
+ )
30
+ .action(async (opts) => {
31
+ const port = parseInt(opts.port, 10);
32
+ const maxConnections = parseInt(opts.maxConnections, 10);
33
+ const timeout = parseInt(opts.timeout, 10);
34
+ let host = opts.host;
35
+
36
+ // Validation
37
+ if (isNaN(port) || port < 1 || port > 65535) {
38
+ logger.error("Invalid port number. Must be between 1 and 65535.");
39
+ process.exit(1);
40
+ }
41
+
42
+ if (opts.allowRemote) {
43
+ if (!opts.token) {
44
+ logger.error("--allow-remote requires --token for security.");
45
+ process.exit(1);
46
+ }
47
+ host = "0.0.0.0";
48
+ }
49
+
50
+ const server = new ChainlessChainWSServer({
51
+ port,
52
+ host,
53
+ token: opts.token || null,
54
+ maxConnections,
55
+ timeout,
56
+ });
57
+
58
+ // Event logging
59
+ server.on("connection", ({ clientId, ip }) => {
60
+ logger.log(chalk.green(` + Client connected: ${clientId} (${ip})`));
61
+ });
62
+
63
+ server.on("disconnection", ({ clientId, reason }) => {
64
+ const extra = reason ? ` (${reason})` : "";
65
+ logger.log(
66
+ chalk.yellow(` - Client disconnected: ${clientId}${extra}`),
67
+ );
68
+ });
69
+
70
+ server.on("command:start", ({ id, command }) => {
71
+ logger.log(chalk.cyan(` > [${id}] ${command}`));
72
+ });
73
+
74
+ server.on("command:end", ({ id, exitCode }) => {
75
+ const color = exitCode === 0 ? chalk.green : chalk.red;
76
+ logger.log(color(` < [${id}] exit ${exitCode}`));
77
+ });
78
+
79
+ // Graceful shutdown
80
+ const shutdown = async () => {
81
+ logger.log("\n" + chalk.yellow("Shutting down WebSocket server..."));
82
+ await server.stop();
83
+ process.exit(0);
84
+ };
85
+
86
+ process.on("SIGINT", shutdown);
87
+ process.on("SIGTERM", shutdown);
88
+
89
+ try {
90
+ await server.start();
91
+
92
+ logger.log("");
93
+ logger.log(chalk.bold(" ChainlessChain WebSocket Server"));
94
+ logger.log("");
95
+ logger.log(` Address: ${chalk.cyan(`ws://${host}:${port}`)}`);
96
+ logger.log(
97
+ ` Auth: ${opts.token ? chalk.green("enabled") : chalk.yellow("disabled")}`,
98
+ );
99
+ logger.log(` Max conn: ${maxConnections}`);
100
+ logger.log(` Timeout: ${timeout}ms`);
101
+ logger.log("");
102
+ logger.log(chalk.dim(" Press Ctrl+C to stop"));
103
+ logger.log("");
104
+ } catch (err) {
105
+ logger.error(`Failed to start server: ${err.message}`);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ }
package/src/constants.js CHANGED
@@ -23,6 +23,7 @@ export const DEFAULT_PORTS = {
23
23
  redis: 6379,
24
24
  projectService: 9090,
25
25
  aiService: 8001,
26
+ wsServer: 18800,
26
27
  };
27
28
 
28
29
  export const LLM_PROVIDERS = {
package/src/index.js CHANGED
@@ -79,6 +79,12 @@ import { registerLowcodeCommand } from "./commands/lowcode.js";
79
79
  // EvoMap: Gene Exchange Protocol
80
80
  import { registerEvoMapCommand } from "./commands/evomap.js";
81
81
 
82
+ // CLI-Anything: Agent-Native Software Integration
83
+ import { registerCliAnythingCommand } from "./commands/cli-anything.js";
84
+
85
+ // WebSocket Server Interface
86
+ import { registerServeCommand } from "./commands/serve.js";
87
+
82
88
  export function createProgram() {
83
89
  const program = new Command();
84
90
 
@@ -187,5 +193,11 @@ export function createProgram() {
187
193
  // EvoMap: Gene Exchange Protocol
188
194
  registerEvoMapCommand(program);
189
195
 
196
+ // CLI-Anything: Agent-Native Software Integration
197
+ registerCliAnythingCommand(program);
198
+
199
+ // WebSocket Server Interface
200
+ registerServeCommand(program);
201
+
190
202
  return program;
191
203
  }