@mathzdevolper/phantomcode-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.
Files changed (42) hide show
  1. package/dist/commands/chat.d.ts +9 -0
  2. package/dist/commands/chat.js +68 -0
  3. package/dist/commands/chat.js.map +1 -0
  4. package/dist/commands/config.d.ts +1 -0
  5. package/dist/commands/config.js +57 -0
  6. package/dist/commands/config.js.map +1 -0
  7. package/dist/commands/interactive.d.ts +7 -0
  8. package/dist/commands/interactive.js +155 -0
  9. package/dist/commands/interactive.js.map +1 -0
  10. package/dist/commands/models.d.ts +5 -0
  11. package/dist/commands/models.js +38 -0
  12. package/dist/commands/models.js.map +1 -0
  13. package/dist/commands/session.d.ts +1 -0
  14. package/dist/commands/session.js +77 -0
  15. package/dist/commands/session.js.map +1 -0
  16. package/dist/config/manager.d.ts +14 -0
  17. package/dist/config/manager.js +49 -0
  18. package/dist/config/manager.js.map +1 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +90 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/provider/dogrouter.d.ts +28 -0
  23. package/dist/provider/dogrouter.js +58 -0
  24. package/dist/provider/dogrouter.js.map +1 -0
  25. package/dist/session/manager.d.ts +20 -0
  26. package/dist/session/manager.js +112 -0
  27. package/dist/session/manager.js.map +1 -0
  28. package/dist/tui/colors.d.ts +15 -0
  29. package/dist/tui/colors.js +47 -0
  30. package/dist/tui/colors.js.map +1 -0
  31. package/package.json +34 -0
  32. package/src/commands/chat.ts +81 -0
  33. package/src/commands/config.ts +74 -0
  34. package/src/commands/interactive.ts +140 -0
  35. package/src/commands/models.ts +41 -0
  36. package/src/commands/session.ts +93 -0
  37. package/src/config/manager.ts +49 -0
  38. package/src/index.ts +109 -0
  39. package/src/provider/dogrouter.ts +82 -0
  40. package/src/session/manager.ts +93 -0
  41. package/src/tui/colors.ts +43 -0
  42. package/tsconfig.json +17 -0
@@ -0,0 +1,41 @@
1
+ import { getApiKey } from "../config/manager";
2
+ import { listModels } from "../provider/dogrouter";
3
+ import { style } from "../tui/colors";
4
+
5
+ interface ModelsArgs {
6
+ search?: string;
7
+ }
8
+
9
+ export async function handleModels(args: ModelsArgs): Promise<void> {
10
+ let apiKey: string;
11
+ try {
12
+ apiKey = getApiKey();
13
+ } catch {
14
+ console.error(style.error("✗ API key nao configurada"));
15
+ console.log(style.muted(" Execute: phantomcode config set <sua-api-key>"));
16
+ process.exit(1);
17
+ }
18
+
19
+ try {
20
+ const models = await listModels(apiKey);
21
+
22
+ const filtered = args.search
23
+ ? models.filter((m) => m.id.toLowerCase().includes(args.search!.toLowerCase()))
24
+ : models;
25
+
26
+ if (filtered.length === 0) {
27
+ console.log(style.warning("Nenhum modelo encontrado"));
28
+ return;
29
+ }
30
+
31
+ console.log(style.header(`┌─ Modelos Disponiveis (${filtered.length}) ───────────`));
32
+ for (const m of filtered) {
33
+ console.log(`│ ${style.code(m.id)}`);
34
+ }
35
+ console.log(style.header("└───────────────────────────────────────────"));
36
+ } catch (error: unknown) {
37
+ const err = error as { message?: string };
38
+ console.error(style.error(`✗ Erro ao listar modelos: ${err.message || "Erro desconhecido"}`));
39
+ process.exit(1);
40
+ }
41
+ }
@@ -0,0 +1,93 @@
1
+ import { listSessions, getSession, deleteSession } from "../session/manager";
2
+ import { style } from "../tui/colors";
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export function buildSessionCommand(yargs: any): any {
6
+ return yargs
7
+ .command(
8
+ "list",
9
+ "Lista todas as sessoes salvas",
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ (y: any) =>
12
+ y.option("limit", {
13
+ alias: "n",
14
+ describe: "Numero maximo de sessoes",
15
+ type: "number",
16
+ default: 10,
17
+ }),
18
+ (args: { limit: number }) => {
19
+ const sessions = listSessions().slice(0, args.limit);
20
+
21
+ if (sessions.length === 0) {
22
+ console.log(style.muted("Nenhuma sessao encontrada"));
23
+ return;
24
+ }
25
+
26
+ console.log(style.header(`┌─ Sessoes (${sessions.length}) ───────────────────────────`));
27
+ for (const s of sessions) {
28
+ const date = new Date(s.updatedAt).toLocaleString("pt-BR");
29
+ console.log(`│ ${style.code(s.id.slice(0, 8))}... ${style.highlight(s.title)}`);
30
+ console.log(`│ ${style.muted(`${date} · ${s.model} · ${s.messages.length} msgs`)}`);
31
+ console.log(`│`);
32
+ }
33
+ console.log(style.header("└───────────────────────────────────────────"));
34
+ }
35
+ )
36
+ .command(
37
+ "show <id>",
38
+ "Exibe detalhes de uma sessao",
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ (y: any) =>
41
+ y.positional("id", {
42
+ describe: "ID da sessao",
43
+ type: "string",
44
+ demandOption: true,
45
+ }),
46
+ (args: { id: string }) => {
47
+ const session = getSession(args.id);
48
+ if (!session) {
49
+ console.error(style.error("✗ Sessao nao encontrada"));
50
+ process.exit(1);
51
+ }
52
+
53
+ console.log(style.header("┌─ Sessao ─────────────────────────────────"));
54
+ console.log(`│ ID: ${style.code(session.id)}`);
55
+ console.log(`│ Titulo: ${style.highlight(session.title)}`);
56
+ console.log(`│ Modelo: ${style.code(session.model)}`);
57
+ console.log(`│ Mensagens: ${session.messages.length}`);
58
+ console.log(`│ Criada: ${new Date(session.createdAt).toLocaleString("pt-BR")}`);
59
+ console.log(style.header("├─ Mensagens ──────────────────────────────"));
60
+
61
+ for (const msg of session.messages) {
62
+ const role = msg.role === "user" ? "Voce" : msg.role === "assistant" ? "Phantom" : "Sistema";
63
+ const color = msg.role === "user" ? style.label : msg.role === "assistant" ? style.primary : style.warning;
64
+ const preview = msg.content.slice(0, 200) + (msg.content.length > 200 ? "..." : "");
65
+ console.log(color(`${role}: ${preview}`));
66
+ console.log("");
67
+ }
68
+ console.log(style.header("└───────────────────────────────────────────"));
69
+ }
70
+ )
71
+ .command(
72
+ "delete <id>",
73
+ "Deleta uma sessao",
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ (y: any) =>
76
+ y.positional("id", {
77
+ describe: "ID da sessao",
78
+ type: "string",
79
+ demandOption: true,
80
+ }),
81
+ (args: { id: string }) => {
82
+ const session = getSession(args.id);
83
+ if (!session) {
84
+ console.error(style.error("✗ Sessao nao encontrada"));
85
+ process.exit(1);
86
+ }
87
+ deleteSession(args.id);
88
+ console.log(style.success(`✓ Sessao "${session.title}" deletada`));
89
+ }
90
+ )
91
+ .demandCommand(1, "Use: phantomcode session <comando>")
92
+ .help();
93
+ }
@@ -0,0 +1,49 @@
1
+ import Conf from "conf";
2
+
3
+ interface PhantomConfig {
4
+ apiKey?: string;
5
+ defaultModel?: string;
6
+ theme?: "light" | "dark";
7
+ }
8
+
9
+ const store = new Conf<PhantomConfig>({
10
+ projectName: "phantomcode",
11
+ defaults: {
12
+ defaultModel: "dogrouter/auto",
13
+ theme: "dark",
14
+ },
15
+ });
16
+
17
+ export function getApiKey(): string {
18
+ const key = store.get("apiKey");
19
+ if (!key) throw new Error("API key nao configurada. Use: phantomcode config --set <api-key>");
20
+ return key;
21
+ }
22
+
23
+ export function setApiKey(key: string): void {
24
+ store.set("apiKey", key);
25
+ }
26
+
27
+ export function removeApiKey(): void {
28
+ store.delete("apiKey");
29
+ }
30
+
31
+ export function hasApiKey(): boolean {
32
+ return store.has("apiKey");
33
+ }
34
+
35
+ export function getDefaultModel(): string {
36
+ return store.get("defaultModel", "dogrouter/auto");
37
+ }
38
+
39
+ export function setDefaultModel(model: string): void {
40
+ store.set("defaultModel", model);
41
+ }
42
+
43
+ export function getTheme(): string {
44
+ return store.get("theme", "dark");
45
+ }
46
+
47
+ export function getAll(): PhantomConfig {
48
+ return store.store;
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,109 @@
1
+ #! /usr/bin/env node
2
+
3
+ import yargs from "yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { style } from "./tui/colors";
6
+ import { handleChat } from "./commands/chat";
7
+ import { handleModels } from "./commands/models";
8
+ import { buildConfigCommand } from "./commands/config";
9
+ import { buildSessionCommand } from "./commands/session";
10
+ import { runInteractive } from "./commands/interactive";
11
+
12
+ const pkg: { version: string } = require("../package.json");
13
+
14
+ yargs(hideBin(process.argv))
15
+ .scriptName("phantomcode")
16
+ .version(`PhantomCode v${pkg.version}`)
17
+ .command(
18
+ "chat <message>",
19
+ "Envia mensagem para IA via DogRouter",
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ ((y: any) =>
22
+ y
23
+ .positional("message", {
24
+ describe: "Mensagem a ser enviada",
25
+ type: "string",
26
+ demandOption: true,
27
+ })
28
+ .option("model", {
29
+ alias: "m",
30
+ describe: "Modelo a usar",
31
+ type: "string",
32
+ })
33
+ .option("system", {
34
+ alias: "s",
35
+ describe: "System prompt",
36
+ type: "string",
37
+ })
38
+ .option("session", {
39
+ describe: "ID de sessao para continuar",
40
+ type: "string",
41
+ })
42
+ .option("no-save", {
43
+ describe: "Nao salvar em sessao",
44
+ type: "boolean",
45
+ default: false,
46
+ })) as any,
47
+ handleChat as any
48
+ )
49
+ .command(
50
+ "interactive",
51
+ "Modo interativo com historico de conversa",
52
+ ((y: any) =>
53
+ y
54
+ .option("model", { alias: "m", describe: "Modelo a usar", type: "string" })
55
+ .option("system", { alias: "s", describe: "System prompt", type: "string" })
56
+ .option("session", { describe: "ID de sessao para continuar", type: "string" })) as any,
57
+ runInteractive as any
58
+ )
59
+ .command(
60
+ "models",
61
+ "Lista modelos disponiveis via DogRouter",
62
+ ((y: any) =>
63
+ y.option("search", {
64
+ alias: "s",
65
+ describe: "Filtrar modelos por nome",
66
+ type: "string",
67
+ })) as any,
68
+ handleModels as any
69
+ )
70
+ .command("config", "Gerencia configuracoes do CLI", buildConfigCommand as any, () => {})
71
+ .command("session", "Gerencia sessoes de conversa", buildSessionCommand as any, () => {})
72
+ .command(
73
+ "$0",
74
+ "",
75
+ () => {},
76
+ () => {
77
+ printWelcome();
78
+ }
79
+ )
80
+ .demandCommand(0)
81
+ .strict()
82
+ .help()
83
+ .fail((msg: string, err: Error) => {
84
+ if (err) {
85
+ console.error(style.error(`\n✗ ${err.message}`));
86
+ } else {
87
+ console.error(style.error(`\n✗ ${msg}`));
88
+ console.log(style.muted("\n Use: phantomcode --help para ver os comandos disponiveis"));
89
+ }
90
+ process.exit(1);
91
+ })
92
+ .parse();
93
+
94
+ function printWelcome(): void {
95
+ console.log(style.header(`
96
+ ╔══════════════════════════════════════════════╗
97
+ ║ ✦ PhantomCode - CLI v${pkg.version} ✦ ║
98
+ ║ DogRouter AI Integration Platform ║
99
+ ╚══════════════════════════════════════════════╝`));
100
+ console.log(style.muted("\n Um CLI poderoso para interagir com modelos de IA via DogRouter."));
101
+ console.log(style.muted(" Roteamento inteligente, multi-modelo, sessoes e historico.\n"));
102
+ console.log(`${style.primary(" USO:")}`);
103
+ console.log(` ${style.code("phantomcode chat <mensagem>")} ${style.muted("Enviar mensagem para IA")}`);
104
+ console.log(` ${style.code("phantomcode interactive")} ${style.muted("Modo interativo")}`);
105
+ console.log(` ${style.code("phantomcode models")} ${style.muted("Listar modelos")}`);
106
+ console.log(` ${style.code("phantomcode config set <key>")} ${style.muted("Configurar API key")}`);
107
+ console.log(` ${style.code("phantomcode session list")} ${style.muted("Listar sessoes")}`);
108
+ console.log(style.muted(`\n Documentacao: https://dogrouter.ai\n`));
109
+ }
@@ -0,0 +1,82 @@
1
+ import OpenAI from "openai";
2
+
3
+ const BASE_URL = "https://api.dogrouter.ai/v1";
4
+
5
+ let client: OpenAI | null = null;
6
+
7
+ function getClient(apiKey: string): OpenAI {
8
+ if (!client) {
9
+ client = new OpenAI({ apiKey, baseURL: BASE_URL });
10
+ }
11
+ return client;
12
+ }
13
+
14
+ export interface ChatOptions {
15
+ model?: string;
16
+ systemPrompt?: string;
17
+ temperature?: number;
18
+ maxTokens?: number;
19
+ stream?: boolean;
20
+ }
21
+
22
+ export interface ChatResponse {
23
+ content: string;
24
+ model: string;
25
+ usage?: {
26
+ promptTokens: number;
27
+ completionTokens: number;
28
+ totalTokens: number;
29
+ };
30
+ }
31
+
32
+ export async function chat(
33
+ apiKey: string,
34
+ messages: { role: "user" | "assistant" | "system"; content: string }[],
35
+ options: ChatOptions = {}
36
+ ): Promise<ChatResponse> {
37
+ const c = getClient(apiKey);
38
+ const completion = await c.chat.completions.create({
39
+ model: options.model || "dogrouter/auto",
40
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
41
+ temperature: options.temperature ?? 0.7,
42
+ max_tokens: options.maxTokens,
43
+ });
44
+
45
+ return {
46
+ content: completion.choices[0]?.message?.content || "",
47
+ model: completion.model,
48
+ usage: completion.usage
49
+ ? {
50
+ promptTokens: completion.usage.prompt_tokens,
51
+ completionTokens: completion.usage.completion_tokens,
52
+ totalTokens: completion.usage.total_tokens,
53
+ }
54
+ : undefined,
55
+ };
56
+ }
57
+
58
+ export async function* chatStream(
59
+ apiKey: string,
60
+ messages: { role: "user" | "assistant" | "system"; content: string }[],
61
+ options: ChatOptions = {}
62
+ ): AsyncGenerator<string> {
63
+ const c = getClient(apiKey);
64
+ const stream = await c.chat.completions.create({
65
+ model: options.model || "dogrouter/auto",
66
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
67
+ temperature: options.temperature ?? 0.7,
68
+ max_tokens: options.maxTokens,
69
+ stream: true,
70
+ });
71
+
72
+ for await (const chunk of stream) {
73
+ const delta = chunk.choices[0]?.delta?.content;
74
+ if (delta) yield delta;
75
+ }
76
+ }
77
+
78
+ export async function listModels(apiKey: string): Promise<{ id: string; ownedBy: string }[]> {
79
+ const c = getClient(apiKey);
80
+ const models = await c.models.list();
81
+ return models.data.map((m) => ({ id: m.id, ownedBy: m.owned_by || "dogrouter" }));
82
+ }
@@ -0,0 +1,93 @@
1
+ import { v4 as uuid } from "uuid";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+
6
+ interface Message {
7
+ role: "user" | "assistant" | "system";
8
+ content: string;
9
+ timestamp: number;
10
+ }
11
+
12
+ interface Session {
13
+ id: string;
14
+ title: string;
15
+ model: string;
16
+ messages: Message[];
17
+ createdAt: number;
18
+ updatedAt: number;
19
+ }
20
+
21
+ const SESSIONS_DIR = path.join(os.homedir(), ".phantomcode", "sessions");
22
+
23
+ function ensureDir(): void {
24
+ if (!fs.existsSync(SESSIONS_DIR)) {
25
+ fs.mkdirSync(SESSIONS_DIR, { recursive: true });
26
+ }
27
+ }
28
+
29
+ function sessionPath(id: string): string {
30
+ return path.join(SESSIONS_DIR, `${id}.json`);
31
+ }
32
+
33
+ export function createSession(model: string, title = "Nova sessao"): Session {
34
+ ensureDir();
35
+ const session: Session = {
36
+ id: uuid(),
37
+ title,
38
+ model,
39
+ messages: [],
40
+ createdAt: Date.now(),
41
+ updatedAt: Date.now(),
42
+ };
43
+ fs.writeFileSync(sessionPath(session.id), JSON.stringify(session, null, 2));
44
+ return session;
45
+ }
46
+
47
+ export function getSession(id: string): Session | null {
48
+ try {
49
+ return JSON.parse(fs.readFileSync(sessionPath(id), "utf-8"));
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ export function listSessions(): Session[] {
56
+ ensureDir();
57
+ const files = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
58
+ return files
59
+ .map((f) => {
60
+ try {
61
+ return JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, f), "utf-8")) as Session;
62
+ } catch {
63
+ return null;
64
+ }
65
+ })
66
+ .filter((s): s is Session => s !== null)
67
+ .sort((a, b) => b.updatedAt - a.updatedAt);
68
+ }
69
+
70
+ export function addMessage(sessionId: string, role: Message["role"], content: string): Session | null {
71
+ const session = getSession(sessionId);
72
+ if (!session) return null;
73
+
74
+ session.messages.push({ role, content, timestamp: Date.now() });
75
+ session.updatedAt = Date.now();
76
+
77
+ if (session.messages.length === 1) {
78
+ session.title = content.slice(0, 50) + (content.length > 50 ? "..." : "");
79
+ }
80
+
81
+ fs.writeFileSync(sessionPath(sessionId), JSON.stringify(session, null, 2));
82
+ return session;
83
+ }
84
+
85
+ export function deleteSession(id: string): void {
86
+ const p = sessionPath(id);
87
+ if (fs.existsSync(p)) fs.unlinkSync(p);
88
+ }
89
+
90
+ export function getMessages(sessionId: string): Message[] {
91
+ const session = getSession(sessionId);
92
+ return session?.messages || [];
93
+ }
@@ -0,0 +1,43 @@
1
+ import chalk from "chalk";
2
+ import { getTheme } from "../config/manager";
3
+
4
+ const theme = {
5
+ light: {
6
+ primary: chalk.hex("#2563eb"),
7
+ success: chalk.hex("#16a34a"),
8
+ warning: chalk.hex("#ea580c"),
9
+ error: chalk.hex("#dc2626"),
10
+ muted: chalk.hex("#64748b"),
11
+ highlight: chalk.hex("#1e293b"),
12
+ label: chalk.hex("#475569"),
13
+ },
14
+ dark: {
15
+ primary: chalk.hex("#60a5fa"),
16
+ success: chalk.hex("#4ade80"),
17
+ warning: chalk.hex("#fb923c"),
18
+ error: chalk.hex("#f87171"),
19
+ muted: chalk.hex("#94a3b8"),
20
+ highlight: chalk.hex("#e2e8f0"),
21
+ label: chalk.hex("#cbd5e1"),
22
+ },
23
+ };
24
+
25
+ function getColors() {
26
+ const t = getTheme() === "light" ? theme.light : theme.dark;
27
+ return t;
28
+ }
29
+
30
+ export const style = {
31
+ primary: (s: string) => getColors().primary(s),
32
+ success: (s: string) => getColors().success(s),
33
+ warning: (s: string) => getColors().warning(s),
34
+ error: (s: string) => getColors().error(s),
35
+ muted: (s: string) => getColors().muted(s),
36
+ highlight: (s: string) => getColors().highlight(s),
37
+ label: (s: string) => getColors().label(s),
38
+ bold: chalk.bold,
39
+ dim: chalk.dim,
40
+ code: (s: string) => chalk.cyan(s),
41
+ header: (s: string) => chalk.bold.hex("#60a5fa")(s),
42
+ separator: () => chalk.dim("─".repeat(process.stdout.columns || 60)),
43
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022", "dom"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true,
11
+ "declaration": true,
12
+ "sourceMap": true,
13
+ "skipLibCheck": true,
14
+ "types": ["node"]
15
+ },
16
+ "include": ["src/**/*"]
17
+ }