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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -1
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +33 -0
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +55 -0
  7. package/dist/commands/doctor.d.ts +2 -0
  8. package/dist/commands/doctor.js +100 -0
  9. package/dist/commands/hive.d.ts +2 -0
  10. package/dist/commands/hive.js +248 -0
  11. package/dist/commands/init/phases/configure.d.ts +2 -0
  12. package/dist/commands/init/phases/configure.js +85 -0
  13. package/dist/commands/init/phases/global-setup.d.ts +2 -0
  14. package/dist/commands/init/phases/global-setup.js +81 -0
  15. package/dist/commands/init/phases/packages.d.ts +2 -0
  16. package/dist/commands/init/phases/packages.js +30 -0
  17. package/dist/commands/init/phases/project.d.ts +2 -0
  18. package/dist/commands/init/phases/project.js +54 -0
  19. package/dist/commands/init/phases/use-case.d.ts +2 -0
  20. package/dist/commands/init/phases/use-case.js +41 -0
  21. package/dist/commands/init/state.d.ts +11 -0
  22. package/dist/commands/init/state.js +8 -0
  23. package/dist/commands/init/state.test.d.ts +1 -0
  24. package/dist/commands/init/state.test.js +20 -0
  25. package/dist/commands/init/wizard.d.ts +1 -0
  26. package/dist/commands/init/wizard.js +56 -0
  27. package/dist/commands/init.d.ts +2 -0
  28. package/dist/commands/init.js +10 -0
  29. package/dist/commands/login.d.ts +2 -0
  30. package/dist/commands/login.js +91 -0
  31. package/dist/commands/logout.d.ts +2 -0
  32. package/dist/commands/logout.js +19 -0
  33. package/dist/commands/remove.d.ts +2 -0
  34. package/dist/commands/remove.js +49 -0
  35. package/dist/commands/status.d.ts +2 -0
  36. package/dist/commands/status.js +87 -0
  37. package/dist/commands/update.d.ts +2 -0
  38. package/dist/commands/update.js +54 -0
  39. package/dist/commands/whoami.d.ts +2 -0
  40. package/dist/commands/whoami.js +40 -0
  41. package/dist/config/global.d.ts +24 -0
  42. package/dist/config/global.js +71 -0
  43. package/dist/config/global.test.d.ts +1 -0
  44. package/dist/config/global.test.js +167 -0
  45. package/dist/config/keys.d.ts +10 -0
  46. package/dist/config/keys.js +47 -0
  47. package/dist/config/keys.test.d.ts +1 -0
  48. package/dist/config/keys.test.js +87 -0
  49. package/dist/doctor/checks.d.ts +31 -0
  50. package/dist/doctor/checks.js +210 -0
  51. package/dist/doctor/checks.test.d.ts +1 -0
  52. package/dist/doctor/checks.test.js +276 -0
  53. package/dist/doctor/types.d.ts +29 -0
  54. package/dist/doctor/types.js +1 -0
  55. package/dist/hub/auth-flow.d.ts +16 -0
  56. package/dist/hub/auth-flow.js +118 -0
  57. package/dist/hub/auth-flow.test.d.ts +1 -0
  58. package/dist/hub/auth-flow.test.js +98 -0
  59. package/dist/hub/client.d.ts +51 -0
  60. package/dist/hub/client.js +107 -0
  61. package/dist/hub/client.test.d.ts +1 -0
  62. package/dist/hub/client.test.js +177 -0
  63. package/dist/hub/credentials.d.ts +14 -0
  64. package/dist/hub/credentials.js +41 -0
  65. package/dist/hub/credentials.test.d.ts +1 -0
  66. package/dist/hub/credentials.test.js +102 -0
  67. package/dist/index.d.ts +16 -1
  68. package/dist/index.js +9 -2
  69. package/dist/packages/installer.d.ts +33 -0
  70. package/dist/packages/installer.js +127 -0
  71. package/dist/packages/installer.test.d.ts +1 -0
  72. package/dist/packages/installer.test.js +200 -0
  73. package/dist/packages/registry.d.ts +37 -0
  74. package/dist/packages/registry.js +179 -0
  75. package/dist/packages/registry.test.d.ts +1 -0
  76. package/dist/packages/registry.test.js +199 -0
  77. package/dist/packages/setup.d.ts +48 -0
  78. package/dist/packages/setup.js +309 -0
  79. package/dist/packages/setup.test.d.ts +1 -0
  80. package/dist/packages/setup.test.js +717 -0
  81. package/dist/utils/ui.d.ts +10 -0
  82. package/dist/utils/ui.js +47 -0
  83. package/dist/utils/ui.test.d.ts +1 -0
  84. package/dist/utils/ui.test.js +102 -0
  85. 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,2 @@
1
+ import type { WizardState } from "../state.js";
2
+ export declare function initGlobal(state: WizardState): Promise<void>;
@@ -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,2 @@
1
+ import type { WizardState } from "../state.js";
2
+ export declare function installSelectedPackages(state: WizardState): Promise<void>;
@@ -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,2 @@
1
+ import type { WizardState } from "../state.js";
2
+ export declare function initProject(state: WizardState): Promise<void>;
@@ -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,2 @@
1
+ import type { WizardState } from "../state.js";
2
+ export declare function selectUseCase(state: WizardState): Promise<WizardState>;
@@ -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,8 @@
1
+ export function createEmptyState() {
2
+ return {
3
+ bundle: "",
4
+ selectedPackages: [],
5
+ embeddingProvider: null,
6
+ apiKeys: {},
7
+ };
8
+ }
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerLoginCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerLogoutCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerRemoveCommand(program: Command): void;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerStatusCommand(program: Command): void;