noiton 1.0.0 → 2.0.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.
@@ -1,68 +1,80 @@
1
- import { BANNER, color } from "../constants.js";
2
- import { validateKey } from "../lib/api.js";
3
- import {
4
- readConfig,
5
- writeConfig,
6
- hasProvider,
7
- getProvider,
8
- upsertProvider,
9
- buildModels,
10
- } from "../lib/config.js";
11
-
12
1
  /**
13
- * Re-fetch models from API and update config.
2
+ * NOITON refresh command - re-fetch models from the API
14
3
  */
4
+
5
+ import { c } from "../constants.js";
6
+ import { printHeader, spinner, ok, fail, info, box, dim } from "../ui.js";
7
+ import { getConfiguredAgents, getAgent } from "../lib/agents/index.js";
8
+ import { validateKey, maskKey } from "../lib/api.js";
9
+ import { input } from "@inquirer/prompts";
10
+ import { farewellMessage } from "../messages.js";
11
+
15
12
  export async function refreshCommand() {
16
- console.log(BANNER);
13
+ printHeader("Atualizar Modelos");
17
14
 
18
- const { config, path } = readConfig();
15
+ const configured = getConfiguredAgents();
19
16
 
20
- if (!hasProvider(config)) {
21
- console.log(color("NOITON não está configurado.\n", "red"));
22
- console.log(color(" Rode ", "dim") + color("npx noiton", "cyan") + color(" primeiro.\n", "dim"));
23
- process.exit(1);
17
+ if (configured.length === 0) {
18
+ box("NOITON não está configurado em nenhum agente.\n\nRode 'npx noiton' para configurar.", "warning");
19
+ return;
24
20
  }
25
21
 
26
- const provider = getProvider(config);
27
- const apiKey = provider?.options?.apiKey;
22
+ info(`${configured.length} agente(s) encontrado(s): ${configured.map(a => a.name).join(", ")}`);
23
+ console.log();
28
24
 
29
- if (!apiKey) {
30
- console.log(color(" Chave API não encontrada na config.\n", "red"));
31
- process.exit(1);
25
+ // We need the API key to refresh. Try to read from any agent.
26
+ let apiKey = null;
27
+ for (const agent of configured) {
28
+ try {
29
+ const status = agent.getStatus();
30
+ if (status.apiKey && !status.apiKey.startsWith("{env:")) {
31
+ apiKey = status.apiKey;
32
+ break;
33
+ }
34
+ } catch {}
32
35
  }
33
36
 
34
- console.log(color(" ⏳ Buscando modelos atualizados...\n", "dim"));
37
+ if (!apiKey) {
38
+ dim(" Sua chave não pode ser lida. Cole ela:");
39
+ const key = await input({
40
+ message: " Chave NOITON:",
41
+ validate: (v) => (v.trim().length >= 10 ? true : "Chave muito curta"),
42
+ transformer: (v) => maskKey(v),
43
+ });
44
+ apiKey = key.trim();
45
+ }
35
46
 
47
+ const s = spinner("Buscando modelos atualizados da API...");
36
48
  const { valid, models, error } = await validateKey(apiKey);
37
49
 
38
- if (!valid) {
39
- console.log(color(`\n ❌ Erro: ${error}\n`, "red"));
40
- console.log(color(" Dica: rode ", "dim") + color("npx noiton change", "cyan") + color(" para trocar a chave.\n", "dim"));
41
- process.exit(1);
42
- }
43
-
44
- if (!models || models.length === 0) {
45
- console.log(color(" ⚠ Nenhum modelo disponível.\n", "yellow"));
50
+ if (!valid || !models) {
51
+ s.fail(`Erro: ${error || "Resposta inválida"}`);
46
52
  return;
47
53
  }
48
54
 
49
- const oldCount = Object.keys(provider.models || {}).length;
50
- const newModels = buildModels(models);
51
- const newCount = Object.keys(newModels).length;
52
-
53
- console.log(color(" ✓ Modelos atualizados!\n", "green"));
54
- console.log(color(` Antes: ${oldCount} modelo(s) → Agora: ${newCount} modelo(s)\n`, "dim"));
55
+ s.succeed(`${models.length} modelo(s) encontrado(s)!`);
56
+ console.log();
55
57
 
56
- for (const m of models) {
57
- const ctx = m.context_window
58
- ? color(` (${Math.round(m.context_window / 1000)}K ctx)`, "dim")
59
- : "";
60
- console.log(` • ${m.id}${ctx}`);
58
+ let success = 0;
59
+ for (const agent of configured) {
60
+ const sub = spinner(`Atualizando ${agent.name}...`);
61
+ try {
62
+ agent.refresh(models);
63
+ sub.succeed(`${agent.name} atualizado (${models.length} modelos)`);
64
+ success++;
65
+ } catch (err) {
66
+ sub.fail(`${agent.name}: ${err.message}`);
67
+ }
61
68
  }
62
69
 
63
- const newConfig = upsertProvider(config, apiKey, newModels);
64
- writeConfig(newConfig, path);
70
+ console.log();
71
+ box(
72
+ success > 0
73
+ ? `✨ Atualização concluída!\n${success} agente(s) atualizado(s).`
74
+ : "⚠️ Nenhum agente pôde ser atualizado.",
75
+ success > 0 ? "success" : "warning"
76
+ );
65
77
 
66
- console.log(color("\n ✓ Config atualizada!\n", "green"));
67
- console.log(color(" 🚀 Reinicie o OpenCode para usar os modelos atualizados.\n", "cyan"));
78
+ console.log();
79
+ console.log(c.dim(` ${farewellMessage()}`));
68
80
  }
@@ -1,38 +1,62 @@
1
- import { confirm } from "@inquirer/prompts";
2
- import { BANNER, color } from "../constants.js";
3
- import {
4
- readConfig,
5
- writeConfig,
6
- hasProvider,
7
- removeProvider,
8
- } from "../lib/config.js";
9
-
10
1
  /**
11
- * Remove NOITON provider from config.
2
+ * NOITON remove command - remove NOITON provider from agents
12
3
  */
4
+
5
+ import { confirm } from "@inquirer/prompts";
6
+ import { c } from "../constants.js";
7
+ import { printHeader, spinner, ok, fail, info, box, dim } from "../ui.js";
8
+ import { getConfiguredAgents } from "../lib/agents/index.js";
9
+ import { farewellMessage, cancelMessage } from "../messages.js";
10
+
13
11
  export async function removeCommand() {
14
- console.log(BANNER);
12
+ printHeader("Remover NOITON");
15
13
 
16
- const { config, path } = readConfig();
14
+ const configured = getConfiguredAgents();
17
15
 
18
- if (!hasProvider(config)) {
19
- console.log(color(" ℹ️ NOITON não está configurado.\n", "yellow"));
16
+ if (configured.length === 0) {
17
+ box("NOITON não está configurado em nenhum agente.", "info");
20
18
  return;
21
19
  }
22
20
 
23
- const proceed = await confirm({
24
- message: " Remover provider NOITON do opencode.json?",
21
+ info(`Agentes com NOITON: ${configured.map(a => a.name).join(", ")}`);
22
+ console.log();
23
+
24
+ const sure = await confirm({
25
+ message: `Remover NOITON de ${configured.length} agente(s)?`,
25
26
  default: false,
26
27
  });
27
28
 
28
- if (!proceed) {
29
- console.log(color("\n ✋ Operação cancelada.\n", "dim"));
29
+ if (!sure) {
30
+ console.log();
31
+ info(cancelMessage());
30
32
  return;
31
33
  }
32
34
 
33
- const newConfig = removeProvider(config);
34
- writeConfig(newConfig, path);
35
+ console.log();
36
+ let success = 0;
37
+ for (const agent of configured) {
38
+ const sub = spinner(`Removendo de ${agent.name}...`);
39
+ try {
40
+ const result = agent.remove();
41
+ if (result.removed) {
42
+ sub.succeed(`${agent.name} limpo!`);
43
+ success++;
44
+ } else {
45
+ sub.info(`${agent.name} já estava limpo`);
46
+ }
47
+ } catch (err) {
48
+ sub.fail(`${agent.name}: ${err.message}`);
49
+ }
50
+ }
51
+
52
+ console.log();
53
+ box(
54
+ success > 0
55
+ ? `🐙 NOITON removido de ${success} agente(s).\n\nAs outras configurações foram preservadas.`
56
+ : "Nada para remover.",
57
+ success > 0 ? "success" : "info"
58
+ );
35
59
 
36
- console.log(color("\n ✓ Provider NOITON removido!\n", "green"));
37
- console.log(color(` 📁 Arquivo: ${path}\n`, "dim"));
60
+ console.log();
61
+ console.log(c.dim(` ${farewellMessage()}`));
38
62
  }
@@ -1,41 +1,68 @@
1
- import { BANNER, color } from "../constants.js";
2
- import { maskKey } from "../lib/api.js";
3
- import { readConfig, hasProvider, getProvider } from "../lib/config.js";
4
-
5
1
  /**
6
- * Show current NOITON configuration.
2
+ * NOITON status command - show configuration across all agents
7
3
  */
4
+
5
+ import { c } from "../constants.js";
6
+ import { printHeader, ok, warn, fail, info, dim, divider, checklist, box } from "../ui.js";
7
+ import { getAllStatuses } from "../lib/agents/index.js";
8
+ import { maskKey } from "../lib/api.js";
9
+ import { farewellMessage } from "../messages.js";
10
+
8
11
  export async function statusCommand() {
9
- console.log(BANNER);
12
+ printHeader("Status dos Agentes");
10
13
 
11
- const { config, path } = readConfig();
14
+ const statuses = getAllStatuses();
15
+ const detected = statuses.filter((s) => s.detected);
12
16
 
13
- if (!hasProvider(config)) {
14
- console.log(color(" NOITON não está configurado.\n", "red"));
15
- console.log(color(" Rode ", "dim") + color("npx noiton", "cyan") + color(" para configurar.\n", "dim"));
17
+ if (detected.length === 0) {
18
+ box("Nenhum agente de IA detectado!\n\nRode 'npx noiton' para configurar.", "warning");
19
+ console.log();
20
+ console.log(c.dim(` ${farewellMessage()}`));
16
21
  return;
17
22
  }
18
23
 
19
- const provider = getProvider(config);
20
- const apiKey = provider?.options?.apiKey || "";
21
- const baseURL = provider?.options?.baseURL || "";
22
- const models = provider?.models || {};
23
- const modelCount = Object.keys(models).length;
24
-
25
- console.log(color(" ✅ NOITON está configurado!\n\n", "green"));
26
- console.log(color(" 📁 Config: ", "dim") + path);
27
- console.log(color(" 🌐 Base URL: ", "dim") + baseURL);
28
- console.log(color(" 🔑 Chave: ", "dim") + maskKey(apiKey));
29
- console.log(color(` 📦 Modelos (${modelCount}):\n`, "dim"));
30
-
31
- for (const [id, model] of Object.entries(models)) {
32
- const ctx = model?.limit?.context
33
- ? Math.round(model.limit.context / 1000) + "K"
34
- : "?";
35
- const out = model?.limit?.output
36
- ? Math.round(model.limit.output / 1000) + "K"
37
- : "?";
38
- console.log(` ${id} ` + color(`[${ctx} ctx / ${out} out]`, "dim"));
24
+ for (const s of statuses) {
25
+ if (!s.detected) continue;
26
+
27
+ console.log();
28
+ console.log(c.bold(c.purple(` 🐙 ${s.name}`)));
29
+
30
+ if (s.error) {
31
+ fail(`Erro: ${s.error}`);
32
+ continue;
33
+ }
34
+
35
+ if (s.configured) {
36
+ ok("NOITON configurado");
37
+ if (s.apiKey) {
38
+ dim(`Chave: ${maskKey(s.apiKey)}`);
39
+ }
40
+ if (s.models && s.models.length > 0) {
41
+ dim(`Modelos: ${s.models.join(", ")}`);
42
+ }
43
+ dim(`Config: ${s.path}`);
44
+ if (s.envPath) {
45
+ dim(`Env: ${s.envPath}`);
46
+ }
47
+ } else {
48
+ warn("NOITON não configurado");
49
+ dim(`Config: ${s.path}`);
50
+ }
39
51
  }
40
- console.log("");
52
+
53
+ // Show not-detected agents
54
+ const notDetected = statuses.filter((s) => !s.detected);
55
+ if (notDetected.length > 0) {
56
+ console.log();
57
+ divider();
58
+ console.log();
59
+ console.log(c.dim(" Outros agentes suportados (não detectados):"));
60
+ for (const s of notDetected) {
61
+ console.log(c.dim(` ○ ${s.name}`));
62
+ }
63
+ }
64
+
65
+ console.log();
66
+ console.log(c.dim(` ${farewellMessage()}`));
67
+ console.log();
41
68
  }
package/src/constants.js CHANGED
@@ -1,26 +1,40 @@
1
- export const VERSION = "1.0.0";
1
+ import picocolors from "picocolors";
2
+
3
+ export const VERSION = "2.0.0";
2
4
  export const PROVIDER_ID = "noiton";
3
5
  export const PROVIDER_NAME = "NOITON";
4
6
  export const BASE_URL = "https://blogs-litellm-app.rwezkp.easypanel.host";
5
7
  export const NPM_PACKAGE = "@ai-sdk/openai-compatible";
8
+ export const GITHUB_URL = "https://github.com/tokensilimitados/noiton";
6
9
 
7
- export const BANNER = `
8
- ╔══════════════════════════════════════════╗
9
- ║ NOITON - OpenCode Authenticator v${VERSION} ║
10
- ╚══════════════════════════════════════════╝
11
- `;
10
+ // Octopus says goodbye
11
+ export const sayGoodbye = "O polvo agradece! 🐙";
12
12
 
13
- export const COLORS = {
14
- reset: "\x1b[0m",
15
- bold: "\x1b[1m",
16
- dim: "\x1b[2m",
17
- red: "\x1b[31m",
18
- green: "\x1b[32m",
19
- yellow: "\x1b[33m",
20
- blue: "\x1b[34m",
21
- cyan: "\x1b[36m",
13
+ /**
14
+ * Colorize text using picocolors (lightweight, no deps beyond itself).
15
+ * Falls back gracefully when colors are disabled.
16
+ */
17
+ export const c = {
18
+ purple: (t) => picocolors.magenta(t),
19
+ cyan: (t) => picocolors.cyan(t),
20
+ green: (t) => picocolors.green(t),
21
+ yellow: (t) => picocolors.yellow(t),
22
+ red: (t) => picocolors.red(t),
23
+ blue: (t) => picocolors.blue(t),
24
+ gray: (t) => picocolors.gray(t),
25
+ dim: (t) => picocolors.dim(t),
26
+ bold: (t) => picocolors.bold(t),
27
+ italic: (t) => picocolors.italic(t),
28
+ underline: (t) => picocolors.underline(t),
29
+ magenta: (t) => picocolors.magenta(t),
30
+ white: (t) => picocolors.white(t),
31
+ bgMagenta: (t) => picocolors.bgMagenta(t),
32
+ bgCyan: (t) => picocolors.bgCyan(t),
33
+ bgGreen: (t) => picocolors.bgGreen(t),
22
34
  };
23
35
 
24
- export function color(text, c) {
25
- return `${COLORS[c] || ""}${text}${COLORS.reset}`;
36
+ /** Backwards-compat alias for the old color() signature */
37
+ export function color(text, col) {
38
+ if (c[col]) return c[col](text);
39
+ return text;
26
40
  }
package/src/index.js CHANGED
@@ -1,30 +1,41 @@
1
- import { BANNER, VERSION, color } from "./constants.js";
1
+ import { VERSION, c, GITHUB_URL } from "./constants.js";
2
+ import { printBanner, box } from "./ui.js";
2
3
  import { initCommand } from "./commands/init.js";
3
4
  import { statusCommand } from "./commands/status.js";
4
5
  import { refreshCommand } from "./commands/refresh.js";
5
6
  import { changeCommand } from "./commands/change.js";
6
7
  import { removeCommand } from "./commands/remove.js";
8
+ import { randomGoodbye } from "./messages.js";
7
9
 
8
10
  const HELP = `
9
- ${color("NOITON", "cyan")} ${color("v" + VERSION, "dim")} - OpenCode Authenticator
11
+ ${c.purple("🐙 NOITON")} ${c.dim("v" + VERSION)} - Universal AI Agent Connector
10
12
 
11
- ${color("USAGE", "bold")}
13
+ ${c.bold("USAGE")}
12
14
  npx noiton Wizard interativo (padrão)
13
15
  npx noiton init Mesmo que acima
14
- npx noiton status Mostra configuração atual
16
+ npx noiton status Mostra configuração de todos os agentes
15
17
  npx noiton refresh Re-busca modelos da API
16
- npx noiton change Troca a chave API
17
- npx noiton remove Remove provider NOITON
18
+ npx noiton change Troca a chave API em todos
19
+ npx noiton remove Remove NOITON de todos os agentes
18
20
  npx noiton help, --help Mostra esta ajuda
19
21
  npx noiton --version Mostra versão
20
22
 
21
- ${color("EXEMPLOS", "bold")}
23
+ ${c.bold("AGENTES SUPORTADOS")}
24
+ ${c.cyan("•")} OpenCode Programador IA (CLI)
25
+ ${c.cyan("•")} Hermes Assistente de tarefas (CLI)
26
+ ${c.cyan("•")} OpenClaw Agente open-source (CLI)
27
+ ${c.cyan("•")} Cline Extensão VS Code
28
+ ${c.cyan("•")} Aider Pair programmer de terminal
29
+ ${c.cyan("•")} Kilo Code CLI + extensão VS Code
30
+ ${c.cyan("•")} Kimi Code CLI + extensão VS Code
31
+
32
+ ${c.bold("EXEMPLOS")}
22
33
  npx noiton # configura pela primeira vez
23
34
  npx noiton refresh # atualiza lista de modelos
24
35
  npx noiton status # ver o que está configurado
25
36
 
26
- ${color("MAIS INFO", "bold")}
27
- GitHub: https://github.com/tokensilimitados/noiton
37
+ ${c.bold("MAIS INFO")}
38
+ GitHub: ${GITHUB_URL}
28
39
  `;
29
40
 
30
41
  /**
@@ -70,7 +81,7 @@ export async function run(args) {
70
81
  await removeCommand();
71
82
  break;
72
83
  default:
73
- console.error(color(`\n ❌ Comando desconhecido: ${command}\n`, "red"));
84
+ console.error(c.red(`\n ❌ Comando desconhecido: ${command}\n`));
74
85
  console.log(HELP);
75
86
  process.exit(1);
76
87
  }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Aider Agent Adapter
3
+ *
4
+ * Config: ~/.aider.conf.yml (YAML) + env vars
5
+ * Format:
6
+ * openai-api-base: <BASE_URL>
7
+ * model: <model-id>
8
+ * .env: OPENAI_API_KEY=<key>
9
+ */
10
+
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import YAML from "yaml";
15
+ import { BASE_URL } from "../../constants.js";
16
+
17
+ const HOME = homedir();
18
+ const CONFIG_PATH = join(HOME, ".aider.conf.yml");
19
+ const ENV_PATH = join(HOME, ".env");
20
+
21
+ function readYaml() {
22
+ if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
23
+ try {
24
+ const raw = readFileSync(CONFIG_PATH, "utf8");
25
+ return { config: YAML.parse(raw) || {}, path: CONFIG_PATH };
26
+ } catch {
27
+ return { config: {}, path: CONFIG_PATH };
28
+ }
29
+ }
30
+
31
+ function writeYaml(config) {
32
+ if (!existsSync(dirname(CONFIG_PATH))) {
33
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
34
+ }
35
+ writeFileSync(CONFIG_PATH, YAML.stringify(config, { indent: 2 }), "utf8");
36
+ }
37
+
38
+ function readEnvFile() {
39
+ if (!existsSync(ENV_PATH)) return {};
40
+ const raw = readFileSync(ENV_PATH, "utf8");
41
+ const env = {};
42
+ for (const line of raw.split("\n")) {
43
+ const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
44
+ if (m) env[m[1]] = m[2];
45
+ }
46
+ return env;
47
+ }
48
+
49
+ function writeEnvFile(env) {
50
+ const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`);
51
+ writeFileSync(ENV_PATH, lines.join("\n") + "\n", "utf8");
52
+ }
53
+
54
+ // ============= ADAPTER INTERFACE =============
55
+
56
+ export const aiderAgent = {
57
+ id: "aider",
58
+ name: "Aider",
59
+ supportsMultipleModels: false,
60
+
61
+ detect() {
62
+ return existsSync(CONFIG_PATH);
63
+ },
64
+
65
+ isConfigured() {
66
+ const { config } = readYaml();
67
+ return Boolean(config?.["openai-api-base"] === BASE_URL);
68
+ },
69
+
70
+ getStatus() {
71
+ const { config, path } = readYaml();
72
+ const env = readEnvFile();
73
+ const apiKey = env.OPENAI_API_KEY;
74
+ const configured = config?.["openai-api-base"] === BASE_URL;
75
+ const models = config?.model ? [config.model] : [];
76
+ return {
77
+ configured,
78
+ path,
79
+ models,
80
+ apiKey: apiKey ? `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}` : null,
81
+ envPath: ENV_PATH,
82
+ };
83
+ },
84
+
85
+ configure({ apiKey, models, primaryModel }) {
86
+ const { config } = readYaml();
87
+ config["openai-api-base"] = `${BASE_URL}/v1`;
88
+ config.model = primaryModel || models[0]?.id || "gpt-4";
89
+ config["weak-model"] = primaryModel || models[0]?.id || "gpt-4";
90
+ writeYaml(config);
91
+
92
+ // Set API key in .env
93
+ const env = readEnvFile();
94
+ env.OPENAI_API_KEY = apiKey;
95
+ writeEnvFile(env);
96
+
97
+ return { path: CONFIG_PATH, envPath: ENV_PATH, modelsCount: 1 };
98
+ },
99
+
100
+ changeKey(apiKey) {
101
+ if (!this.isConfigured()) {
102
+ throw new Error("NOITON não está configurado no Aider");
103
+ }
104
+ const env = readEnvFile();
105
+ env.OPENAI_API_KEY = apiKey;
106
+ writeEnvFile(env);
107
+ return { path: ENV_PATH };
108
+ },
109
+
110
+ refresh(models) {
111
+ const { config } = readYaml();
112
+ if (!config?.["openai-api-base"]) {
113
+ throw new Error("NOITON não está configurado no Aider");
114
+ }
115
+ if (models.length > 0) {
116
+ config.model = models[0].id;
117
+ config["weak-model"] = models[0].id;
118
+ }
119
+ writeYaml(config);
120
+ return { path: CONFIG_PATH, modelsCount: 1 };
121
+ },
122
+
123
+ remove() {
124
+ const { config } = readYaml();
125
+ let removed = false;
126
+ if (config?.["openai-api-base"] === BASE_URL) {
127
+ delete config["openai-api-base"];
128
+ delete config.model;
129
+ delete config["weak-model"];
130
+ removed = true;
131
+ writeYaml(config);
132
+ }
133
+ const env = readEnvFile();
134
+ if (env.OPENAI_API_KEY) {
135
+ delete env.OPENAI_API_KEY;
136
+ writeEnvFile(env);
137
+ removed = true;
138
+ }
139
+ return { path: CONFIG_PATH, envPath: ENV_PATH, removed };
140
+ },
141
+ };