@dreamlogic-ai/cli 1.0.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/commands/catalog.d.ts +1 -0
- package/dist/commands/catalog.js +39 -0
- package/dist/commands/helpers.d.ts +2 -0
- package/dist/commands/helpers.js +15 -0
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +138 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +31 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +70 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +19 -0
- package/dist/commands/rollback.d.ts +1 -0
- package/dist/commands/rollback.js +40 -0
- package/dist/commands/setup-mcp.d.ts +3 -0
- package/dist/commands/setup-mcp.js +204 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +69 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +155 -0
- package/dist/lib/api-client.d.ts +25 -0
- package/dist/lib/api-client.js +132 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +121 -0
- package/dist/lib/installer.d.ts +14 -0
- package/dist/lib/installer.js +283 -0
- package/dist/lib/ui.d.ts +39 -0
- package/dist/lib/ui.js +89 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +7 -0
- package/package.json +49 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setup-mcp command — auto-configure MCP for AI agents
|
|
3
|
+
* D-11: Shows diff + confirmation before writing; supports --dry-run
|
|
4
|
+
* R1-02: execFileSync instead of execSync (no shell injection)
|
|
5
|
+
* R1-01: Config files written with 0o600 permissions
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir, platform } from "os";
|
|
10
|
+
import { execFileSync } from "child_process";
|
|
11
|
+
import { confirm } from "@inquirer/prompts";
|
|
12
|
+
import { getServer, maskKey } from "../lib/config.js";
|
|
13
|
+
import { ui } from "../lib/ui.js";
|
|
14
|
+
import { requireAuth } from "./helpers.js";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
/** R1-01: Mask key in displayed config JSON */
|
|
17
|
+
function maskConfigJson(config, key) {
|
|
18
|
+
return JSON.stringify(config, null, 2).replace(new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), "g"), maskKey(key));
|
|
19
|
+
}
|
|
20
|
+
/** Write config file with restrictive permissions (R1-01) */
|
|
21
|
+
function writeConfigSafe(path, data) {
|
|
22
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
23
|
+
writeFileSync(path, data, { mode: 0o600 });
|
|
24
|
+
try {
|
|
25
|
+
chmodSync(path, 0o600);
|
|
26
|
+
}
|
|
27
|
+
catch { /* Windows fallback */ }
|
|
28
|
+
}
|
|
29
|
+
function getAgents(server, key) {
|
|
30
|
+
const isWin = platform() === "win32";
|
|
31
|
+
const home = homedir();
|
|
32
|
+
const claudeDesktopPath = isWin
|
|
33
|
+
? join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json")
|
|
34
|
+
: join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
name: "Claude Desktop",
|
|
38
|
+
configPath: claudeDesktopPath,
|
|
39
|
+
detect: () => existsSync(claudeDesktopPath) || existsSync(join(claudeDesktopPath, "..")),
|
|
40
|
+
generate: () => ({
|
|
41
|
+
mcpServers: {
|
|
42
|
+
"dreamlogic-skills": {
|
|
43
|
+
url: `${server}/sse?api_key=${key}`,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
apply: (newSection) => {
|
|
48
|
+
let existing = {};
|
|
49
|
+
if (existsSync(claudeDesktopPath)) {
|
|
50
|
+
try {
|
|
51
|
+
existing = JSON.parse(readFileSync(claudeDesktopPath, "utf-8"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// R2-07: Backup corrupted config instead of silently discarding
|
|
55
|
+
const bakPath = claudeDesktopPath + ".bak";
|
|
56
|
+
writeFileSync(bakPath, readFileSync(claudeDesktopPath));
|
|
57
|
+
ui.warning(`Existing config has invalid JSON — backed up to ${bakPath}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const merged = {
|
|
61
|
+
...existing,
|
|
62
|
+
mcpServers: {
|
|
63
|
+
...(existing.mcpServers || {}),
|
|
64
|
+
...(newSection.mcpServers || {}),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
writeConfigSafe(claudeDesktopPath, JSON.stringify(merged, null, 2) + "\n");
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Claude CLI",
|
|
72
|
+
configPath: "(uses `claude mcp add` command)",
|
|
73
|
+
// R1-18: Platform-specific detection
|
|
74
|
+
detect: () => {
|
|
75
|
+
try {
|
|
76
|
+
if (isWin) {
|
|
77
|
+
execFileSync("where", ["claude"], { stdio: "ignore" });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
execFileSync("sh", ["-c", "command -v claude"], { stdio: "ignore" });
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
generate: () => ({
|
|
89
|
+
// R1-02: Store args as array, not interpolated string
|
|
90
|
+
args: ["mcp", "add", "dreamlogic-skills", "--url", `${server}/sse?api_key=${key}`],
|
|
91
|
+
}),
|
|
92
|
+
apply: (config) => {
|
|
93
|
+
// R1-02: execFileSync with arg array — no shell injection possible
|
|
94
|
+
execFileSync("claude", config.args, { stdio: "inherit" });
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "Cursor",
|
|
99
|
+
configPath: join(home, ".cursor", "mcp.json"),
|
|
100
|
+
detect: () => existsSync(join(home, ".cursor")),
|
|
101
|
+
generate: () => ({
|
|
102
|
+
mcpServers: {
|
|
103
|
+
"dreamlogic-skills": {
|
|
104
|
+
url: `${server}/sse?api_key=${key}`,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
apply: (newSection) => {
|
|
109
|
+
const path = join(home, ".cursor", "mcp.json");
|
|
110
|
+
let existing = {};
|
|
111
|
+
if (existsSync(path)) {
|
|
112
|
+
try {
|
|
113
|
+
existing = JSON.parse(readFileSync(path, "utf-8"));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// R2-07: Backup corrupted config
|
|
117
|
+
const bakPath = path + ".bak";
|
|
118
|
+
writeFileSync(bakPath, readFileSync(path));
|
|
119
|
+
ui.warning(`Existing config has invalid JSON — backed up to ${bakPath}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const merged = {
|
|
123
|
+
...existing,
|
|
124
|
+
mcpServers: {
|
|
125
|
+
...(existing.mcpServers || {}),
|
|
126
|
+
...(newSection.mcpServers || {}),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
writeConfigSafe(path, JSON.stringify(merged, null, 2) + "\n");
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
export async function setupMcpCommand(opts) {
|
|
135
|
+
const apiKey = requireAuth();
|
|
136
|
+
if (!apiKey)
|
|
137
|
+
return;
|
|
138
|
+
const server = getServer();
|
|
139
|
+
const agents = getAgents(server, apiKey);
|
|
140
|
+
console.log();
|
|
141
|
+
ui.header("MCP Agent Configuration");
|
|
142
|
+
console.log();
|
|
143
|
+
// Detect installed agents
|
|
144
|
+
const detected = agents.filter((a) => a.detect());
|
|
145
|
+
const notDetected = agents.filter((a) => !a.detect());
|
|
146
|
+
if (detected.length > 0) {
|
|
147
|
+
ui.ok(`Detected ${detected.length} agent(s):`);
|
|
148
|
+
for (const a of detected)
|
|
149
|
+
ui.line(` ${chalk.green("●")} ${a.name}`);
|
|
150
|
+
}
|
|
151
|
+
if (notDetected.length > 0) {
|
|
152
|
+
ui.line(` ${ui.dim("Not detected: " + notDetected.map((a) => a.name).join(", "))}`);
|
|
153
|
+
}
|
|
154
|
+
if (detected.length === 0) {
|
|
155
|
+
ui.warning("No supported agents detected.");
|
|
156
|
+
ui.info("Supported agents: " + agents.map((a) => a.name).join(", "));
|
|
157
|
+
console.log();
|
|
158
|
+
ui.info("Manual MCP configuration:");
|
|
159
|
+
ui.line(` Server URL: ${server}/sse`);
|
|
160
|
+
ui.line(` API Key: ${maskKey(apiKey)}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// R1-01: Warn about key in MCP URL (inherent to MCP SSE protocol)
|
|
164
|
+
ui.warning("MCP config will contain your API key in the URL (required by MCP protocol).");
|
|
165
|
+
ui.line(` Config files are written with restricted permissions (owner-only).`);
|
|
166
|
+
// Configure each detected agent
|
|
167
|
+
for (const agent of detected) {
|
|
168
|
+
console.log();
|
|
169
|
+
ui.line(`${chalk.bold(agent.name)}:`);
|
|
170
|
+
const config = agent.generate(server, apiKey);
|
|
171
|
+
// D-11: Show what will be written (R1-01: mask key in display)
|
|
172
|
+
if (agent.name === "Claude CLI") {
|
|
173
|
+
const maskedCmd = `claude mcp add dreamlogic-skills --url "${server}/sse?api_key=${maskKey(apiKey)}"`;
|
|
174
|
+
ui.line(` Command: ${chalk.cyan(maskedCmd)}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
ui.line(` Config: ${agent.configPath}`);
|
|
178
|
+
ui.line(` ${chalk.dim(maskConfigJson(config, apiKey).split("\n").join("\n "))}`);
|
|
179
|
+
}
|
|
180
|
+
if (opts.dryRun) {
|
|
181
|
+
ui.info("(dry-run — no changes made)");
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const ok = await confirm({
|
|
185
|
+
message: `Apply configuration for ${agent.name}?`,
|
|
186
|
+
default: true,
|
|
187
|
+
});
|
|
188
|
+
if (!ok) {
|
|
189
|
+
ui.info("Skipped.");
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
agent.apply(config);
|
|
194
|
+
ui.ok(`${agent.name} configured`);
|
|
195
|
+
if (agent.name === "Claude Desktop") {
|
|
196
|
+
ui.info("Restart Claude Desktop to apply changes.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
ui.err(`Failed: ${err.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
ui.goodbye();
|
|
204
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function statusCommand(): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status command — show full status overview
|
|
3
|
+
*/
|
|
4
|
+
import { ApiClient } from "../lib/api-client.js";
|
|
5
|
+
import { getApiKey, getServer, getInstallDir, loadInstalled, loadConfig, maskKey, } from "../lib/config.js";
|
|
6
|
+
import { ui } from "../lib/ui.js";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { CLI_VERSION } from "../types.js";
|
|
9
|
+
export async function statusCommand() {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const installed = loadInstalled();
|
|
12
|
+
console.log();
|
|
13
|
+
ui.header("Dreamlogic CLI Status");
|
|
14
|
+
console.log();
|
|
15
|
+
// Config
|
|
16
|
+
ui.table([
|
|
17
|
+
["CLI Version", CLI_VERSION],
|
|
18
|
+
["Server", config?.server || "(not configured)"],
|
|
19
|
+
["API Key", config?.api_key ? maskKey(config.api_key) : chalk.red("(not set)")],
|
|
20
|
+
["Install Dir", getInstallDir()],
|
|
21
|
+
]);
|
|
22
|
+
// Server health — R2-05: use redirect:"error" like all other fetches
|
|
23
|
+
const server = getServer();
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`${server}/health`, {
|
|
26
|
+
headers: { "User-Agent": "dreamlogic-cli" },
|
|
27
|
+
redirect: "error",
|
|
28
|
+
signal: AbortSignal.timeout(5000),
|
|
29
|
+
});
|
|
30
|
+
const data = (await res.json());
|
|
31
|
+
// R2-09: Basic shape validation
|
|
32
|
+
if (typeof data?.status !== "string" || typeof data?.version !== "string") {
|
|
33
|
+
throw new Error("Invalid health response");
|
|
34
|
+
}
|
|
35
|
+
console.log();
|
|
36
|
+
ui.ok(`Server: ${data.status} (v${data.version})`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
console.log();
|
|
40
|
+
ui.warning("Server unreachable");
|
|
41
|
+
}
|
|
42
|
+
// Installed skills
|
|
43
|
+
const entries = Object.entries(installed);
|
|
44
|
+
console.log();
|
|
45
|
+
ui.line(`📦 Installed: ${entries.length} skill(s)`);
|
|
46
|
+
if (entries.length > 0) {
|
|
47
|
+
// Check for updates if authenticated
|
|
48
|
+
const apiKey = getApiKey();
|
|
49
|
+
let remoteSkills = [];
|
|
50
|
+
if (apiKey) {
|
|
51
|
+
try {
|
|
52
|
+
const client = new ApiClient(server, apiKey);
|
|
53
|
+
remoteSkills = await client.listSkills();
|
|
54
|
+
}
|
|
55
|
+
catch { /* skip remote check */ }
|
|
56
|
+
}
|
|
57
|
+
for (const [id, info] of entries) {
|
|
58
|
+
const remote = remoteSkills.find((s) => s.id === id);
|
|
59
|
+
// R2-06: Normalize version for comparison (strip leading 'v')
|
|
60
|
+
const normalizeVer = (v) => v.replace(/^v/, "");
|
|
61
|
+
const isUpToDate = !remote?.latest_version ||
|
|
62
|
+
normalizeVer(remote.latest_version) === normalizeVer(info.version);
|
|
63
|
+
const icon = isUpToDate ? chalk.green("✓") : chalk.yellow("⬆");
|
|
64
|
+
const update = !isUpToDate ? chalk.yellow(` → ${remote.latest_version}`) : "";
|
|
65
|
+
ui.line(` ${icon} ${chalk.bold(id)} ${info.version}${update}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
ui.goodbye();
|
|
69
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update command — check for and apply skill updates
|
|
3
|
+
*/
|
|
4
|
+
import { confirm, checkbox } from "@inquirer/prompts";
|
|
5
|
+
import { ApiClient } from "../lib/api-client.js";
|
|
6
|
+
import { getServer, loadInstalled } from "../lib/config.js";
|
|
7
|
+
import { installSkill } from "../lib/installer.js";
|
|
8
|
+
import { ui } from "../lib/ui.js";
|
|
9
|
+
import { requireAuth } from "./helpers.js";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
export async function updateCommand(opts) {
|
|
12
|
+
const apiKey = requireAuth();
|
|
13
|
+
if (!apiKey)
|
|
14
|
+
return;
|
|
15
|
+
const installed = loadInstalled();
|
|
16
|
+
const installedIds = Object.keys(installed);
|
|
17
|
+
if (installedIds.length === 0) {
|
|
18
|
+
ui.info("No skills installed. Run: dreamlogic install");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const client = new ApiClient(getServer(), apiKey);
|
|
22
|
+
const spinner = ui.spinner("Checking for updates...");
|
|
23
|
+
spinner.start();
|
|
24
|
+
let skills;
|
|
25
|
+
try {
|
|
26
|
+
skills = await client.listSkills();
|
|
27
|
+
spinner.stop();
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
spinner.fail(" Failed to check updates");
|
|
31
|
+
ui.err(err.message);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Compare versions
|
|
36
|
+
const updates = [];
|
|
37
|
+
console.log();
|
|
38
|
+
for (const id of installedIds) {
|
|
39
|
+
const local = installed[id];
|
|
40
|
+
const remote = skills.find((s) => s.id === id);
|
|
41
|
+
if (!remote) {
|
|
42
|
+
ui.line(` ${ui.warn("?")} ${id} — not found on server (removed?)`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!remote.latest_version) {
|
|
46
|
+
ui.line(` ${ui.dim("–")} ${remote.name} — no version info`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// R1-16: Normalize version comparison (strip leading 'v')
|
|
50
|
+
const normalizeVer = (v) => v.replace(/^v/, "");
|
|
51
|
+
if (normalizeVer(local.version) === normalizeVer(remote.latest_version)) {
|
|
52
|
+
ui.line(` ${chalk.green("✓")} ${remote.name} ${local.version} — up to date`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
ui.line(` ${chalk.yellow("⬆")} ${chalk.bold(remote.name)} ${local.version} → ${chalk.green(remote.latest_version)}`);
|
|
56
|
+
updates.push({
|
|
57
|
+
id: remote.id,
|
|
58
|
+
name: remote.name,
|
|
59
|
+
currentVersion: local.version,
|
|
60
|
+
latestVersion: remote.latest_version,
|
|
61
|
+
packageFile: remote.package_file || `${remote.id}-${remote.latest_version}.zip`,
|
|
62
|
+
sha256: remote.package_sha256,
|
|
63
|
+
size: remote.package_size,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (updates.length === 0) {
|
|
68
|
+
console.log();
|
|
69
|
+
ui.ok("All skills are up to date! ✨");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log();
|
|
73
|
+
ui.info(`${updates.length} update(s) available`);
|
|
74
|
+
// Select which to update
|
|
75
|
+
let selectedIds;
|
|
76
|
+
if (opts.yes) {
|
|
77
|
+
selectedIds = updates.map((u) => u.id);
|
|
78
|
+
}
|
|
79
|
+
else if (updates.length === 1) {
|
|
80
|
+
const ok = await confirm({
|
|
81
|
+
message: `Update ${updates[0].name} to ${updates[0].latestVersion}?`,
|
|
82
|
+
default: true,
|
|
83
|
+
});
|
|
84
|
+
selectedIds = ok ? [updates[0].id] : [];
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
selectedIds = await checkbox({
|
|
88
|
+
message: "Select updates to apply:",
|
|
89
|
+
choices: updates.map((u) => ({
|
|
90
|
+
name: `${u.name} ${u.currentVersion} → ${u.latestVersion}${u.size ? ` (${ui.fileSize(u.size)})` : ""}`,
|
|
91
|
+
value: u.id,
|
|
92
|
+
checked: true,
|
|
93
|
+
})),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (selectedIds.length === 0) {
|
|
97
|
+
ui.info("No updates selected.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Apply updates
|
|
101
|
+
let successCount = 0;
|
|
102
|
+
for (const id of selectedIds) {
|
|
103
|
+
const u = updates.find((u) => u.id === id);
|
|
104
|
+
if (!u)
|
|
105
|
+
continue; // R1-19
|
|
106
|
+
console.log();
|
|
107
|
+
ui.header(`Updating ${u.name}`);
|
|
108
|
+
ui.line(` ${u.currentVersion} → ${chalk.green(u.latestVersion)}`);
|
|
109
|
+
try {
|
|
110
|
+
await installSkill(client, u.id, u.packageFile, u.sha256, u.latestVersion);
|
|
111
|
+
ui.ok(`Updated to ${u.latestVersion}`);
|
|
112
|
+
successCount++;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
ui.err(`Failed: ${err.message}`);
|
|
116
|
+
ui.info(`Rollback with: dreamlogic rollback ${u.id}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
if (successCount === selectedIds.length) {
|
|
121
|
+
ui.ok(`All ${successCount} update(s) applied! 🎉`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
ui.warning(`${successCount}/${selectedIds.length} updated. Check errors above.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dreamlogic CLI — AI Skill Manager
|
|
4
|
+
* by Dreamlogic-ai · MAJORNINE
|
|
5
|
+
*
|
|
6
|
+
* Install, update and manage AI agent skills for your team.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { select } from "@inquirer/prompts";
|
|
10
|
+
import { CLI_VERSION, CLI_NAME, CLI_AUTHOR } from "./types.js";
|
|
11
|
+
import { ui } from "./lib/ui.js";
|
|
12
|
+
import { getApiKey } from "./lib/config.js";
|
|
13
|
+
// Commands
|
|
14
|
+
import { loginCommand } from "./commands/login.js";
|
|
15
|
+
import { logoutCommand } from "./commands/logout.js";
|
|
16
|
+
import { catalogCommand } from "./commands/catalog.js";
|
|
17
|
+
import { installCommand } from "./commands/install.js";
|
|
18
|
+
import { updateCommand } from "./commands/update.js";
|
|
19
|
+
import { listCommand } from "./commands/list.js";
|
|
20
|
+
import { rollbackCommand } from "./commands/rollback.js";
|
|
21
|
+
import { statusCommand } from "./commands/status.js";
|
|
22
|
+
import { setupMcpCommand } from "./commands/setup-mcp.js";
|
|
23
|
+
const program = new Command();
|
|
24
|
+
program
|
|
25
|
+
.name("dreamlogic")
|
|
26
|
+
.description(`${CLI_NAME} — AI Skill Manager\n${CLI_AUTHOR}`)
|
|
27
|
+
.version(CLI_VERSION, "-v, --version");
|
|
28
|
+
// ===== login =====
|
|
29
|
+
program
|
|
30
|
+
.command("login")
|
|
31
|
+
.description("Authenticate with your Dreamlogic API key")
|
|
32
|
+
.option("-k, --key <key>", "API key (or enter interactively)")
|
|
33
|
+
.action((opts) => loginCommand(opts));
|
|
34
|
+
// ===== logout =====
|
|
35
|
+
program
|
|
36
|
+
.command("logout")
|
|
37
|
+
.description("Clear saved credentials")
|
|
38
|
+
.action(() => logoutCommand());
|
|
39
|
+
// ===== catalog =====
|
|
40
|
+
program
|
|
41
|
+
.command("catalog")
|
|
42
|
+
.description("List available skills from server")
|
|
43
|
+
.action(() => catalogCommand());
|
|
44
|
+
// ===== install =====
|
|
45
|
+
program
|
|
46
|
+
.command("install [skills...]")
|
|
47
|
+
.description("Install skills (interactive if no skill specified)")
|
|
48
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
49
|
+
.option("--from-file <path>", "Install from local ZIP file (D-10)")
|
|
50
|
+
.action((skills, opts) => installCommand(skills, opts));
|
|
51
|
+
// ===== update =====
|
|
52
|
+
program
|
|
53
|
+
.command("update")
|
|
54
|
+
.description("Check for and apply skill updates")
|
|
55
|
+
.option("-y, --yes", "Auto-apply all updates")
|
|
56
|
+
.action((opts) => updateCommand(opts));
|
|
57
|
+
// ===== list =====
|
|
58
|
+
program
|
|
59
|
+
.command("list")
|
|
60
|
+
.alias("ls")
|
|
61
|
+
.description("List locally installed skills")
|
|
62
|
+
.action(() => listCommand());
|
|
63
|
+
// ===== rollback =====
|
|
64
|
+
program
|
|
65
|
+
.command("rollback <skill>")
|
|
66
|
+
.description("Rollback a skill to its previous version (D-13)")
|
|
67
|
+
.action((skill) => rollbackCommand(skill));
|
|
68
|
+
// ===== status =====
|
|
69
|
+
program
|
|
70
|
+
.command("status")
|
|
71
|
+
.description("Show full status overview")
|
|
72
|
+
.action(() => statusCommand());
|
|
73
|
+
// ===== setup-mcp =====
|
|
74
|
+
program
|
|
75
|
+
.command("setup-mcp")
|
|
76
|
+
.description("Auto-configure MCP for AI agents")
|
|
77
|
+
.option("--dry-run", "Show what would be configured without applying")
|
|
78
|
+
.action((opts) => setupMcpCommand(opts));
|
|
79
|
+
// ===== Interactive mode (no command) =====
|
|
80
|
+
program.action(async () => {
|
|
81
|
+
ui.banner();
|
|
82
|
+
const isLoggedIn = !!getApiKey();
|
|
83
|
+
if (!isLoggedIn) {
|
|
84
|
+
ui.info("First time? Let's get you set up!");
|
|
85
|
+
console.log();
|
|
86
|
+
await loginCommand({});
|
|
87
|
+
if (!getApiKey())
|
|
88
|
+
return; // login failed
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
// Interactive menu
|
|
92
|
+
while (true) {
|
|
93
|
+
console.log();
|
|
94
|
+
const action = await select({
|
|
95
|
+
message: "What would you like to do?",
|
|
96
|
+
choices: [
|
|
97
|
+
{ name: "📦 Install skills", value: "install" },
|
|
98
|
+
{ name: "🔄 Check for updates", value: "update" },
|
|
99
|
+
{ name: "📋 List installed skills", value: "list" },
|
|
100
|
+
{ name: "⚙️ Configure MCP for Agent", value: "setup-mcp" },
|
|
101
|
+
{ name: "📊 Status overview", value: "status" },
|
|
102
|
+
{ name: "📚 Browse skill catalog", value: "catalog" },
|
|
103
|
+
{ name: "🚪 Exit", value: "exit" },
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
switch (action) {
|
|
107
|
+
case "install":
|
|
108
|
+
await installCommand([], {});
|
|
109
|
+
break;
|
|
110
|
+
case "update":
|
|
111
|
+
await updateCommand({});
|
|
112
|
+
break;
|
|
113
|
+
case "list":
|
|
114
|
+
listCommand();
|
|
115
|
+
break;
|
|
116
|
+
case "setup-mcp":
|
|
117
|
+
await setupMcpCommand({});
|
|
118
|
+
break;
|
|
119
|
+
case "status":
|
|
120
|
+
await statusCommand();
|
|
121
|
+
break;
|
|
122
|
+
case "catalog":
|
|
123
|
+
await catalogCommand();
|
|
124
|
+
break;
|
|
125
|
+
case "exit":
|
|
126
|
+
ui.goodbye();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// Handle errors gracefully
|
|
132
|
+
program.exitOverride();
|
|
133
|
+
async function main() {
|
|
134
|
+
try {
|
|
135
|
+
await program.parseAsync(process.argv);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
139
|
+
const code = err.code;
|
|
140
|
+
// Commander built-in exit (help, version)
|
|
141
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// User cancelled (Ctrl+C on inquirer)
|
|
146
|
+
if (err && typeof err === "object" && "name" in err && err.name === "ExitPromptError") {
|
|
147
|
+
console.log();
|
|
148
|
+
ui.dim(" Cancelled.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
ui.err(`Unexpected error: ${err.message}`);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
main();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SkillInfo, type UserInfo } from "../types.js";
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
private apiKey;
|
|
5
|
+
constructor(baseUrl: string, apiKey: string);
|
|
6
|
+
private request;
|
|
7
|
+
/** GET /api/me — verify key + get user info */
|
|
8
|
+
me(): Promise<UserInfo>;
|
|
9
|
+
/** GET /skills — list available skills with version info */
|
|
10
|
+
listSkills(): Promise<SkillInfo[]>;
|
|
11
|
+
/** GET /packages/:filename — download package as buffer (R1-11: size limited) */
|
|
12
|
+
downloadPackage(filename: string, onProgress?: (downloaded: number, total: number) => void): Promise<{
|
|
13
|
+
buffer: Buffer;
|
|
14
|
+
sha256: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** GET /health — check server connectivity (R2-09: validated) */
|
|
17
|
+
health(): Promise<{
|
|
18
|
+
status: string;
|
|
19
|
+
version: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export declare class ApiError extends Error {
|
|
23
|
+
status: number;
|
|
24
|
+
constructor(message: string, status: number);
|
|
25
|
+
}
|