pulse-cli-divakar 1.0.1 → 1.0.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pulse-cli-divakar",
3
- "version": "1.0.1",
4
- "main": "src/index.js",
3
+ "version": "1.0.4",
4
+ "main": "./src/cli/main.js",
5
5
  "bin": {
6
6
  "pulse": "./src/cli/main.js"
7
7
  },
@@ -47,9 +47,12 @@
47
47
  "prisma": "^7.10.1"
48
48
  },
49
49
  "files": [
50
- "src/cli",
51
- "src/lib",
52
- "src/config"
50
+ "src/cli",
51
+ "src/service",
52
+ "src/lib/token.js",
53
+ "src/config/agent-config.js",
54
+ "src/config/google.config.js",
55
+ "src/config/tool.config.js"
53
56
  ]
54
57
 
55
58
  }
@@ -16,7 +16,6 @@ import { markedTerminal } from "marked-terminal";
16
16
  import { AIService } from "../ai/google-service.js";
17
17
  import { ChatService } from "../../service/chat.service.js";
18
18
  import { getStoredToken } from "../../lib/token.js";
19
- import { prisma } from "../../lib/db.js";
20
19
  import { getUserFromToken, saveMessage } from "./utils/chataiutils.js";
21
20
  import { generateApplication } from "../../config/agent-config.js";
22
21
 
@@ -7,7 +7,7 @@ import {markedTerminal} from "marked-terminal"
7
7
  import { AIService } from "../ai/google-service.js";
8
8
  import { ChatService } from "../../service/chat.service.js";
9
9
  import { getStoredToken } from "../../lib/token.js";
10
- import {prisma} from "../../lib/db.js"
10
+
11
11
 
12
12
  import {availableTools,getEnabledToolNames,getEnabledTools,enableTools,resetTools} from "../../config/tool.config.js"
13
13
  import { displayMessages, getUserFromToken ,updateConversationTitle,saveMessage} from "./utils/chataiutils.js";
@@ -1,40 +1,61 @@
1
1
  import yoctoSpinner from "yocto-spinner";
2
- import { getStoredToken } from "../../../lib/token.js";
3
- import { prisma } from "../../../lib/db.js";
4
2
  import { AIService } from "../../ai/google-service.js";
5
3
  import { ChatService } from "../../../service/chat.service.js";
6
4
  import { isCancel, text } from "@clack/prompts";
7
5
  import chalk from "chalk";
8
6
  import boxen from "boxen";
9
7
  import { marked } from "marked";
8
+ import { requireAuth } from "../../../lib/token.js";
9
+ import { URL } from "../../commands/auth/login.js";
10
10
 
11
11
  const aiService=new AIService()
12
12
  const chatService=new ChatService()
13
13
 
14
14
  export async function getUserFromToken() {
15
- const token=await getStoredToken();
16
- if(!token.access_token){
17
- throw new Error("Not authenticated. Please run 'pulse login' first");
18
- }
19
- const spinner=yoctoSpinner({text:"Authenticating..."});
20
- spinner.start()
15
+ const token = await requireAuth();
16
+
17
+ const spinner = yoctoSpinner({
18
+ text: "Authenticating..."
19
+ });
20
+
21
+ spinner.start();
22
+
23
+ try {
24
+ const serverUrl = URL.replace(/\/+$/, "");
21
25
 
22
- const user=await prisma.user.findFirst({
23
- where:{
24
- sessions:{
25
- some:{token:token.access_token},
26
+ const response = await fetch(`${serverUrl}/api/cli/me`, {
27
+ headers: {
28
+ Authorization: `Bearer ${token.access_token}`,
29
+ },
30
+ });
31
+
32
+ if (!response.ok) {
33
+ spinner.error("Authentication failed");
34
+
35
+ if (response.status === 401) {
36
+ throw new Error("Session invalid or expired. Please login again.");
26
37
  }
38
+
39
+ throw new Error(`Failed to fetch user: HTTP ${response.status}`);
27
40
  }
28
- })
29
- if(!user){
30
- spinner.error("User not found")
31
- throw new Error("User not found.Please login again")
41
+
42
+ const user = await response.json();
43
+
44
+ if (!user?.id) {
45
+ spinner.error("User not found");
46
+ throw new Error("User not found. Please login again.");
47
+ }
48
+
49
+ spinner.success(`Welcome back, ${user.name}!`);
50
+ return user;
51
+ } catch (error) {
52
+ if (spinner.isSpinning) {
53
+ spinner.error("Failed to authenticate");
54
+ }
55
+ throw error;
32
56
  }
33
- spinner.success(`Welcome back, ${user.name}!`)
34
- return user;
35
57
  }
36
58
 
37
-
38
59
  export async function getAIResponse(conversationId) {
39
60
  const spinner = yoctoSpinner({
40
61
  text: "AI is thinking...",
@@ -1,85 +1,89 @@
1
- import chalk from "chalk"
2
- import { Command } from "commander"
3
- import yoctoSpinner from "yocto-spinner"
4
- import { getStoredToken } from "../../../lib/token.js"
5
- import { select } from "@clack/prompts"
6
- import { prisma } from "../../../lib/db.js"
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import yoctoSpinner from "yocto-spinner";
4
+ import { getStoredToken, requireAuth } from "../../../lib/token.js";
5
+ import { select } from "@clack/prompts";
6
+ import { URL } from "../auth/login.js";
7
7
 
8
+ const wakeUpAction = async () => {
9
+ const token = await requireAuth();
10
+ if (!token?.access_token) {
11
+ console.log(chalk.red("Not Authenticated.Please login"));
12
+ return;
13
+ }
8
14
 
15
+ const spinner = yoctoSpinner({ text: "Fetching user information..." });
16
+ spinner.start();
9
17
 
10
- const wakeUpAction=async ()=>{
11
- const token=await getStoredToken()
12
- if(!token?.access_token){
13
- console.log(chalk.red("Not Authenticated.Please login"))
14
- return;
15
- }
18
+ const serverUrl = URL.replace(/\/+$/, "");
16
19
 
17
- const spinner=yoctoSpinner({text:"Fetching user information..."})
18
- spinner.start()
20
+ const response = await fetch(`${serverUrl}/api/cli/me`, {
21
+ headers: {
22
+ Authorization: `Bearer ${token.access_token}`,
23
+ },
24
+ });
19
25
 
20
- const user=await prisma.user.findFirst({
21
- where:{
22
- sessions:{
23
- some:{
24
- token:token.access_token,
25
- }
26
- }
27
- },
28
- select:{
29
- id:true,
30
- name:true,
31
- email:true,
32
- image:true
33
- }
34
- })
26
+ if (!response.ok) {
27
+ spinner.stop();
35
28
 
36
- if(!user){
37
- console.log(chalk.red("User not Found."))
38
- return;
29
+ if (response.status === 401) {
30
+ console.log(chalk.red("Session invalid or expired. Please login again."));
31
+ } else {
32
+ console.log(chalk.red(`Failed to fetch user: HTTP ${response.status}`));
39
33
  }
40
34
 
41
- spinner.stop()
42
- console.log(chalk.green(`Welcome back, ${user.name}!\n`))
35
+ return;
36
+ }
43
37
 
44
- const choice=await select({
45
- message:"Select an Option:",
46
- options:[
47
- {
48
- value:"chat",
49
- label:"Chat",
50
- hint:"Simple chat with AI"
51
- },
52
- {
53
- value:"tool",
54
- label:"Tool Calling",
55
- hint:"Chat with tools.(Google Search,Code Execution)"
56
- },
57
- {
58
- value:"agent",
59
- label:"Agentic Mode",
60
- hint:"Advanced AI agent."
61
- }
62
- ]
63
- })
38
+ const user = await response.json();
39
+ if (!user?.id) {
40
+ spinner.stop();
41
+ console.log(chalk.red("User not found."));
42
+ return;
43
+ }
64
44
 
65
- switch(choice){
66
- case "chat":
67
- const { startChat } = await import("../../chat/chat-with-ai.js");
68
- await startChat("chat");
69
- break;
70
- case "tool":
71
- const { startToolChat } = await import("../../chat/chat-with-aitool.js");
72
-
73
- await startToolChat();
74
- break;
75
- case "agent":
76
- const { startAgentChat } = await import("../../chat/chat-with-aiAgent.js");
77
- await startAgentChat();
78
- break;
79
- }
80
- }
45
+ spinner.stop();
46
+ console.log(chalk.green(`Welcome back, ${user.name}!\n`));
47
+
48
+ const choice = await select({
49
+ message: "Select an Option:",
50
+ options: [
51
+ {
52
+ value: "chat",
53
+ label: "Chat",
54
+ hint: "Simple chat with AI",
55
+ },
56
+ {
57
+ value: "tool",
58
+ label: "Tool Calling",
59
+ hint: "Chat with tools.(Google Search,Code Execution)",
60
+ },
61
+ {
62
+ value: "agent",
63
+ label: "Agentic Mode",
64
+ hint: "Advanced AI agent.",
65
+ },
66
+ ],
67
+ });
68
+
69
+ switch (choice) {
70
+ case "chat":
71
+ const { startChat } = await import("../../chat/chat-with-ai.js");
72
+ await startChat("chat");
73
+ break;
74
+ case "tool":
75
+ const { startToolChat } = await import("../../chat/chat-with-aitool.js");
81
76
 
77
+ await startToolChat();
78
+ break;
79
+ case "agent":
80
+ const { startAgentChat } =
81
+ await import("../../chat/chat-with-aiAgent.js");
82
+ await startAgentChat();
83
+ break;
84
+ }
85
+ };
82
86
 
83
- export const wakeUp=new Command("wakeup")
84
- .description("Wake up the AI.")
85
- .action(wakeUpAction)
87
+ export const wakeUp = new Command("wakeup")
88
+ .description("Wake up the AI.")
89
+ .action(wakeUpAction);
@@ -13,7 +13,6 @@ import yoctoSpinner from "yocto-spinner";
13
13
  import * as z from "zod/v4";
14
14
  import dotenv from "dotenv";
15
15
 
16
- import { prisma } from "../../../lib/db.js";
17
16
  import { clearStoredToken, getStoredToken, isTokenExpired, requireAuth, storeToken } from "../../../lib/token.js";
18
17
  import { pollForToken } from "../utils/polling.js";
19
18
 
@@ -42,8 +41,8 @@ export async function loginAction() {
42
41
 
43
42
  intro(chalk.bold("🔏 Auth CLI Login."));
44
43
 
45
- const existingToken = getStoredToken();
46
- const expired = isTokenExpired();
44
+ const existingToken =await getStoredToken();
45
+ const expired = await isTokenExpired();
47
46
 
48
47
  if (existingToken && !expired) {
49
48
  const shouldReAuth = await confirm({
@@ -1,32 +1,38 @@
1
1
  import chalk from "chalk";
2
- import { prisma } from "../../../lib/db.js";
2
+
3
3
  import { requireAuth } from "../../../lib/token.js";
4
4
  import { Command } from "commander";
5
5
  import { URL } from "./login.js";
6
6
 
7
- export async function whoamiAction() {
7
+ export async function whoamiAction(options) {
8
8
  const token=await requireAuth();
9
9
  if(!token?.access_token){
10
10
  console.log("No access token found.Please login");
11
11
  process.exit(1);
12
12
  }
13
13
 
14
- const user=await prisma.user.findFirst({
15
- where:{
16
- sessions:{
17
- some:{
18
- token:token.access_token,
19
- }
20
- }
21
- },
22
- select:{
23
- id:true,
24
- name:true,
25
- email:true,
26
- image:true
27
- }
28
-
29
- })
14
+
15
+ const serverUrl = (options.serverUrl || URL).replace(/\/+$/, "");
16
+
17
+ const response = await fetch(`${serverUrl}/api/cli/me`, {
18
+ headers: {
19
+ Authorization: `Bearer ${token.access_token}`,
20
+ },
21
+ });
22
+
23
+ if (!response.ok) {
24
+ console.log(
25
+ chalk.red(
26
+ response.status === 401
27
+ ? "Session invalid or expired. Please login again."
28
+ : `Failed to fetch user: HTTP ${response.status}`
29
+ )
30
+ );
31
+ return;
32
+ }
33
+
34
+ const user = await response.json();
35
+
30
36
 
31
37
  console.log(
32
38
  chalk.bold.greenBright(`\n 👤User: ${user.name}
@@ -0,0 +1,72 @@
1
+
2
+ import {
3
+ cancel,
4
+ intro,
5
+ isCancel,
6
+ outro,
7
+ password,
8
+ } from "@clack/prompts";
9
+ import chalk from "chalk";
10
+ import { Command } from "commander";
11
+ import fs from "node:fs/promises";
12
+ import os from "node:os";
13
+ import path from "node:path";
14
+
15
+ const CONFIG_DIR = path.join(os.homedir(), ".pulse");
16
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
17
+
18
+ export async function configAction() {
19
+ intro(chalk.bold("⚙️ Pulse Configuration"));
20
+
21
+ const apiKey = await password({
22
+ message: "Enter your Google Gemini API key:",
23
+ validate(value) {
24
+ if (!value?.trim()) return "API key cannot be empty.";
25
+ },
26
+ });
27
+
28
+ if (isCancel(apiKey)) {
29
+ cancel("Configuration cancelled.");
30
+ return;
31
+ }
32
+
33
+ try {
34
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
35
+
36
+ let existingConfig = {};
37
+ try {
38
+ existingConfig = JSON.parse(
39
+ await fs.readFile(CONFIG_FILE, "utf8"),
40
+ );
41
+ } catch {
42
+ // No existing config file yet.
43
+ }
44
+
45
+ await fs.writeFile(
46
+ CONFIG_FILE,
47
+ JSON.stringify(
48
+ {
49
+ ...existingConfig,
50
+ googleApiKey: apiKey.trim(),
51
+ },
52
+ null,
53
+ 2,
54
+ ),
55
+ { encoding: "utf8", mode: 0o600 },
56
+ );
57
+
58
+ outro(chalk.green("Google API key saved successfully."));
59
+ console.log(chalk.gray(`Config location: ${CONFIG_FILE}`));
60
+ console.log(chalk.gray("You can now run Pulse from any folder."));
61
+ } catch (error) {
62
+ console.error(
63
+ chalk.red("Could not save configuration:"),
64
+ error.message,
65
+ );
66
+ process.exitCode = 1;
67
+ }
68
+ }
69
+
70
+ export const config = new Command("config")
71
+ .description("Configure your Google Gemini API key")
72
+ .action(configAction);
package/src/cli/main.js CHANGED
@@ -9,6 +9,7 @@ import { login} from "./commands/auth/login.js"
9
9
  import { logout } from "./commands/auth/logout.js"
10
10
  import { whoami } from "./commands/auth/whoami.js"
11
11
  import { wakeUp } from "./commands/ai/wakeup.js"
12
+ import {config} from "./commands/config/config.js"
12
13
 
13
14
 
14
15
  dotenv.config()
@@ -26,12 +27,13 @@ async function main() {
26
27
  console.log(chalk.magenta("A Cli Based AI application.\n"))
27
28
 
28
29
  const program=new Command("pulse")
29
- program.version("0.0.1")
30
+ program.version("1.0.4")
30
31
  .description("Pulse is a Cli based application")
31
32
  .addCommand(login)
32
33
  .addCommand(logout)
33
34
  .addCommand(whoami)
34
35
  .addCommand(wakeUp)
36
+ .addCommand(config)
35
37
 
36
38
 
37
39
  program.action(()=>{
@@ -1,8 +1,23 @@
1
- import dotenv from "dotenv"
2
- dotenv.config()
1
+ import dotenv from "dotenv";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
3
5
 
4
- export const config={
5
- googleApiKey:process.env.GOOGLE_GENERATIVE_AI_API_KEY,
6
- model:process.env.ORBITAL_MODEL || "gemini-2.5-flash"
6
+ dotenv.config();
7
7
 
8
- }
8
+ const configPath = path.join(os.homedir(), ".pulse", "config.json");
9
+
10
+ let savedConfig = {};
11
+
12
+ try {
13
+ savedConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
14
+ } catch {
15
+ // Configuration has not been created yet.
16
+ }
17
+
18
+ export const config = {
19
+ googleApiKey:
20
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY || savedConfig.googleApiKey,
21
+
22
+ model: process.env.ORBITAL_MODEL || savedConfig.model || "gemini-2.5-flash",
23
+ };
@@ -0,0 +1,120 @@
1
+ import { URL } from "../cli/commands/auth/login.js";
2
+ import { getStoredToken } from "../lib/token.js";
3
+
4
+
5
+
6
+ export class ChatService{
7
+ async request(endpoint, options = {}) {
8
+ const token = await getStoredToken();
9
+
10
+ if (!token?.access_token) {
11
+ throw new Error("Not authenticated. Please run 'pulse login' first.");
12
+ }
13
+
14
+ const response = await fetch(
15
+ `${URL.replace(/\/+$/, "")}/api/cli${endpoint}`,
16
+ {
17
+ ...options,
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${token.access_token}`,
21
+ ...options.headers,
22
+ },
23
+ }
24
+ );
25
+
26
+ if (!response.ok) {
27
+ const result = await response.json().catch(() => ({}));
28
+ throw new Error(result.error || `API request failed: HTTP ${response.status}`);
29
+ }
30
+
31
+ if (response.status === 204) return null;
32
+
33
+ return response.json();
34
+ }
35
+ async createConversation(userId, mode = "chat", title = null) {
36
+ return this.request("/conversations", {
37
+ method: "POST",
38
+ body: JSON.stringify({
39
+ mode,
40
+ title: title || `New ${mode} conversation`,
41
+ }),
42
+ });
43
+ }
44
+
45
+ async getOrCreateConversation(userId, conversationId = null, mode = "chat") {
46
+ if (conversationId) {
47
+ return this.request(
48
+ `/conversations/${encodeURIComponent(conversationId)}`
49
+ );
50
+ }
51
+
52
+ return this.createConversation(userId, mode);
53
+ }
54
+
55
+ // user messages add to convo like {role:user ,message:"jhfdks"}
56
+ async addMessage(conversationId, role, content) {
57
+ const contentStr =
58
+ typeof content === "string" ? content : JSON.stringify(content);
59
+
60
+ return this.request(
61
+ `/conversations/${encodeURIComponent(conversationId)}/messages`,
62
+ {
63
+ method: "POST",
64
+ body: JSON.stringify({
65
+ role,
66
+ content: contentStr,
67
+ }),
68
+ }
69
+ );
70
+ }
71
+
72
+
73
+ async getMessages(conversationId) {
74
+ const messages = await this.request(
75
+ `/conversations/${encodeURIComponent(conversationId)}/messages`
76
+ );
77
+
78
+ return messages.map((msg) => ({
79
+ ...msg,
80
+ content: this.parseContent(msg.content),
81
+ }));
82
+ }
83
+ async getUserConversation(userId) {
84
+ return this.request("/conversations");
85
+ }
86
+
87
+ async deleteConversation(conversationId, userId) {
88
+ return this.request(
89
+ `/conversations/${encodeURIComponent(conversationId)}`,
90
+ {
91
+ method: "DELETE",
92
+ }
93
+ );
94
+ }
95
+ async updateTitle(conversationId, title) {
96
+ return this.request(
97
+ `/conversations/${encodeURIComponent(conversationId)}`,
98
+ {
99
+ method: "PATCH",
100
+ body: JSON.stringify({ title }),
101
+ }
102
+ );
103
+ }
104
+
105
+ parseContent(content){
106
+ try {
107
+ return JSON.parse(content)
108
+ } catch (error) {
109
+ return content;
110
+ }
111
+ }
112
+
113
+ formatMessageForAI(messages){
114
+ return messages.map((msg)=>({
115
+ role:msg.role,
116
+ content:typeof msg.content==="string" ? msg.content : JSON.stringify(msg.content)
117
+ }))
118
+ }
119
+
120
+ }
package/src/index.js DELETED
@@ -1,66 +0,0 @@
1
-
2
- import "dotenv/config";
3
-
4
- import express from "express";
5
- import cors from "cors";
6
- import { toNodeHandler, fromNodeHeaders } from "better-auth/node";
7
- import { auth } from "./lib/auth.js";
8
-
9
- const app = express();
10
-
11
- const frontendUrl =
12
- process.env.CLIENT_URL || "http://localhost:3000";
13
-
14
- const allowedOrigins = [
15
- "http://localhost:3000",
16
- frontendUrl,
17
- ].filter(Boolean);
18
-
19
- app.set("trust proxy", 1);
20
-
21
- app.use(
22
- cors({
23
- origin: allowedOrigins,
24
- methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
25
- credentials: true,
26
- })
27
- );
28
-
29
- // Better Auth handler must come before express.json().
30
- app.all("/api/auth/*splat", toNodeHandler(auth));
31
-
32
- app.use(express.json());
33
-
34
- app.get("/api/me", async (req, res, next) => {
35
- try {
36
- const session = await auth.api.getSession({
37
- headers: fromNodeHeaders(req.headers),
38
- });
39
-
40
- return res.json(session);
41
- } catch (error) {
42
- next(error);
43
- }
44
- });
45
-
46
- app.get("/device", (req, res) => {
47
- const userCode = req.query.user_code;
48
-
49
- const destination = new URL("/device", frontendUrl);
50
-
51
- if (typeof userCode === "string") {
52
- destination.searchParams.set("user_code", userCode);
53
- }
54
-
55
- return res.redirect(destination.toString());
56
- });
57
-
58
- app.get("/", (_req, res) => {
59
- res.status(200).send("Pulse API is running");
60
- });
61
-
62
- const port = process.env.PORT || 3004;
63
-
64
- app.listen(port, "0.0.0.0", () => {
65
- console.log(`Pulse API listening on port ${port}`);
66
- });
package/src/lib/auth.js DELETED
@@ -1,29 +0,0 @@
1
- import { betterAuth } from "better-auth";
2
- import { prismaAdapter } from "better-auth/adapters/prisma";
3
- import { deviceAuthorization } from "better-auth/plugins";
4
-
5
- import { prisma } from "./db.js";
6
-
7
- export const auth = betterAuth({
8
- baseURL: process.env.BETTER_AUTH_URL,
9
- database: prismaAdapter(prisma, {
10
- provider: "postgresql",
11
- }),
12
- basePath:"/api/auth",
13
- trustedOrigins:["http://localhost:3000",
14
- process.env.CLIENT_URL,
15
- ],
16
- socialProviders: {
17
- github: {
18
- clientId: process.env.GITHUB_CLIENT_ID,
19
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
20
- },
21
- },
22
- plugins: [
23
- deviceAuthorization({
24
- verificationUri: "/device",
25
- expiresIn:"30m",
26
- interval:"5s"
27
- }),
28
- ],
29
- });
package/src/lib/db.js DELETED
@@ -1,9 +0,0 @@
1
- import "dotenv/config";
2
- import { PrismaNeon } from "@prisma/adapter-neon";
3
- import { PrismaClient } from "../generated/prisma/client.js";
4
-
5
- const adapter = new PrismaNeon({
6
- connectionString: process.env.DATABASE_URL,
7
- });
8
-
9
- export const prisma = new PrismaClient({ adapter });