vibebusiness 1.2.49 → 1.2.53
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/.next/standalone/.env +1 -1
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +20 -20
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +4 -4
- package/.next/standalone/.next/prerender-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/api/analyze/route.js +1 -1
- package/.next/standalone/.next/server/app/api/analyze/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/provider-status/route.js +1 -0
- package/.next/standalone/.next/server/app/api/provider-status/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/goals/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +12 -11
- package/.next/standalone/.next/server/chunks/3794.js +92 -17
- package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/data/business-context.json +60 -6
- package/.next/standalone/data/goals.json +50 -10
- package/.next/standalone/data/ideas.json +1278 -597
- package/.next/standalone/data/implementations.json +101 -0
- package/.next/standalone/data/posthog.json +68 -0
- package/.next/standalone/data/sessions.json +5 -4
- package/.next/standalone/data/social.json +86 -3
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/analyze.ts +10 -0
- package/.next/standalone/scripts/implement.ts +37 -30
- package/.next/standalone/scripts/skills/social-media.ts +172 -2
- package/.next/static/chunks/59-76eecfb6e8216043.js +1 -0
- package/.next/static/chunks/app/goals/[id]/page-051064c3146131cc.js +1 -0
- package/.next/static/chunks/app/ideas/[id]/page-adaeb619cd0425e9.js +1 -0
- package/.next/static/chunks/app/roadmap/[id]/page-3822586c0d64fff1.js +1 -0
- package/.next/static/chunks/app/settings/page-2204cc936ec9474b.js +1 -0
- package/.next/static/chunks/app/social/page-018893f87b308651.js +1 -0
- package/.next/static/chunks/{main-61d2f25883998186.js → main-c99f3473a63aa803.js} +1 -1
- package/.next/static/css/d8bd6d69d1ff97e3.css +3 -0
- package/dist/bin/vibebusiness.js +12 -0
- package/dist/scripts/analyze.js +170 -68
- package/dist/scripts/chat.js +137 -36
- package/dist/scripts/heartbeat.js +617 -348
- package/dist/scripts/implement.js +218 -70
- package/dist/scripts/init.js +164 -16
- package/dist/scripts/provider.js +428 -0
- package/dist/scripts/scan.js +44 -5
- package/package.json +1 -1
- package/.next/static/chunks/59-a053b1c0e85128de.js +0 -1
- package/.next/static/chunks/app/goals/[id]/page-8dbeab5cc8cf0988.js +0 -1
- package/.next/static/chunks/app/ideas/[id]/page-89e3625db9017166.js +0 -1
- package/.next/static/chunks/app/roadmap/[id]/page-f437e783039534c4.js +0 -1
- package/.next/static/chunks/app/settings/page-d2d630a799b6b495.js +0 -1
- package/.next/static/chunks/app/social/page-69e480936711ccf2.js +0 -1
- package/.next/static/css/179dd3e0738c2fe4.css +0 -3
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → p1Sl4kPMQcYC3c1LgbNSP}/_buildManifest.js +0 -0
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → p1Sl4kPMQcYC3c1LgbNSP}/_ssgManifest.js +0 -0
package/dist/scripts/init.js
CHANGED
|
@@ -41,6 +41,63 @@ function resolveScript(baseDir, scriptName) {
|
|
|
41
41
|
var import_child_process = require("child_process");
|
|
42
42
|
var fs = __toESM(require("fs"));
|
|
43
43
|
var path2 = __toESM(require("path"));
|
|
44
|
+
var CREDENTIALS_DIR = path2.join(process.env.HOME || "", ".vibebusiness");
|
|
45
|
+
var CREDENTIALS_FILE = path2.join(CREDENTIALS_DIR, "credentials.json");
|
|
46
|
+
function saveCredential(envVar, value) {
|
|
47
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
48
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
49
|
+
}
|
|
50
|
+
const existing = loadCredentials();
|
|
51
|
+
existing[envVar] = value;
|
|
52
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
53
|
+
fs.chmodSync(CREDENTIALS_FILE, 384);
|
|
54
|
+
}
|
|
55
|
+
function loadCredentials() {
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
58
|
+
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
async function validateApiKey(provider, key) {
|
|
65
|
+
if (provider === "anthropic-api") {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"x-api-key": key,
|
|
72
|
+
"anthropic-version": "2023-06-01"
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
model: "claude-haiku-4-5-20251001",
|
|
76
|
+
max_tokens: 1,
|
|
77
|
+
messages: [{ role: "user", content: "Hi" }]
|
|
78
|
+
}),
|
|
79
|
+
signal: AbortSignal.timeout(15e3)
|
|
80
|
+
});
|
|
81
|
+
if (response.ok) {
|
|
82
|
+
return { valid: true };
|
|
83
|
+
}
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
if (response.status === 401) {
|
|
86
|
+
return { valid: false, error: "Invalid API key" };
|
|
87
|
+
}
|
|
88
|
+
if (response.status === 403) {
|
|
89
|
+
return { valid: false, error: "API key does not have access to this model" };
|
|
90
|
+
}
|
|
91
|
+
if (response.status === 429 || response.status >= 500) {
|
|
92
|
+
return { valid: true };
|
|
93
|
+
}
|
|
94
|
+
return { valid: false, error: `API error ${response.status}: ${errorText.slice(0, 100)}` };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { valid: false, error: `Connection failed: ${err.message}` };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { valid: true };
|
|
100
|
+
}
|
|
44
101
|
function detectClaudeCLI(customPath) {
|
|
45
102
|
const claudeBin = customPath || "claude";
|
|
46
103
|
try {
|
|
@@ -73,13 +130,27 @@ function detectClaudeCLI(customPath) {
|
|
|
73
130
|
}
|
|
74
131
|
function detectBYOKKeys() {
|
|
75
132
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
76
|
-
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY };
|
|
133
|
+
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
|
|
77
134
|
}
|
|
78
135
|
if (process.env.OPENAI_API_KEY) {
|
|
79
|
-
return { provider: "openai-api", key: process.env.OPENAI_API_KEY };
|
|
136
|
+
return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
|
|
80
137
|
}
|
|
81
138
|
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
82
|
-
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY };
|
|
139
|
+
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
|
|
140
|
+
}
|
|
141
|
+
const creds = loadCredentials();
|
|
142
|
+
if (creds.ANTHROPIC_API_KEY) {
|
|
143
|
+
process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
|
|
144
|
+
return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
|
|
145
|
+
}
|
|
146
|
+
if (creds.OPENAI_API_KEY) {
|
|
147
|
+
process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
|
|
148
|
+
return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
|
|
149
|
+
}
|
|
150
|
+
if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
|
|
151
|
+
const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
|
|
152
|
+
process.env.GOOGLE_API_KEY = key;
|
|
153
|
+
return { provider: "google-api", key, source: "credentials" };
|
|
83
154
|
}
|
|
84
155
|
return null;
|
|
85
156
|
}
|
|
@@ -125,6 +196,53 @@ function saveProviderConfig(config) {
|
|
|
125
196
|
fs.writeFileSync(PROVIDER_CONFIG_FILE, JSON.stringify(safeConfig, null, 2));
|
|
126
197
|
}
|
|
127
198
|
|
|
199
|
+
// scripts/lib/secure-input.ts
|
|
200
|
+
function askSecret(prompt) {
|
|
201
|
+
return new Promise((resolve2, reject) => {
|
|
202
|
+
process.stdout.write(prompt);
|
|
203
|
+
const input = [];
|
|
204
|
+
if (!process.stdin.isTTY) {
|
|
205
|
+
let data = "";
|
|
206
|
+
process.stdin.setEncoding("utf-8");
|
|
207
|
+
process.stdin.on("data", (chunk) => {
|
|
208
|
+
data += chunk;
|
|
209
|
+
});
|
|
210
|
+
process.stdin.on("end", () => resolve2(data.trim()));
|
|
211
|
+
process.stdin.resume();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
process.stdin.setRawMode(true);
|
|
215
|
+
process.stdin.resume();
|
|
216
|
+
process.stdin.setEncoding("utf-8");
|
|
217
|
+
const onData = (ch) => {
|
|
218
|
+
const code = ch.charCodeAt(0);
|
|
219
|
+
if (ch === "\r" || ch === "\n") {
|
|
220
|
+
cleanup();
|
|
221
|
+
process.stdout.write("\n");
|
|
222
|
+
resolve2(input.join(""));
|
|
223
|
+
} else if (code === 3) {
|
|
224
|
+
cleanup();
|
|
225
|
+
process.stdout.write("\n");
|
|
226
|
+
reject(new Error("User cancelled input"));
|
|
227
|
+
} else if (code === 127 || code === 8) {
|
|
228
|
+
if (input.length > 0) {
|
|
229
|
+
input.pop();
|
|
230
|
+
process.stdout.write("\b \b");
|
|
231
|
+
}
|
|
232
|
+
} else if (code >= 32) {
|
|
233
|
+
input.push(ch);
|
|
234
|
+
process.stdout.write("*");
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const cleanup = () => {
|
|
238
|
+
process.stdin.setRawMode(false);
|
|
239
|
+
process.stdin.pause();
|
|
240
|
+
process.stdin.removeListener("data", onData);
|
|
241
|
+
};
|
|
242
|
+
process.stdin.on("data", onData);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
128
246
|
// scripts/lib/license.ts
|
|
129
247
|
var fs2 = __toESM(require("fs"));
|
|
130
248
|
var path3 = __toESM(require("path"));
|
|
@@ -788,6 +906,8 @@ async function stepAIProvider(rootDir) {
|
|
|
788
906
|
}
|
|
789
907
|
if (!detection.message.includes("No AI provider")) {
|
|
790
908
|
printSuccess(`AI: ${detection.message}`);
|
|
909
|
+
print(" Heartbeat reasoning and idea evaluation will work with your API key.");
|
|
910
|
+
print(" Codebase analysis and implementation require Claude Code CLI.");
|
|
791
911
|
const config2 = { type: detection.provider };
|
|
792
912
|
saveProviderConfig(config2);
|
|
793
913
|
return config2;
|
|
@@ -800,7 +920,7 @@ async function stepAIProvider(rootDir) {
|
|
|
800
920
|
"Skip for now (AI features will be disabled)"
|
|
801
921
|
]);
|
|
802
922
|
if (choice === 3) {
|
|
803
|
-
printWarning("AI features disabled. Configure later with `vibebusiness
|
|
923
|
+
printWarning("AI features disabled. Configure later with `vibebusiness provider setup`.");
|
|
804
924
|
const config2 = { type: "claude-cli" };
|
|
805
925
|
return config2;
|
|
806
926
|
}
|
|
@@ -810,21 +930,49 @@ async function stepAIProvider(rootDir) {
|
|
|
810
930
|
2: { type: "google-api", envVar: "GOOGLE_API_KEY" }
|
|
811
931
|
};
|
|
812
932
|
const selected = providerMap[choice];
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
933
|
+
let key = "";
|
|
934
|
+
let validated = false;
|
|
935
|
+
const MAX_ATTEMPTS = 3;
|
|
936
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
937
|
+
try {
|
|
938
|
+
rl.pause();
|
|
939
|
+
key = await askSecret(`
|
|
940
|
+
Enter your ${selected.envVar}: `);
|
|
941
|
+
rl.resume();
|
|
942
|
+
} catch {
|
|
943
|
+
rl.resume();
|
|
944
|
+
printWarning("Input cancelled.");
|
|
945
|
+
const config2 = { type: "claude-cli" };
|
|
946
|
+
return config2;
|
|
819
947
|
}
|
|
820
|
-
if (!
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
fs7.writeFileSync(envFile, envContent);
|
|
825
|
-
printSuccess(`Saved ${selected.envVar} to .env.local`);
|
|
948
|
+
if (!key.trim()) {
|
|
949
|
+
printWarning("No key entered. Skipping AI provider setup.");
|
|
950
|
+
const config2 = { type: "claude-cli" };
|
|
951
|
+
return config2;
|
|
826
952
|
}
|
|
953
|
+
key = key.trim();
|
|
954
|
+
process.stdout.write(" Validating... ");
|
|
955
|
+
const result = await validateApiKey(selected.type, key);
|
|
956
|
+
if (result.valid) {
|
|
957
|
+
console.log("\u2713 API key is valid");
|
|
958
|
+
validated = true;
|
|
959
|
+
break;
|
|
960
|
+
} else {
|
|
961
|
+
console.log(`\u2717 ${result.error || "Invalid key"}`);
|
|
962
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
963
|
+
print(` Please try again (attempt ${attempt + 1}/${MAX_ATTEMPTS}).`);
|
|
964
|
+
} else {
|
|
965
|
+
printWarning(`Failed after ${MAX_ATTEMPTS} attempts.`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (validated && key) {
|
|
970
|
+
saveCredential(selected.envVar, key);
|
|
971
|
+
printSuccess(`Key saved to ~/.vibebusiness/credentials.json (permissions: 600)`);
|
|
827
972
|
process.env[selected.envVar] = key;
|
|
973
|
+
printSuccess(`AI: ${selected.type === "anthropic-api" ? "Anthropic API" : selected.type === "openai-api" ? "OpenAI API" : "Google AI API"} configured`);
|
|
974
|
+
print(" Heartbeat reasoning and idea evaluation work with your API key.");
|
|
975
|
+
print(" Codebase analysis and implementation require Claude Code CLI.");
|
|
828
976
|
appendAnalyticsEvent("api_key_provided", initSessionId, initAnalyticsFile, { provider: selected.type });
|
|
829
977
|
}
|
|
830
978
|
const config = { type: selected.type };
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// scripts/provider.ts
|
|
26
|
+
var readline = __toESM(require("readline"));
|
|
27
|
+
|
|
28
|
+
// scripts/lib/ai-provider.ts
|
|
29
|
+
var import_child_process = require("child_process");
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
var path = __toESM(require("path"));
|
|
32
|
+
var CREDENTIALS_DIR = path.join(process.env.HOME || "", ".vibebusiness");
|
|
33
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
34
|
+
function saveCredential(envVar, value) {
|
|
35
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
36
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
37
|
+
}
|
|
38
|
+
const existing = loadCredentials();
|
|
39
|
+
existing[envVar] = value;
|
|
40
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
41
|
+
fs.chmodSync(CREDENTIALS_FILE, 384);
|
|
42
|
+
}
|
|
43
|
+
function loadCredentials() {
|
|
44
|
+
try {
|
|
45
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
46
|
+
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
function clearCredentials() {
|
|
53
|
+
try {
|
|
54
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
55
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function maskApiKey(key) {
|
|
61
|
+
if (key.length <= 12) return "****";
|
|
62
|
+
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
63
|
+
}
|
|
64
|
+
async function validateApiKey(provider, key) {
|
|
65
|
+
if (provider === "anthropic-api") {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"x-api-key": key,
|
|
72
|
+
"anthropic-version": "2023-06-01"
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
model: "claude-haiku-4-5-20251001",
|
|
76
|
+
max_tokens: 1,
|
|
77
|
+
messages: [{ role: "user", content: "Hi" }]
|
|
78
|
+
}),
|
|
79
|
+
signal: AbortSignal.timeout(15e3)
|
|
80
|
+
});
|
|
81
|
+
if (response.ok) {
|
|
82
|
+
return { valid: true };
|
|
83
|
+
}
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
if (response.status === 401) {
|
|
86
|
+
return { valid: false, error: "Invalid API key" };
|
|
87
|
+
}
|
|
88
|
+
if (response.status === 403) {
|
|
89
|
+
return { valid: false, error: "API key does not have access to this model" };
|
|
90
|
+
}
|
|
91
|
+
if (response.status === 429 || response.status >= 500) {
|
|
92
|
+
return { valid: true };
|
|
93
|
+
}
|
|
94
|
+
return { valid: false, error: `API error ${response.status}: ${errorText.slice(0, 100)}` };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { valid: false, error: `Connection failed: ${err.message}` };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { valid: true };
|
|
100
|
+
}
|
|
101
|
+
function detectClaudeCLI(customPath) {
|
|
102
|
+
const claudeBin = customPath || "claude";
|
|
103
|
+
try {
|
|
104
|
+
const version = (0, import_child_process.execSync)(`${claudeBin} --version 2>/dev/null`, {
|
|
105
|
+
timeout: 1e4,
|
|
106
|
+
encoding: "utf-8"
|
|
107
|
+
}).trim();
|
|
108
|
+
return { found: true, path: claudeBin, version };
|
|
109
|
+
} catch {
|
|
110
|
+
const commonPaths = [
|
|
111
|
+
"/usr/local/bin/claude",
|
|
112
|
+
path.join(process.env.HOME || "", ".claude", "bin", "claude"),
|
|
113
|
+
path.join(process.env.HOME || "", ".local", "bin", "claude")
|
|
114
|
+
];
|
|
115
|
+
for (const p of commonPaths) {
|
|
116
|
+
try {
|
|
117
|
+
if (fs.existsSync(p)) {
|
|
118
|
+
const version = (0, import_child_process.execSync)(`${p} --version 2>/dev/null`, {
|
|
119
|
+
timeout: 1e4,
|
|
120
|
+
encoding: "utf-8"
|
|
121
|
+
}).trim();
|
|
122
|
+
return { found: true, path: p, version };
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { found: false, path: claudeBin };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function detectBYOKKeys() {
|
|
132
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
133
|
+
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
|
|
134
|
+
}
|
|
135
|
+
if (process.env.OPENAI_API_KEY) {
|
|
136
|
+
return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
|
|
137
|
+
}
|
|
138
|
+
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
139
|
+
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
|
|
140
|
+
}
|
|
141
|
+
const creds = loadCredentials();
|
|
142
|
+
if (creds.ANTHROPIC_API_KEY) {
|
|
143
|
+
process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
|
|
144
|
+
return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
|
|
145
|
+
}
|
|
146
|
+
if (creds.OPENAI_API_KEY) {
|
|
147
|
+
process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
|
|
148
|
+
return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
|
|
149
|
+
}
|
|
150
|
+
if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
|
|
151
|
+
const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
|
|
152
|
+
process.env.GOOGLE_API_KEY = key;
|
|
153
|
+
return { provider: "google-api", key, source: "credentials" };
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
var CONFIG_DIR = path.join(process.env.HOME || "", ".ai-analyst");
|
|
158
|
+
var PROVIDER_CONFIG_FILE = path.join(CONFIG_DIR, "provider.json");
|
|
159
|
+
function loadProviderConfig() {
|
|
160
|
+
try {
|
|
161
|
+
if (fs.existsSync(PROVIDER_CONFIG_FILE)) {
|
|
162
|
+
return JSON.parse(fs.readFileSync(PROVIDER_CONFIG_FILE, "utf-8"));
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function saveProviderConfig(config) {
|
|
169
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
170
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
const safeConfig = {
|
|
173
|
+
type: config.type,
|
|
174
|
+
model: config.model,
|
|
175
|
+
claudePath: config.claudePath
|
|
176
|
+
};
|
|
177
|
+
fs.writeFileSync(PROVIDER_CONFIG_FILE, JSON.stringify(safeConfig, null, 2));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// scripts/lib/secure-input.ts
|
|
181
|
+
function askSecret(prompt) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
process.stdout.write(prompt);
|
|
184
|
+
const input = [];
|
|
185
|
+
if (!process.stdin.isTTY) {
|
|
186
|
+
let data = "";
|
|
187
|
+
process.stdin.setEncoding("utf-8");
|
|
188
|
+
process.stdin.on("data", (chunk) => {
|
|
189
|
+
data += chunk;
|
|
190
|
+
});
|
|
191
|
+
process.stdin.on("end", () => resolve(data.trim()));
|
|
192
|
+
process.stdin.resume();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
process.stdin.setRawMode(true);
|
|
196
|
+
process.stdin.resume();
|
|
197
|
+
process.stdin.setEncoding("utf-8");
|
|
198
|
+
const onData = (ch) => {
|
|
199
|
+
const code = ch.charCodeAt(0);
|
|
200
|
+
if (ch === "\r" || ch === "\n") {
|
|
201
|
+
cleanup();
|
|
202
|
+
process.stdout.write("\n");
|
|
203
|
+
resolve(input.join(""));
|
|
204
|
+
} else if (code === 3) {
|
|
205
|
+
cleanup();
|
|
206
|
+
process.stdout.write("\n");
|
|
207
|
+
reject(new Error("User cancelled input"));
|
|
208
|
+
} else if (code === 127 || code === 8) {
|
|
209
|
+
if (input.length > 0) {
|
|
210
|
+
input.pop();
|
|
211
|
+
process.stdout.write("\b \b");
|
|
212
|
+
}
|
|
213
|
+
} else if (code >= 32) {
|
|
214
|
+
input.push(ch);
|
|
215
|
+
process.stdout.write("*");
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const cleanup = () => {
|
|
219
|
+
process.stdin.setRawMode(false);
|
|
220
|
+
process.stdin.pause();
|
|
221
|
+
process.stdin.removeListener("data", onData);
|
|
222
|
+
};
|
|
223
|
+
process.stdin.on("data", onData);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// scripts/provider.ts
|
|
228
|
+
var rl = readline.createInterface({
|
|
229
|
+
input: process.stdin,
|
|
230
|
+
output: process.stdout
|
|
231
|
+
});
|
|
232
|
+
function askChoice(question, options) {
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
console.log(`
|
|
235
|
+
${question}`);
|
|
236
|
+
options.forEach((opt, i) => console.log(` ${i + 1}. ${opt}`));
|
|
237
|
+
rl.question(`Choice [1-${options.length}]: `, (answer) => {
|
|
238
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
239
|
+
resolve(idx >= 0 && idx < options.length ? idx : 0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
var PROVIDER_NAMES = {
|
|
244
|
+
"claude-cli": "Claude Code CLI",
|
|
245
|
+
"anthropic-api": "Anthropic API",
|
|
246
|
+
"openai-api": "OpenAI API",
|
|
247
|
+
"google-api": "Google AI API"
|
|
248
|
+
};
|
|
249
|
+
async function cmdStatus() {
|
|
250
|
+
const line = "\u2500".repeat(50);
|
|
251
|
+
console.log(`
|
|
252
|
+
AI Provider`);
|
|
253
|
+
console.log(line);
|
|
254
|
+
const cli = detectClaudeCLI();
|
|
255
|
+
const byok = detectBYOKKeys();
|
|
256
|
+
const savedConfig = loadProviderConfig();
|
|
257
|
+
const activeProvider = cli.found ? "claude-cli" : byok?.provider || savedConfig?.type || null;
|
|
258
|
+
if (activeProvider) {
|
|
259
|
+
console.log(` Provider: ${PROVIDER_NAMES[activeProvider]}`);
|
|
260
|
+
} else {
|
|
261
|
+
console.log(" Provider: None configured");
|
|
262
|
+
}
|
|
263
|
+
if (byok) {
|
|
264
|
+
console.log(` Key: ${maskApiKey(byok.key)}`);
|
|
265
|
+
console.log(` Source: ${byok.source === "env" ? "Environment variable" : "~/.vibebusiness/credentials.json"}`);
|
|
266
|
+
process.stdout.write(" Status: ");
|
|
267
|
+
const result = await validateApiKey(byok.provider, byok.key);
|
|
268
|
+
if (result.valid) {
|
|
269
|
+
console.log("\u2713 Active");
|
|
270
|
+
} else {
|
|
271
|
+
console.log(`\u2717 ${result.error || "Invalid"}`);
|
|
272
|
+
}
|
|
273
|
+
} else if (!cli.found) {
|
|
274
|
+
console.log(" Key: Not set");
|
|
275
|
+
console.log(" Status: \u2717 No API key configured");
|
|
276
|
+
}
|
|
277
|
+
if (savedConfig?.model) {
|
|
278
|
+
console.log(` Model: ${savedConfig.model}`);
|
|
279
|
+
} else if (activeProvider === "anthropic-api") {
|
|
280
|
+
console.log(" Model: claude-sonnet-4-6 (default)");
|
|
281
|
+
}
|
|
282
|
+
if (cli.found) {
|
|
283
|
+
console.log(` CLI: ${cli.version || "installed"}`);
|
|
284
|
+
}
|
|
285
|
+
console.log("");
|
|
286
|
+
console.log(" Capabilities:");
|
|
287
|
+
const hasKey = !!byok;
|
|
288
|
+
const hasCLI = cli.found;
|
|
289
|
+
const caps = [
|
|
290
|
+
{ name: "Heartbeat reasoning", needs: "API key", available: hasKey || hasCLI },
|
|
291
|
+
{ name: "Idea evaluation", needs: "API key", available: hasKey || hasCLI },
|
|
292
|
+
{ name: "Idea decomposition", needs: "API key", available: hasKey || hasCLI },
|
|
293
|
+
{ name: "Codebase analysis", needs: "Claude Code CLI", available: hasCLI },
|
|
294
|
+
{ name: "Implementation", needs: "Claude Code CLI", available: hasCLI },
|
|
295
|
+
{ name: "Interactive chat", needs: "Claude Code CLI", available: hasCLI }
|
|
296
|
+
];
|
|
297
|
+
for (const cap of caps) {
|
|
298
|
+
const icon = cap.available ? "\u2713" : "\u2717";
|
|
299
|
+
const suffix = cap.available ? `(${cap.needs})` : `(needs ${cap.needs})`;
|
|
300
|
+
console.log(` ${icon} ${cap.name.padEnd(24)} ${suffix}`);
|
|
301
|
+
}
|
|
302
|
+
console.log("");
|
|
303
|
+
}
|
|
304
|
+
async function cmdSetup() {
|
|
305
|
+
console.log("");
|
|
306
|
+
const choice = await askChoice("How would you like to connect AI?", [
|
|
307
|
+
"Enter Anthropic API key (recommended)",
|
|
308
|
+
"Enter OpenAI API key",
|
|
309
|
+
"Enter Google AI API key",
|
|
310
|
+
"Skip for now"
|
|
311
|
+
]);
|
|
312
|
+
if (choice === 3) {
|
|
313
|
+
console.log("\n Skipped. Run `vibebusiness provider setup` when ready.\n");
|
|
314
|
+
rl.close();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const providerMap = {
|
|
318
|
+
0: { type: "anthropic-api", envVar: "ANTHROPIC_API_KEY" },
|
|
319
|
+
1: { type: "openai-api", envVar: "OPENAI_API_KEY" },
|
|
320
|
+
2: { type: "google-api", envVar: "GOOGLE_API_KEY" }
|
|
321
|
+
};
|
|
322
|
+
const selected = providerMap[choice];
|
|
323
|
+
let key = "";
|
|
324
|
+
let validated = false;
|
|
325
|
+
const MAX_ATTEMPTS = 3;
|
|
326
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
327
|
+
try {
|
|
328
|
+
rl.pause();
|
|
329
|
+
key = await askSecret(`
|
|
330
|
+
Enter your ${selected.envVar}: `);
|
|
331
|
+
rl.resume();
|
|
332
|
+
} catch {
|
|
333
|
+
rl.resume();
|
|
334
|
+
console.log("\n Cancelled.\n");
|
|
335
|
+
rl.close();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!key.trim()) {
|
|
339
|
+
console.log("\n No key entered. Skipping.\n");
|
|
340
|
+
rl.close();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
key = key.trim();
|
|
344
|
+
process.stdout.write(" Validating... ");
|
|
345
|
+
const result = await validateApiKey(selected.type, key);
|
|
346
|
+
if (result.valid) {
|
|
347
|
+
console.log("\u2713 API key is valid");
|
|
348
|
+
validated = true;
|
|
349
|
+
break;
|
|
350
|
+
} else {
|
|
351
|
+
console.log(`\u2717 ${result.error || "Invalid key"}`);
|
|
352
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
353
|
+
console.log(` Please try again (attempt ${attempt + 1}/${MAX_ATTEMPTS}).`);
|
|
354
|
+
} else {
|
|
355
|
+
console.log(` Failed after ${MAX_ATTEMPTS} attempts.
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (validated && key) {
|
|
361
|
+
saveCredential(selected.envVar, key);
|
|
362
|
+
console.log(" \u2713 Key saved to ~/.vibebusiness/credentials.json (permissions: 600)");
|
|
363
|
+
const config = { type: selected.type };
|
|
364
|
+
saveProviderConfig(config);
|
|
365
|
+
console.log(` \u2713 Provider set to ${PROVIDER_NAMES[selected.type]}
|
|
366
|
+
`);
|
|
367
|
+
}
|
|
368
|
+
rl.close();
|
|
369
|
+
}
|
|
370
|
+
async function cmdValidate() {
|
|
371
|
+
const byok = detectBYOKKeys();
|
|
372
|
+
if (!byok) {
|
|
373
|
+
console.log("\n No API key found. Run `vibebusiness provider setup` to configure one.\n");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
console.log(`
|
|
377
|
+
Provider: ${PROVIDER_NAMES[byok.provider]}`);
|
|
378
|
+
console.log(` Key: ${maskApiKey(byok.key)}`);
|
|
379
|
+
console.log(` Source: ${byok.source === "env" ? "Environment variable" : "~/.vibebusiness/credentials.json"}`);
|
|
380
|
+
process.stdout.write(" Result: ");
|
|
381
|
+
const result = await validateApiKey(byok.provider, byok.key);
|
|
382
|
+
if (result.valid) {
|
|
383
|
+
console.log("\u2713 API key is valid\n");
|
|
384
|
+
} else {
|
|
385
|
+
console.log(`\u2717 ${result.error || "Invalid"}
|
|
386
|
+
`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function cmdClear() {
|
|
391
|
+
const creds = loadCredentials();
|
|
392
|
+
const hasKeys = Object.keys(creds).length > 0;
|
|
393
|
+
if (!hasKeys) {
|
|
394
|
+
console.log("\n No stored credentials to remove.\n");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
clearCredentials();
|
|
398
|
+
console.log("\n \u2713 Removed stored credentials from ~/.vibebusiness/credentials.json\n");
|
|
399
|
+
console.log(" Note: Environment variables (if set) are unaffected.");
|
|
400
|
+
console.log(" Run `vibebusiness provider` to verify status.\n");
|
|
401
|
+
}
|
|
402
|
+
async function main() {
|
|
403
|
+
const args = process.argv.slice(2);
|
|
404
|
+
const subcommand = args[0];
|
|
405
|
+
switch (subcommand) {
|
|
406
|
+
case "setup":
|
|
407
|
+
await cmdSetup();
|
|
408
|
+
break;
|
|
409
|
+
case "validate":
|
|
410
|
+
await cmdValidate();
|
|
411
|
+
break;
|
|
412
|
+
case "clear":
|
|
413
|
+
cmdClear();
|
|
414
|
+
break;
|
|
415
|
+
case void 0:
|
|
416
|
+
case "status":
|
|
417
|
+
await cmdStatus();
|
|
418
|
+
break;
|
|
419
|
+
default:
|
|
420
|
+
console.error(`
|
|
421
|
+
Unknown provider subcommand: ${subcommand}`);
|
|
422
|
+
console.log(" Usage: vibebusiness provider [setup|validate|clear]\n");
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
main().finally(() => {
|
|
427
|
+
rl.close();
|
|
428
|
+
});
|