swarmkit 0.0.1 → 0.0.2
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 +194 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +55 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +100 -0
- package/dist/commands/hive.d.ts +2 -0
- package/dist/commands/hive.js +248 -0
- package/dist/commands/init/phases/configure.d.ts +2 -0
- package/dist/commands/init/phases/configure.js +85 -0
- package/dist/commands/init/phases/global-setup.d.ts +2 -0
- package/dist/commands/init/phases/global-setup.js +81 -0
- package/dist/commands/init/phases/packages.d.ts +2 -0
- package/dist/commands/init/phases/packages.js +30 -0
- package/dist/commands/init/phases/project.d.ts +2 -0
- package/dist/commands/init/phases/project.js +54 -0
- package/dist/commands/init/phases/use-case.d.ts +2 -0
- package/dist/commands/init/phases/use-case.js +41 -0
- package/dist/commands/init/state.d.ts +11 -0
- package/dist/commands/init/state.js +8 -0
- package/dist/commands/init/state.test.d.ts +1 -0
- package/dist/commands/init/state.test.js +20 -0
- package/dist/commands/init/wizard.d.ts +1 -0
- package/dist/commands/init/wizard.js +56 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +10 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +91 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +19 -0
- package/dist/commands/remove.d.ts +2 -0
- package/dist/commands/remove.js +49 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +87 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +54 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +40 -0
- package/dist/config/global.d.ts +24 -0
- package/dist/config/global.js +71 -0
- package/dist/config/global.test.d.ts +1 -0
- package/dist/config/global.test.js +167 -0
- package/dist/config/keys.d.ts +10 -0
- package/dist/config/keys.js +47 -0
- package/dist/config/keys.test.d.ts +1 -0
- package/dist/config/keys.test.js +87 -0
- package/dist/doctor/checks.d.ts +31 -0
- package/dist/doctor/checks.js +210 -0
- package/dist/doctor/checks.test.d.ts +1 -0
- package/dist/doctor/checks.test.js +276 -0
- package/dist/doctor/types.d.ts +29 -0
- package/dist/doctor/types.js +1 -0
- package/dist/hub/auth-flow.d.ts +16 -0
- package/dist/hub/auth-flow.js +118 -0
- package/dist/hub/auth-flow.test.d.ts +1 -0
- package/dist/hub/auth-flow.test.js +98 -0
- package/dist/hub/client.d.ts +51 -0
- package/dist/hub/client.js +107 -0
- package/dist/hub/client.test.d.ts +1 -0
- package/dist/hub/client.test.js +177 -0
- package/dist/hub/credentials.d.ts +14 -0
- package/dist/hub/credentials.js +41 -0
- package/dist/hub/credentials.test.d.ts +1 -0
- package/dist/hub/credentials.test.js +102 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +9 -2
- package/dist/packages/installer.d.ts +33 -0
- package/dist/packages/installer.js +127 -0
- package/dist/packages/installer.test.d.ts +1 -0
- package/dist/packages/installer.test.js +200 -0
- package/dist/packages/registry.d.ts +37 -0
- package/dist/packages/registry.js +179 -0
- package/dist/packages/registry.test.d.ts +1 -0
- package/dist/packages/registry.test.js +199 -0
- package/dist/packages/setup.d.ts +48 -0
- package/dist/packages/setup.js +309 -0
- package/dist/packages/setup.test.d.ts +1 -0
- package/dist/packages/setup.test.js +717 -0
- package/dist/utils/ui.d.ts +10 -0
- package/dist/utils/ui.js +47 -0
- package/dist/utils/ui.test.d.ts +1 -0
- package/dist/utils/ui.test.js +102 -0
- package/package.json +29 -6
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { select, password } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { readConfig, writeConfig } from "../../../config/global.js";
|
|
4
|
+
import { writeKey } from "../../../config/keys.js";
|
|
5
|
+
import * as ui from "../../../utils/ui.js";
|
|
6
|
+
/** Packages that use embeddings */
|
|
7
|
+
const EMBEDDING_CONSUMERS = ["minimem", "cognitive-core", "skill-tree"];
|
|
8
|
+
export async function configureKeys(state) {
|
|
9
|
+
const updated = { ...state };
|
|
10
|
+
// Check if any installed package uses embeddings
|
|
11
|
+
const needsEmbeddings = state.selectedPackages.some((p) => EMBEDDING_CONSUMERS.includes(p));
|
|
12
|
+
if (needsEmbeddings) {
|
|
13
|
+
console.log();
|
|
14
|
+
const embeddingChoice = await select({
|
|
15
|
+
message: "Embedding provider (used by " +
|
|
16
|
+
state.selectedPackages
|
|
17
|
+
.filter((p) => EMBEDDING_CONSUMERS.includes(p))
|
|
18
|
+
.join(", ") +
|
|
19
|
+
"):",
|
|
20
|
+
choices: [
|
|
21
|
+
{ name: "OpenAI (requires OPENAI_API_KEY)", value: "openai" },
|
|
22
|
+
{
|
|
23
|
+
name: "Gemini (free tier available)",
|
|
24
|
+
value: "gemini",
|
|
25
|
+
},
|
|
26
|
+
{ name: "Local (no API key, uses local models)", value: "local" },
|
|
27
|
+
{ name: "Skip (configure later)", value: null },
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
if (embeddingChoice) {
|
|
31
|
+
updated.embeddingProvider = embeddingChoice;
|
|
32
|
+
// Collect embedding API key if needed
|
|
33
|
+
if (embeddingChoice === "openai") {
|
|
34
|
+
const key = await password({
|
|
35
|
+
message: "OpenAI API key:",
|
|
36
|
+
mask: "*",
|
|
37
|
+
});
|
|
38
|
+
if (key) {
|
|
39
|
+
updated.apiKeys["openai"] = key;
|
|
40
|
+
writeKey("openai", key);
|
|
41
|
+
ui.success("OpenAI API key stored");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (embeddingChoice === "gemini") {
|
|
45
|
+
const key = await password({
|
|
46
|
+
message: "Gemini API key:",
|
|
47
|
+
mask: "*",
|
|
48
|
+
});
|
|
49
|
+
if (key) {
|
|
50
|
+
updated.apiKeys["gemini"] = key;
|
|
51
|
+
writeKey("gemini", key);
|
|
52
|
+
ui.success("Gemini API key stored");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Save embedding preference
|
|
56
|
+
const config = readConfig();
|
|
57
|
+
config.embeddingProvider = embeddingChoice;
|
|
58
|
+
if (embeddingChoice === "openai") {
|
|
59
|
+
config.embeddingModel = "text-embedding-3-small";
|
|
60
|
+
}
|
|
61
|
+
writeConfig(config);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Always ask for Anthropic key (needed for agents)
|
|
65
|
+
console.log();
|
|
66
|
+
const hasAnthropicKey = !!process.env["ANTHROPIC_API_KEY"];
|
|
67
|
+
if (hasAnthropicKey) {
|
|
68
|
+
ui.info(`Anthropic API key detected from environment ${chalk.dim("(ANTHROPIC_API_KEY)")}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const anthropicKey = await password({
|
|
72
|
+
message: "Anthropic API key (for running agents):",
|
|
73
|
+
mask: "*",
|
|
74
|
+
});
|
|
75
|
+
if (anthropicKey) {
|
|
76
|
+
updated.apiKeys["anthropic"] = anthropicKey;
|
|
77
|
+
writeKey("anthropic", anthropicKey);
|
|
78
|
+
ui.success("Anthropic API key stored");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
ui.info("Skipped — you can set ANTHROPIC_API_KEY later.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return updated;
|
|
85
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { input, select } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { GLOBAL_CONFIG_DIRS, isGlobalInit, initGlobalPackage, } from "../../../packages/setup.js";
|
|
4
|
+
import * as ui from "../../../utils/ui.js";
|
|
5
|
+
/** Global packages in setup order */
|
|
6
|
+
const GLOBAL_SETUP_ORDER = ["agent-iam", "skill-tree", "openswarm", "openhive"];
|
|
7
|
+
export async function initGlobal(state) {
|
|
8
|
+
const globalPackages = GLOBAL_SETUP_ORDER.filter((pkg) => state.selectedPackages.includes(pkg));
|
|
9
|
+
if (globalPackages.length === 0)
|
|
10
|
+
return;
|
|
11
|
+
const uninitialized = globalPackages.filter((pkg) => !isGlobalInit(pkg));
|
|
12
|
+
if (uninitialized.length === 0) {
|
|
13
|
+
ui.blank();
|
|
14
|
+
ui.info("All global packages already configured.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
ui.heading(" Global package setup");
|
|
18
|
+
const ctx = {
|
|
19
|
+
packages: state.selectedPackages,
|
|
20
|
+
embeddingProvider: state.embeddingProvider,
|
|
21
|
+
apiKeys: state.apiKeys,
|
|
22
|
+
};
|
|
23
|
+
for (const pkg of uninitialized) {
|
|
24
|
+
let openhiveOpts;
|
|
25
|
+
// Openhive: intercept into the wizard for inline configuration
|
|
26
|
+
if (pkg === "openhive") {
|
|
27
|
+
console.log();
|
|
28
|
+
ui.info("Configuring OpenHive...");
|
|
29
|
+
console.log();
|
|
30
|
+
const name = await input({
|
|
31
|
+
message: "OpenHive instance name:",
|
|
32
|
+
default: "OpenHive",
|
|
33
|
+
});
|
|
34
|
+
const port = await input({
|
|
35
|
+
message: "OpenHive port:",
|
|
36
|
+
default: "3000",
|
|
37
|
+
});
|
|
38
|
+
const verification = await select({
|
|
39
|
+
message: "Registration mode:",
|
|
40
|
+
choices: [
|
|
41
|
+
{ name: "Open — anyone can register", value: "open" },
|
|
42
|
+
{
|
|
43
|
+
name: "Invite — require an invite code",
|
|
44
|
+
value: "invite",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Manual — admin approves each",
|
|
48
|
+
value: "manual",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const authMode = await select({
|
|
53
|
+
message: "Auth mode:",
|
|
54
|
+
choices: [
|
|
55
|
+
{ name: "Local — no login, single-user", value: "local" },
|
|
56
|
+
{
|
|
57
|
+
name: "Token — email/password registration",
|
|
58
|
+
value: "token",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
openhiveOpts = {
|
|
63
|
+
name,
|
|
64
|
+
port: parseInt(port, 10) || 3000,
|
|
65
|
+
authMode,
|
|
66
|
+
verification,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const result = await initGlobalPackage(pkg, ctx, openhiveOpts);
|
|
70
|
+
const configDir = GLOBAL_CONFIG_DIRS[pkg] ?? "";
|
|
71
|
+
const suffix = result.message
|
|
72
|
+
? ` ${chalk.dim(`(${result.message})`)}`
|
|
73
|
+
: "";
|
|
74
|
+
if (result.success) {
|
|
75
|
+
ui.success(`${pkg} ${chalk.dim("→")} ~/${configDir}/${suffix}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
ui.fail(`${pkg}: ${result.message ?? "setup failed"}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { installPackages } from "../../../packages/installer.js";
|
|
3
|
+
import { addInstalledPackages } from "../../../config/global.js";
|
|
4
|
+
import * as ui from "../../../utils/ui.js";
|
|
5
|
+
export async function installSelectedPackages(state) {
|
|
6
|
+
const { selectedPackages } = state;
|
|
7
|
+
console.log();
|
|
8
|
+
console.log(` Installing ${chalk.bold(selectedPackages.length.toString())} packages...`);
|
|
9
|
+
console.log();
|
|
10
|
+
const results = await installPackages(selectedPackages);
|
|
11
|
+
let failures = 0;
|
|
12
|
+
for (const result of results) {
|
|
13
|
+
if (result.success) {
|
|
14
|
+
ui.success(`${result.package} ${result.version ? chalk.dim("v" + result.version) : ""}`);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
ui.fail(`${result.package} — ${result.error}`);
|
|
18
|
+
failures++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Record successful installations
|
|
22
|
+
const installed = results.filter((r) => r.success).map((r) => r.package);
|
|
23
|
+
if (installed.length > 0) {
|
|
24
|
+
addInstalledPackages(installed);
|
|
25
|
+
}
|
|
26
|
+
if (failures > 0) {
|
|
27
|
+
ui.blank();
|
|
28
|
+
ui.warn(`${failures} package${failures === 1 ? "" : "s"} failed to install. Run \`swarmkit doctor\` to diagnose.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { confirm } from "@inquirer/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { PACKAGES } from "../../../packages/registry.js";
|
|
6
|
+
import { PROJECT_CONFIG_DIRS, PROJECT_INIT_ORDER, isProjectInit, initProjectPackage, } from "../../../packages/setup.js";
|
|
7
|
+
import * as ui from "../../../utils/ui.js";
|
|
8
|
+
export async function initProject(state) {
|
|
9
|
+
const cwd = process.cwd();
|
|
10
|
+
// Only offer project init if we're in a directory that looks like a project
|
|
11
|
+
const isProject = existsSync(join(cwd, ".git")) || existsSync(join(cwd, "package.json"));
|
|
12
|
+
if (!isProject) {
|
|
13
|
+
ui.blank();
|
|
14
|
+
ui.info("Run `swarmkit init` inside a project directory to initialize per-project configs.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Filter to packages that have project-level config, in init order
|
|
18
|
+
const projectPackages = PROJECT_INIT_ORDER.filter((pkg) => state.selectedPackages.includes(pkg) &&
|
|
19
|
+
!PACKAGES[pkg]?.globalOnly &&
|
|
20
|
+
pkg in PROJECT_CONFIG_DIRS);
|
|
21
|
+
if (projectPackages.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
// Check which are already initialized
|
|
24
|
+
const uninitialized = projectPackages.filter((pkg) => !isProjectInit(cwd, pkg));
|
|
25
|
+
if (uninitialized.length === 0) {
|
|
26
|
+
ui.blank();
|
|
27
|
+
ui.info("All packages already initialized in this project.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log();
|
|
31
|
+
const shouldInit = await confirm({
|
|
32
|
+
message: `Initialize ${uninitialized.length} package${uninitialized.length === 1 ? "" : "s"} in ${chalk.dim(cwd)}?`,
|
|
33
|
+
default: true,
|
|
34
|
+
});
|
|
35
|
+
if (!shouldInit)
|
|
36
|
+
return;
|
|
37
|
+
const ctx = {
|
|
38
|
+
cwd,
|
|
39
|
+
packages: state.selectedPackages,
|
|
40
|
+
embeddingProvider: state.embeddingProvider,
|
|
41
|
+
apiKeys: state.apiKeys,
|
|
42
|
+
};
|
|
43
|
+
ui.blank();
|
|
44
|
+
for (const pkg of uninitialized) {
|
|
45
|
+
const result = await initProjectPackage(pkg, ctx);
|
|
46
|
+
const configDir = PROJECT_CONFIG_DIRS[pkg] ?? "";
|
|
47
|
+
if (result.success) {
|
|
48
|
+
ui.success(`${pkg} ${chalk.dim("→")} ${configDir}/`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
ui.fail(`${pkg}: ${result.message ?? "initialization failed"}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { select, checkbox } from "@inquirer/prompts";
|
|
2
|
+
import { BUNDLES, PACKAGES, getAllPackageNames } from "../../../packages/registry.js";
|
|
3
|
+
export async function selectUseCase(state) {
|
|
4
|
+
const bundleChoice = await select({
|
|
5
|
+
message: "What are you building?",
|
|
6
|
+
choices: [
|
|
7
|
+
...Object.values(BUNDLES).map((b) => ({
|
|
8
|
+
name: `${b.label} (${b.name} bundle — ${b.packages.length} packages)`,
|
|
9
|
+
value: b.name,
|
|
10
|
+
})),
|
|
11
|
+
{
|
|
12
|
+
name: "Let me pick individually",
|
|
13
|
+
value: "custom",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
if (bundleChoice === "custom") {
|
|
18
|
+
const selected = await checkbox({
|
|
19
|
+
message: "Select packages to install:",
|
|
20
|
+
choices: getAllPackageNames().map((name) => ({
|
|
21
|
+
name: `${name} ${PACKAGES[name].description}`,
|
|
22
|
+
value: name,
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
if (selected.length === 0) {
|
|
26
|
+
console.log("\n No packages selected.\n");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
bundle: "custom",
|
|
32
|
+
selectedPackages: selected,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const bundle = BUNDLES[bundleChoice];
|
|
36
|
+
return {
|
|
37
|
+
...state,
|
|
38
|
+
bundle: bundleChoice,
|
|
39
|
+
selectedPackages: [...bundle.packages],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface WizardState {
|
|
2
|
+
/** Selected bundle name, or "custom" */
|
|
3
|
+
bundle: string;
|
|
4
|
+
/** Packages to install */
|
|
5
|
+
selectedPackages: string[];
|
|
6
|
+
/** Embedding provider choice */
|
|
7
|
+
embeddingProvider: "openai" | "gemini" | "local" | null;
|
|
8
|
+
/** Collected API keys (provider -> key) */
|
|
9
|
+
apiKeys: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createEmptyState(): WizardState;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createEmptyState } from "./state.js";
|
|
3
|
+
describe("WizardState", () => {
|
|
4
|
+
describe("createEmptyState", () => {
|
|
5
|
+
it("returns a state with empty defaults", () => {
|
|
6
|
+
const state = createEmptyState();
|
|
7
|
+
expect(state.bundle).toBe("");
|
|
8
|
+
expect(state.selectedPackages).toEqual([]);
|
|
9
|
+
expect(state.embeddingProvider).toBeNull();
|
|
10
|
+
expect(state.apiKeys).toEqual({});
|
|
11
|
+
});
|
|
12
|
+
it("returns a new object each time", () => {
|
|
13
|
+
const a = createEmptyState();
|
|
14
|
+
const b = createEmptyState();
|
|
15
|
+
expect(a).not.toBe(b);
|
|
16
|
+
expect(a.selectedPackages).not.toBe(b.selectedPackages);
|
|
17
|
+
expect(a.apiKeys).not.toBe(b.apiKeys);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runWizard(): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { isFirstRun, readConfig, ensureConfigDir } from "../../config/global.js";
|
|
3
|
+
import * as ui from "../../utils/ui.js";
|
|
4
|
+
import { getActiveIntegrations } from "../../packages/registry.js";
|
|
5
|
+
import { createEmptyState } from "./state.js";
|
|
6
|
+
import { selectUseCase } from "./phases/use-case.js";
|
|
7
|
+
import { installSelectedPackages } from "./phases/packages.js";
|
|
8
|
+
import { configureKeys } from "./phases/configure.js";
|
|
9
|
+
import { initProject } from "./phases/project.js";
|
|
10
|
+
import { initGlobal } from "./phases/global-setup.js";
|
|
11
|
+
export async function runWizard() {
|
|
12
|
+
console.log();
|
|
13
|
+
console.log(` ${chalk.bold("swarmkit")} ${chalk.dim("— multi-agent infrastructure toolkit")}`);
|
|
14
|
+
console.log();
|
|
15
|
+
if (isFirstRun()) {
|
|
16
|
+
await runFirstTimeSetup();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
await runProjectSetup();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function runFirstTimeSetup() {
|
|
23
|
+
ensureConfigDir();
|
|
24
|
+
// Step 1: Use case / bundle selection
|
|
25
|
+
let state = await selectUseCase(createEmptyState());
|
|
26
|
+
// Step 2: Install packages
|
|
27
|
+
await installSelectedPackages(state);
|
|
28
|
+
// Step 3: Configure (embedding provider, API keys)
|
|
29
|
+
state = await configureKeys(state);
|
|
30
|
+
// Step 4: Global package setup (agent-iam, skill-tree, openswarm, openhive)
|
|
31
|
+
await initGlobal(state);
|
|
32
|
+
// Step 5: Project init (if in a project directory)
|
|
33
|
+
await initProject(state);
|
|
34
|
+
// Summary
|
|
35
|
+
printSummary(state);
|
|
36
|
+
}
|
|
37
|
+
async function runProjectSetup() {
|
|
38
|
+
const config = readConfig();
|
|
39
|
+
ui.info(`Global config found. ${config.installedPackages.length} packages installed.`);
|
|
40
|
+
const state = createEmptyState();
|
|
41
|
+
state.selectedPackages = [...config.installedPackages];
|
|
42
|
+
await initProject(state);
|
|
43
|
+
}
|
|
44
|
+
function printSummary(state) {
|
|
45
|
+
const integrations = getActiveIntegrations(state.selectedPackages);
|
|
46
|
+
if (integrations.length > 0) {
|
|
47
|
+
ui.heading(" Active integrations:");
|
|
48
|
+
for (const integration of integrations) {
|
|
49
|
+
const [a, b] = integration.packages;
|
|
50
|
+
ui.bullet(` ${a} ${chalk.dim("↔")} ${b} ${chalk.dim(integration.description)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
ui.blank();
|
|
54
|
+
console.log(` ${chalk.green("Done.")} Run ${chalk.bold("`swarmkit status`")} to see your setup.`);
|
|
55
|
+
ui.blank();
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function registerInitCommand(program) {
|
|
2
|
+
program
|
|
3
|
+
.command("init")
|
|
4
|
+
.description("Interactive setup wizard")
|
|
5
|
+
.action(async () => {
|
|
6
|
+
// Dynamic import to avoid loading @inquirer/prompts for other commands
|
|
7
|
+
const { runWizard } = await import("./init/wizard.js");
|
|
8
|
+
await runWizard();
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { isLoggedIn } from "../hub/credentials.js";
|
|
3
|
+
import { getMe, HubApiError } from "../hub/client.js";
|
|
4
|
+
import { findAvailablePort, getLoginUrl, runLoginFlow, } from "../hub/auth-flow.js";
|
|
5
|
+
import * as ui from "../utils/ui.js";
|
|
6
|
+
export function registerLoginCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("login")
|
|
9
|
+
.description("Log in to SwarmHub")
|
|
10
|
+
.option("--hub <url>", "SwarmHub URL (default: https://hub.swarmkit.ai)")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
// Set env var so getHubUrl() picks it up for this session
|
|
13
|
+
if (opts.hub) {
|
|
14
|
+
process.env.SWARMKIT_HUB_URL = opts.hub;
|
|
15
|
+
}
|
|
16
|
+
if (isLoggedIn()) {
|
|
17
|
+
try {
|
|
18
|
+
const user = await getMe();
|
|
19
|
+
ui.blank();
|
|
20
|
+
ui.info(`Already logged in as ${chalk.bold(user.name)} (${user.email})`);
|
|
21
|
+
ui.info("Run `swarmkit logout` first to switch accounts.");
|
|
22
|
+
ui.blank();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Token expired or invalid — proceed with login
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
let port;
|
|
30
|
+
try {
|
|
31
|
+
port = await findAvailablePort();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
ui.blank();
|
|
35
|
+
ui.fail("Could not find an available port for the callback server.");
|
|
36
|
+
ui.blank();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const loginUrl = getLoginUrl(port);
|
|
40
|
+
ui.blank();
|
|
41
|
+
ui.info("Opening browser to authenticate with SwarmHub...");
|
|
42
|
+
const opened = await openBrowser(loginUrl);
|
|
43
|
+
if (!opened) {
|
|
44
|
+
ui.blank();
|
|
45
|
+
ui.info("Open this URL in your browser to log in:");
|
|
46
|
+
ui.blank();
|
|
47
|
+
console.log(` ${loginUrl}`);
|
|
48
|
+
}
|
|
49
|
+
ui.blank();
|
|
50
|
+
ui.info("Waiting for authentication...");
|
|
51
|
+
try {
|
|
52
|
+
await runLoginFlow(port);
|
|
53
|
+
const user = await getMe();
|
|
54
|
+
ui.blank();
|
|
55
|
+
ui.success(`Logged in as ${chalk.bold(user.name)} (${user.email})`);
|
|
56
|
+
ui.blank();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
ui.blank();
|
|
60
|
+
if (err instanceof HubApiError) {
|
|
61
|
+
ui.fail(`Login failed: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
else if (err instanceof Error) {
|
|
64
|
+
ui.fail(err.message);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
ui.fail("Login failed.");
|
|
68
|
+
}
|
|
69
|
+
ui.blank();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function openBrowser(url) {
|
|
74
|
+
try {
|
|
75
|
+
const { exec } = await import("node:child_process");
|
|
76
|
+
const { platform } = await import("node:os");
|
|
77
|
+
const cmd = platform() === "darwin"
|
|
78
|
+
? "open"
|
|
79
|
+
: platform() === "win32"
|
|
80
|
+
? "start"
|
|
81
|
+
: "xdg-open";
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
exec(`${cmd} ${JSON.stringify(url)}`, (err) => {
|
|
84
|
+
resolve(!err);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isLoggedIn, deleteCredentials } from "../hub/credentials.js";
|
|
2
|
+
import * as ui from "../utils/ui.js";
|
|
3
|
+
export function registerLogoutCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("logout")
|
|
6
|
+
.description("Log out of SwarmHub")
|
|
7
|
+
.action(() => {
|
|
8
|
+
if (!isLoggedIn()) {
|
|
9
|
+
ui.blank();
|
|
10
|
+
ui.info("Not currently logged in.");
|
|
11
|
+
ui.blank();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
deleteCredentials();
|
|
15
|
+
ui.blank();
|
|
16
|
+
ui.success("Logged out of SwarmHub.");
|
|
17
|
+
ui.blank();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { readConfig, removeInstalledPackage } from "../config/global.js";
|
|
3
|
+
import { getLostIntegrations } from "../packages/registry.js";
|
|
4
|
+
import { uninstallPackage, getInstalledVersion } from "../packages/installer.js";
|
|
5
|
+
import * as ui from "../utils/ui.js";
|
|
6
|
+
export function registerRemoveCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("remove <package>")
|
|
9
|
+
.description("Uninstall a swarmkit package (preserves project data)")
|
|
10
|
+
.action(async (packageName) => {
|
|
11
|
+
const config = readConfig();
|
|
12
|
+
// Check if tracked by swarmkit
|
|
13
|
+
if (!config.installedPackages.includes(packageName)) {
|
|
14
|
+
ui.fail(`${packageName} is not in the swarmkit package list.`);
|
|
15
|
+
ui.info("Run `swarmkit status` to see installed packages.");
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Show what will break
|
|
20
|
+
const lostIntegrations = getLostIntegrations(config.installedPackages, packageName);
|
|
21
|
+
if (lostIntegrations.length > 0) {
|
|
22
|
+
console.log();
|
|
23
|
+
console.log(` ${chalk.bold("Affected integrations:")}`);
|
|
24
|
+
for (const integration of lostIntegrations) {
|
|
25
|
+
const other = integration.packages.find((p) => p !== packageName);
|
|
26
|
+
ui.warn(`${other} — ${integration.description}`);
|
|
27
|
+
}
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
// Uninstall
|
|
31
|
+
console.log(` Uninstalling ${chalk.bold(packageName)}...`);
|
|
32
|
+
const version = await getInstalledVersion(packageName);
|
|
33
|
+
try {
|
|
34
|
+
await uninstallPackage(packageName);
|
|
35
|
+
ui.success(`${packageName}${version ? " v" + version : ""} uninstalled`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39
|
+
ui.fail(`Failed to uninstall: ${msg}`);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Update registry
|
|
44
|
+
removeInstalledPackage(packageName);
|
|
45
|
+
ui.blank();
|
|
46
|
+
ui.info("Project data directories are preserved. Delete them manually if no longer needed.");
|
|
47
|
+
ui.blank();
|
|
48
|
+
});
|
|
49
|
+
}
|