@naia-team/cli 0.1.1 → 0.1.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/README.md CHANGED
@@ -12,6 +12,7 @@ npx @naia-team/cli install
12
12
 
13
13
  ```bash
14
14
  npx @naia-team/cli auth login --org-slug <org-slug> --webapp-url <webapp-url>
15
+ npx @naia-team/cli auth logout --profile default
15
16
  ```
16
17
 
17
18
  ## Verify
@@ -33,3 +34,9 @@ npx @naia-team/cli platform invoices issue --invoice-id <id>
33
34
  ```bash
34
35
  npx @naia-team/cli mcp serve
35
36
  ```
37
+
38
+ ## Uninstall
39
+
40
+ ```bash
41
+ npx @naia-team/cli uninstall
42
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naia-team/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
3
  import { URL } from "node:url";
4
- import { writeAuthProfile } from "../lib/auth-store.js";
4
+ import { removeAuthProfile, writeAuthProfile } from "../lib/auth-store.js";
5
5
  import { parseArgVector } from "../lib/args.js";
6
6
  import { readCliConfig } from "../lib/config-store.js";
7
7
 
@@ -27,6 +27,18 @@ export async function runAuthLogin(args) {
27
27
  console.log(`memberId: ${callback.memberId}`);
28
28
  }
29
29
 
30
+ export async function runAuthLogout(args) {
31
+ const { options } = parseArgVector(args);
32
+ const defaults = await readCliConfig();
33
+ const profile = options.profile ?? defaults.defaultProfile ?? "default";
34
+ const removed = await removeAuthProfile(profile);
35
+ if (!removed) {
36
+ console.log(`No auth profile found for '${profile}'.`);
37
+ return;
38
+ }
39
+ console.log(`Logged out profile '${profile}'.`);
40
+ }
41
+
30
42
  async function waitForCallback(config, state) {
31
43
  return new Promise((resolve, reject) => {
32
44
  const server = createServer((req, res) => {
@@ -0,0 +1,127 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile, copyFile } from "node:fs/promises";
3
+ import { join, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { parseArgVector } from "../lib/args.js";
6
+ import { ensureDir } from "./utils.js";
7
+
8
+ const ALL_TARGETS = ["codex", "claude-code", "claude-desktop"];
9
+
10
+ export async function runUninstall(args) {
11
+ const { options } = parseArgVector(args);
12
+ const targets = parseTargets(options.targets);
13
+ const selected = targets.length > 0 ? targets : ALL_TARGETS;
14
+ const results = [];
15
+
16
+ for (const target of selected) {
17
+ if (target === "codex") {
18
+ results.push(await uninstallCodex());
19
+ continue;
20
+ }
21
+ if (target === "claude-code") {
22
+ results.push(await uninstallClaudeCode());
23
+ continue;
24
+ }
25
+ if (target === "claude-desktop") {
26
+ results.push(await uninstallClaudeDesktop());
27
+ continue;
28
+ }
29
+ }
30
+
31
+ console.log("Naia CLI uninstall completed.");
32
+ for (const item of results) {
33
+ console.log(`- ${item.target}: ${item.status}${item.detail ? ` -> ${item.detail}` : ""}`);
34
+ }
35
+ console.log("");
36
+ console.log("Optional cleanup:");
37
+ console.log("- npx @naia-team/cli auth logout --profile default");
38
+ }
39
+
40
+ function parseTargets(raw) {
41
+ if (!raw) return [];
42
+ return raw
43
+ .split(",")
44
+ .map((item) => item.trim().toLowerCase())
45
+ .filter((item) => ALL_TARGETS.includes(item));
46
+ }
47
+
48
+ async function uninstallCodex() {
49
+ const filePath = join(homedir(), ".codex", "config.toml");
50
+ if (!existsSync(filePath)) {
51
+ return { target: "codex", status: "skipped", detail: "No ~/.codex/config.toml" };
52
+ }
53
+ const existing = await readFile(filePath, "utf8");
54
+ const cleaned = stripCodexNaiaBlock(existing);
55
+ if (cleaned === existing) {
56
+ return { target: "codex", status: "skipped", detail: "No naia MCP block found." };
57
+ }
58
+ await backupFile(filePath);
59
+ await writeFile(filePath, cleaned.trimEnd() + "\n", "utf8");
60
+ return { target: "codex", status: "removed", detail: filePath };
61
+ }
62
+
63
+ async function uninstallClaudeCode() {
64
+ const remove = await import("node:child_process").then(({ spawnSync }) =>
65
+ spawnSync("claude", ["mcp", "remove", "-s", "user", "naia"], {
66
+ stdio: "pipe",
67
+ encoding: "utf8",
68
+ })
69
+ );
70
+ if (remove.status === 0) {
71
+ return { target: "claude-code", status: "removed", detail: "Removed user-scope MCP server 'naia'." };
72
+ }
73
+ return { target: "claude-code", status: "skipped", detail: "Could not remove (not configured or CLI unavailable)." };
74
+ }
75
+
76
+ async function uninstallClaudeDesktop() {
77
+ const filePath = resolveClaudeDesktopPath();
78
+ if (!existsSync(filePath)) {
79
+ return { target: "claude-desktop", status: "skipped", detail: "No desktop config file found." };
80
+ }
81
+ const existing = JSON.parse(await readFile(filePath, "utf8"));
82
+ if (!existing?.mcpServers?.naia) {
83
+ return { target: "claude-desktop", status: "skipped", detail: "No naia MCP server found." };
84
+ }
85
+ delete existing.mcpServers.naia;
86
+ await ensureDir(dirname(filePath));
87
+ await backupFile(filePath);
88
+ await writeFile(filePath, JSON.stringify(existing, null, 2), "utf8");
89
+ return { target: "claude-desktop", status: "removed", detail: filePath };
90
+ }
91
+
92
+ function resolveClaudeDesktopPath() {
93
+ if (process.platform === "darwin") {
94
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
95
+ }
96
+ if (process.platform === "win32") {
97
+ const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
98
+ return join(appData, "Claude", "claude_desktop_config.json");
99
+ }
100
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
101
+ }
102
+
103
+ function stripCodexNaiaBlock(source) {
104
+ const lines = source.split("\n");
105
+ const result = [];
106
+ let skipping = false;
107
+ for (const line of lines) {
108
+ const trimmed = line.trim();
109
+ if (trimmed === "[mcp_servers.naia]" || trimmed === "[mcp_servers.naia.env]") {
110
+ skipping = true;
111
+ continue;
112
+ }
113
+ if (skipping && trimmed.startsWith("[") && trimmed.endsWith("]")) {
114
+ skipping = false;
115
+ }
116
+ if (!skipping) {
117
+ result.push(line);
118
+ }
119
+ }
120
+ return result.join("\n");
121
+ }
122
+
123
+ async function backupFile(path) {
124
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
125
+ await copyFile(path, `${path}.${timestamp}.bak`);
126
+ }
127
+
@@ -0,0 +1,6 @@
1
+ import { mkdir } from "node:fs/promises";
2
+
3
+ export async function ensureDir(path) {
4
+ await mkdir(path, { recursive: true });
5
+ }
6
+
@@ -39,6 +39,34 @@ export async function writeAuthProfile(input) {
39
39
  await writeFile(filePath, JSON.stringify(next, null, 2), { mode: 0o600 });
40
40
  }
41
41
 
42
+ export async function removeAuthProfile(profile = "default") {
43
+ const filePath = getAuthStorePath();
44
+ const existing = await readStore(filePath);
45
+ if (!existing?.profiles || typeof existing.profiles !== "object") {
46
+ return false;
47
+ }
48
+ const resolvedProfile = profile === "default"
49
+ ? (existing.defaultProfile || "default")
50
+ : profile;
51
+ if (!existing.profiles[resolvedProfile]) {
52
+ return false;
53
+ }
54
+ const nextProfiles = { ...existing.profiles };
55
+ delete nextProfiles[resolvedProfile];
56
+
57
+ const nextDefault = existing.defaultProfile === resolvedProfile
58
+ ? (Object.keys(nextProfiles)[0] ?? "default")
59
+ : (existing.defaultProfile || "default");
60
+
61
+ const next = {
62
+ defaultProfile: nextDefault,
63
+ profiles: nextProfiles,
64
+ };
65
+ await mkdir(dirname(filePath), { recursive: true });
66
+ await writeFile(filePath, JSON.stringify(next, null, 2), { mode: 0o600 });
67
+ return true;
68
+ }
69
+
42
70
  async function readStore(filePath) {
43
71
  try {
44
72
  const raw = await readFile(filePath, "utf8");
package/src/main.js CHANGED
@@ -1,9 +1,10 @@
1
- import { runAuthLogin } from "./commands/auth-login.js";
1
+ import { runAuthLogin, runAuthLogout } from "./commands/auth-login.js";
2
2
  import { runInstall } from "./commands/install.js";
3
3
  import { runDoctor } from "./commands/doctor.js";
4
4
  import { runPlatform } from "./commands/platform.js";
5
5
  import { runMcpServe } from "./commands/mcp-serve.js";
6
6
  import { runSession } from "./commands/session-run.js";
7
+ import { runUninstall } from "./commands/uninstall.js";
7
8
 
8
9
  export async function main() {
9
10
  const args = process.argv.slice(2);
@@ -17,10 +18,18 @@ export async function main() {
17
18
  await runAuthLogin(rest);
18
19
  return;
19
20
  }
21
+ if (group === "auth" && action === "logout") {
22
+ await runAuthLogout(rest);
23
+ return;
24
+ }
20
25
  if (group === "install") {
21
26
  await runInstall([action, ...rest].filter(Boolean));
22
27
  return;
23
28
  }
29
+ if (group === "uninstall") {
30
+ await runUninstall([action, ...rest].filter(Boolean));
31
+ return;
32
+ }
24
33
  if (group === "doctor") {
25
34
  await runDoctor([action, ...rest].filter(Boolean));
26
35
  return;
@@ -49,8 +58,10 @@ export async function main() {
49
58
  function printUsage() {
50
59
  console.log(`Usage:
51
60
  naia install [--targets codex,claude-code,claude-desktop,manual] [--local true]
61
+ naia uninstall [--targets codex,claude-code,claude-desktop]
52
62
  naia doctor [--runtime-url <url>] [--profile default]
53
63
  naia auth login --org-slug <slug> [--webapp-url <url>] [--profile default]
64
+ naia auth logout [--profile default]
54
65
  naia session run --message "hola" [--runtime-url <url>]
55
66
  naia platform invoices list [--runtime-url <url>]
56
67
  naia mcp serve