@dubeyvishal/orbital-cli 1.0.3 → 1.0.5

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 (32) hide show
  1. package/README.md +13 -0
  2. package/package.json +2 -1
  3. package/server/src/cli/ai/googleService.js +3 -4
  4. package/server/src/cli/chat/chat-with-ai-agent.js +45 -53
  5. package/server/src/cli/chat/chat-with-ai-tools.js +72 -84
  6. package/server/src/cli/chat/chat-with-ai.js +58 -67
  7. package/server/src/cli/commands/General/openApp.js +3 -20
  8. package/server/src/cli/commands/ai/wakeUp.js +11 -22
  9. package/server/src/cli/commands/auth/aboutMe.js +2 -1
  10. package/server/src/cli/commands/auth/login.js +34 -11
  11. package/server/src/cli/commands/config/setkey.js +12 -5
  12. package/server/src/cli/main.js +1 -1
  13. package/server/src/cli/utils/apiClient.js +66 -0
  14. package/server/src/config/agentConfig.js +22 -17
  15. package/server/src/config/api.js +22 -0
  16. package/server/src/config/env.js +2 -16
  17. package/server/src/config/toolConfig.js +7 -0
  18. package/server/src/controllers/aiController.js +58 -0
  19. package/server/src/controllers/cliController.js +77 -0
  20. package/server/src/db/prisma.js +3 -0
  21. package/server/src/index.js +12 -6
  22. package/server/src/lib/auth.js +4 -4
  23. package/server/src/lib/credentialStore.js +47 -0
  24. package/server/src/lib/orbitalConfig.js +165 -17
  25. package/server/src/lib/token.js +8 -14
  26. package/server/src/middleware/auth.js +39 -0
  27. package/server/src/middleware/errorHandler.js +11 -0
  28. package/server/src/routes/authRoutes.js +14 -0
  29. package/server/src/routes/cliRoutes.js +18 -0
  30. package/server/src/services/aiService.js +10 -0
  31. package/server/src/services/chatService.js +1 -0
  32. package/server/src/types/express.d.ts +19 -0
@@ -0,0 +1,47 @@
1
+ const ORBITAL_KEYTAR_SERVICE = "orbital-cli";
2
+ const ORBITAL_API_KEY_ACCOUNT = "api-key";
3
+
4
+ const loadKeytar = async () => {
5
+ try {
6
+ const mod = await import("keytar");
7
+ return mod?.default ?? mod;
8
+ } catch (err) {
9
+ const wrapped = new Error(
10
+ "keytar is not available. Install it and ensure your OS keychain is supported."
11
+ );
12
+ wrapped.cause = err;
13
+ wrapped.code = "ORBITAL_KEYTAR_NOT_AVAILABLE";
14
+ throw wrapped;
15
+ }
16
+ };
17
+
18
+ export const getCredentialServiceName = () => ORBITAL_KEYTAR_SERVICE;
19
+ export const getApiKeyAccountName = () => ORBITAL_API_KEY_ACCOUNT;
20
+
21
+ export const getStoredApiKey = async () => {
22
+ const keytar = await loadKeytar();
23
+ const value = await keytar.getPassword(
24
+ ORBITAL_KEYTAR_SERVICE,
25
+ ORBITAL_API_KEY_ACCOUNT
26
+ );
27
+ return typeof value === "string" ? value.trim() : "";
28
+ };
29
+
30
+ export const storeApiKey = async (apiKey) => {
31
+ const trimmed = typeof apiKey === "string" ? apiKey.trim() : "";
32
+ if (!trimmed) throw new Error("API key is required");
33
+
34
+ const keytar = await loadKeytar();
35
+ await keytar.setPassword(
36
+ ORBITAL_KEYTAR_SERVICE,
37
+ ORBITAL_API_KEY_ACCOUNT,
38
+ trimmed
39
+ );
40
+ return true;
41
+ };
42
+
43
+ export const deleteStoredApiKey = async () => {
44
+ const keytar = await loadKeytar();
45
+ await keytar.deletePassword(ORBITAL_KEYTAR_SERVICE, ORBITAL_API_KEY_ACCOUNT);
46
+ return true;
47
+ };
@@ -3,42 +3,190 @@ import fsPromises from "fs/promises";
3
3
  import os from "os";
4
4
  import path from "path";
5
5
 
6
+ import { getStoredApiKey, storeApiKey } from "./credentialStore.js";
7
+
6
8
  export const ORBITAL_CONFIG_DIR = path.join(os.homedir(), ".orbital");
7
9
  export const ORBITAL_CONFIG_FILE = path.join(ORBITAL_CONFIG_DIR, "config.json");
8
10
 
11
+ const normalizeOrbitalConfig = (value) => {
12
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
13
+
14
+ const next = { ...value };
15
+
16
+ // Legacy metadata (no longer written).
17
+ if ("updatedAt" in next) delete next.updatedAt;
18
+
19
+ // The Gemini API key is no longer stored on disk.
20
+ if ("gemini_api_key" in next) delete next.gemini_api_key;
21
+ if ("geminiApiKey" in next) delete next.geminiApiKey;
22
+
23
+ return next;
24
+ };
25
+
9
26
  export const readOrbitalConfigSync = () => {
10
27
  try {
11
28
  if (!fs.existsSync(ORBITAL_CONFIG_FILE)) return {};
12
29
  const raw = fs.readFileSync(ORBITAL_CONFIG_FILE, "utf-8");
13
30
  const parsed = JSON.parse(raw);
14
- return parsed && typeof parsed === "object" ? parsed : {};
31
+ return normalizeOrbitalConfig(parsed);
15
32
  } catch {
16
33
  return {};
17
34
  }
18
35
  };
19
36
 
20
- export const getGeminiApiKeySync = () => {
21
- const config = readOrbitalConfigSync();
22
- const key = config?.geminiApiKey;
23
- return typeof key === "string" ? key.trim() : "";
37
+ const readOrbitalConfigRawSync = () => {
38
+ try {
39
+ if (!fs.existsSync(ORBITAL_CONFIG_FILE)) return {};
40
+ const raw = fs.readFileSync(ORBITAL_CONFIG_FILE, "utf-8");
41
+ const parsed = JSON.parse(raw);
42
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
43
+ ? parsed
44
+ : {};
45
+ } catch {
46
+ return {};
47
+ }
24
48
  };
25
49
 
26
- export const setGeminiApiKey = async (apiKey) => {
27
- const trimmed = typeof apiKey === "string" ? apiKey.trim() : "";
28
- if (!trimmed) throw new Error("API key is required");
50
+ export const readOrbitalConfig = async () => {
51
+ try {
52
+ if (!fs.existsSync(ORBITAL_CONFIG_FILE)) return {};
53
+ const raw = await fsPromises.readFile(ORBITAL_CONFIG_FILE, "utf-8");
54
+ const parsed = JSON.parse(raw);
55
+ return normalizeOrbitalConfig(parsed);
56
+ } catch {
57
+ return {};
58
+ }
59
+ };
29
60
 
61
+ export const writeOrbitalConfig = async (nextConfig) => {
30
62
  await fsPromises.mkdir(ORBITAL_CONFIG_DIR, { recursive: true });
31
63
 
32
- const nextConfig = {
33
- geminiApiKey: trimmed,
34
- updatedAt: new Date().toISOString(),
35
- };
36
-
37
- await fsPromises.writeFile(
38
- ORBITAL_CONFIG_FILE,
39
- JSON.stringify(nextConfig, null, 2),
40
- "utf-8",
64
+ const normalized = normalizeOrbitalConfig(nextConfig);
65
+ const tmpFile = path.join(
66
+ ORBITAL_CONFIG_DIR,
67
+ `config.json.${process.pid}.${Date.now()}.tmp`
41
68
  );
42
69
 
70
+ await fsPromises.writeFile(tmpFile, JSON.stringify(normalized, null, 2), "utf-8");
71
+ try {
72
+ await fsPromises.rename(tmpFile, ORBITAL_CONFIG_FILE);
73
+ } catch (err) {
74
+ // Windows cannot rename over an existing file.
75
+ await fsPromises.unlink(ORBITAL_CONFIG_FILE).catch(() => {});
76
+ await fsPromises.rename(tmpFile, ORBITAL_CONFIG_FILE);
77
+ }
78
+
43
79
  return true;
44
80
  };
81
+
82
+ export const updateOrbitalConfig = async (patch = {}) => {
83
+ const current = await readOrbitalConfig();
84
+ const nextConfig = normalizeOrbitalConfig({ ...current, ...patch });
85
+ await writeOrbitalConfig(nextConfig);
86
+ return nextConfig;
87
+ };
88
+
89
+ const getGeminiApiKeyFromEnvSync = () => {
90
+ return typeof process.env.GOOGLE_GENERATIVE_AI_API_KEY === "string"
91
+ ? process.env.GOOGLE_GENERATIVE_AI_API_KEY.trim()
92
+ : "";
93
+ };
94
+
95
+ const getLegacyGeminiApiKeyFromConfigSync = () => {
96
+ const config = readOrbitalConfigRawSync();
97
+ const key =
98
+ (typeof config?.gemini_api_key === "string" && config.gemini_api_key.trim()) ||
99
+ (typeof config?.geminiApiKey === "string" && config.geminiApiKey.trim()) ||
100
+ "";
101
+ return key;
102
+ };
103
+
104
+ const removeLegacyGeminiApiKeyFromConfig = async () => {
105
+ const currentRaw = readOrbitalConfigRawSync();
106
+ if (!currentRaw || typeof currentRaw !== "object") return false;
107
+ if (!("gemini_api_key" in currentRaw) && !("geminiApiKey" in currentRaw)) return false;
108
+
109
+ const next = { ...currentRaw };
110
+ if ("gemini_api_key" in next) delete next.gemini_api_key;
111
+ if ("geminiApiKey" in next) delete next.geminiApiKey;
112
+ await writeOrbitalConfig(next);
113
+ return true;
114
+ };
115
+
116
+ export const hydrateGeminiApiKeyEnv = async () => {
117
+ const already = getGeminiApiKeyFromEnvSync();
118
+ if (already) return already;
119
+
120
+ // Primary: OS credential manager via keytar.
121
+ try {
122
+ const fromKeytar = await getStoredApiKey();
123
+ if (fromKeytar) {
124
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = fromKeytar;
125
+ return fromKeytar;
126
+ }
127
+ } catch {
128
+ // ignore here; requireGeminiApiKey will surface a helpful error
129
+ }
130
+
131
+ // One-time migration: if the key exists in legacy ~/.orbital/config.json,
132
+ // move it into keytar and remove it from disk.
133
+ const legacy = getLegacyGeminiApiKeyFromConfigSync();
134
+ if (legacy) {
135
+ await storeApiKey(legacy);
136
+ await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
137
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = legacy;
138
+ return legacy;
139
+ }
140
+
141
+ return "";
142
+ };
143
+
144
+ export const getGeminiApiKeySync = () => getGeminiApiKeyFromEnvSync();
145
+
146
+ export const getGeminiApiKey = async () => {
147
+ const fromEnv = getGeminiApiKeyFromEnvSync();
148
+ if (fromEnv) return fromEnv;
149
+ return await hydrateGeminiApiKeyEnv();
150
+ };
151
+
152
+ export const hasGeminiApiKeySync = () => Boolean(getGeminiApiKeyFromEnvSync());
153
+
154
+ export const requireGeminiApiKeySync = () => {
155
+ const apiKey = getGeminiApiKeyFromEnvSync();
156
+ if (!apiKey) {
157
+ const err = new Error(
158
+ "Gemini API key not set. Run: orbital set-key <API_KEY>"
159
+ );
160
+ err.code = "ORBITAL_GEMINI_API_KEY_NOT_SET";
161
+ throw err;
162
+ }
163
+ return apiKey;
164
+ };
165
+
166
+ export const requireGeminiApiKey = async () => {
167
+ const apiKey = await getGeminiApiKey();
168
+ if (!apiKey) {
169
+ const err = new Error(
170
+ "Gemini API key not set. Run: orbital set-key <API_KEY>"
171
+ );
172
+ err.code = "ORBITAL_GEMINI_API_KEY_NOT_SET";
173
+ throw err;
174
+ }
175
+ return apiKey;
176
+ };
177
+
178
+ export const setGeminiApiKey = async (apiKey) => {
179
+ const trimmed = typeof apiKey === "string" ? apiKey.trim() : "";
180
+ if (!trimmed) throw new Error("API key is required");
181
+
182
+ await storeApiKey(trimmed);
183
+ // Ensure any legacy on-disk key is removed.
184
+ await removeLegacyGeminiApiKeyFromConfig().catch(() => {});
185
+
186
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = trimmed;
187
+ return true;
188
+ };
189
+
190
+ // Back-compat exports (no longer reads from config specifically).
191
+ export const requireGeminiApiKeyFromConfigSync = requireGeminiApiKeySync;
192
+
@@ -1,18 +1,14 @@
1
1
 
2
- import chalk from "chalk"
3
- import fs from "fs/promises";
4
- import os from "os";
5
- import path from "path";
2
+ import chalk from "chalk";
3
+ import { ORBITAL_CONFIG_FILE, readOrbitalConfig, updateOrbitalConfig } from "./orbitalConfig.js";
6
4
 
7
-
8
- export const CONFIG_DIR = path.join(os.homedir(), ".better-auth");
9
- export const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
5
+ export const TOKEN_FILE = ORBITAL_CONFIG_FILE;
10
6
 
11
7
  export const getStoredToken = async ()=>{
12
8
  try{
13
- const data = await fs.readFile(TOKEN_FILE , "utf-8");
14
- const token = JSON.parse(data);
15
- return token ;
9
+ const config = await readOrbitalConfig();
10
+ const token = config?.auth;
11
+ return token || null;
16
12
  }
17
13
  catch(error){
18
14
  return null ;
@@ -22,8 +18,6 @@ export const getStoredToken = async ()=>{
22
18
 
23
19
  export const storeToken = async(token)=>{
24
20
  try{
25
- await fs.mkdir(CONFIG_DIR , { recursive : true });
26
-
27
21
  const tokenData = {
28
22
  access_token : token.access_token,
29
23
  refresh_token : token.refresh_token ,
@@ -35,7 +29,7 @@ export const storeToken = async(token)=>{
35
29
  created_at : new Date().toISOString() ,
36
30
  };
37
31
 
38
- await fs.writeFile(TOKEN_FILE , JSON.stringify(tokenData , null , 2), "utf-8");
32
+ await updateOrbitalConfig({ auth: tokenData });
39
33
  return true ;
40
34
  }
41
35
  catch(err){
@@ -46,7 +40,7 @@ export const storeToken = async(token)=>{
46
40
 
47
41
  export const clearStoredToken = async ()=>{
48
42
  try{
49
- await fs.unlink(TOKEN_FILE);
43
+ await updateOrbitalConfig({ auth: null });
50
44
  return true ;
51
45
  }
52
46
  catch(err){
@@ -0,0 +1,39 @@
1
+ import prisma from "../db/prisma.js";
2
+
3
+ const parseAuthHeader = (value) => {
4
+ if (!value || typeof value !== "string") return null;
5
+ const [scheme, credentials] = value.split(" ");
6
+ if (!scheme || !credentials) return null;
7
+ if (scheme.toLowerCase() !== "bearer") return null;
8
+ return credentials;
9
+ };
10
+
11
+ export const requireApiAuth = async (req, res, next) => {
12
+ try {
13
+ const token = parseAuthHeader(req.headers.authorization);
14
+ if (!token) {
15
+ return res.status(401).json({ error: "Missing or invalid Authorization header" });
16
+ }
17
+
18
+ const session = await prisma.session.findUnique({
19
+ where: { token },
20
+ include: { user: true },
21
+ });
22
+
23
+ if (!session || !session.user) {
24
+ return res.status(401).json({ error: "Invalid session" });
25
+ }
26
+
27
+ if (session.expiresAt && session.expiresAt.getTime() <= Date.now()) {
28
+ return res.status(401).json({ error: "Session expired" });
29
+ }
30
+
31
+ req.user = session.user;
32
+ req.session = session;
33
+ req.accessToken = token;
34
+
35
+ return next();
36
+ } catch (error) {
37
+ return next(error);
38
+ }
39
+ };
@@ -0,0 +1,11 @@
1
+ export const errorHandler = (err, req, res, _next) => {
2
+ const statusCode = Number(err?.statusCode || err?.status || 500);
3
+ const message = err?.message || "Internal server error";
4
+
5
+ if (process.env.NODE_ENV !== "production") {
6
+ // eslint-disable-next-line no-console
7
+ console.error(err);
8
+ }
9
+
10
+ res.status(statusCode).json({ error: message });
11
+ };
@@ -0,0 +1,14 @@
1
+ import { Router } from "express";
2
+
3
+ const router = Router();
4
+
5
+ router.get("/github/client-id", (req, res) => {
6
+ const clientId = process.env.GITHUB_CLIENT_ID;
7
+ if (!clientId) {
8
+ return res.status(500).json({ error: "GitHub client ID is not configured" });
9
+ }
10
+
11
+ return res.json({ client_id: clientId });
12
+ });
13
+
14
+ export default router;
@@ -0,0 +1,18 @@
1
+ import { Router } from "express";
2
+ import { requireApiAuth } from "../middleware/auth.js";
3
+ import { addMessage, getMe, getMessages, initConversation, updateTitle } from "../controllers/cliController.js";
4
+ import { generateAgentPlan, respond } from "../controllers/aiController.js";
5
+
6
+ const router = Router();
7
+
8
+ router.use(requireApiAuth);
9
+
10
+ router.get("/me", getMe);
11
+ router.post("/conversations/init", initConversation);
12
+ router.post("/messages", addMessage);
13
+ router.get("/messages", getMessages);
14
+ router.patch("/conversations/:id/title", updateTitle);
15
+ router.post("/ai/respond", respond);
16
+ router.post("/ai/agent", generateAgentPlan);
17
+
18
+ export default router;
@@ -0,0 +1,10 @@
1
+ import { AIService } from "../cli/ai/googleService.js";
2
+
3
+ let cachedService;
4
+
5
+ export const getAIService = () => {
6
+ if (!cachedService) {
7
+ cachedService = new AIService();
8
+ }
9
+ return cachedService;
10
+ };
@@ -0,0 +1 @@
1
+ export { ChatService } from "../service/chatService.js";
@@ -0,0 +1,19 @@
1
+ export {};
2
+
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ user?: {
7
+ id: string;
8
+ name?: string | null;
9
+ email?: string | null;
10
+ image?: string | null;
11
+ };
12
+ session?: {
13
+ id: string;
14
+ expiresAt?: Date | null;
15
+ };
16
+ accessToken?: string;
17
+ }
18
+ }
19
+ }