vibeostheog 0.19.4 → 0.19.6
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/CHANGELOG.md +5 -0
- package/README.md +7 -11
- package/bin/setup.js +48 -0
- package/package.json +9 -5
- package/src/flow-enforcer.js +1 -0
- package/src/flow-rules.json +39 -0
- package/src/lib/api-client.js +415 -0
- package/src/lib/classifiers.js +173 -0
- package/src/lib/constants.js +19 -0
- package/src/lib/credit-api.js +145 -0
- package/src/lib/hooks/chat-transform.js +664 -0
- package/src/lib/hooks/footer.js +307 -0
- package/src/lib/hooks/session-compact.js +64 -0
- package/src/lib/hooks/shell-env.js +18 -0
- package/src/lib/hooks/tests/footer.test.js +185 -0
- package/src/lib/hooks/tool-execute.js +912 -0
- package/src/lib/index-helpers.js +306 -0
- package/src/lib/mode-policy.js +190 -0
- package/src/lib/pattern-helpers.js +126 -0
- package/src/lib/pricing.js +552 -0
- package/src/lib/reporting.js +228 -0
- package/src/lib/research-audit.js +134 -0
- package/src/lib/runtime-state.js +42 -0
- package/src/lib/runtime-surface.js +183 -0
- package/src/lib/selection-manager.js +150 -0
- package/src/lib/state.js +1802 -0
- package/src/lib/tdd-enforcer.js +312 -0
- package/src/lib/templates.js +92 -0
- package/src/lib/test-skeletons.js +470 -0
- package/src/lib/tests/api-client.test.js +220 -0
- package/src/lib/tests/pricing.test.js +745 -0
- package/src/lib/tests/state.test.js +686 -0
- package/src/lib/text-compress.js +94 -0
- package/src/lib/trinity-rebuild.js +254 -0
- package/src/lib/trinity-tool.js +997 -0
- package/src/lib/turn-classify.js +518 -0
- package/src/tests/index.test.js +91 -0
- package/src/utils/cost-formatter.js +28 -0
- package/src/utils/math.js +10 -0
- package/src/utils/tdd-helpers.js +267 -0
- package/src/utils/timer.js +72 -0
- package/src/vibeOS-lib/blackbox/advice-layer.js +339 -0
- package/src/vibeOS-lib/blackbox/crew-constants.js +68 -0
- package/src/vibeOS-lib/blackbox/exposure-model.js +42 -0
- package/src/vibeOS-lib/blackbox/index.js +10 -0
- package/src/vibeOS-lib/blackbox/local-stub.js +142 -0
- package/src/vibeOS-lib/blackbox/meta-controller.js +280 -0
- package/src/vibeOS-lib/blackbox/resolution-tracker.js +494 -0
- package/src/vibeOS-lib/blackbox/taxonomy.js +106 -0
- package/src/vibeOS-lib/flow-enforcer.js +378 -0
- package/src/vibeOS-lib/flow-rules.json +39 -0
- package/src/vibeOS-lib/ml-router.js +276 -0
- package/src/vibeOS-lib/session-metrics.js +138 -0
- package/src/vibeOS-lib/smart-cache.js +211 -0
- package/src/vibeOS-lib/tests/experiment-data-export.json +12743 -0
- package/src/vibeOS-lib/tests/experiment-scenarios-progressive.json +115 -0
- package/src/vibeOS-lib/tests/experiment-scenarios-token-latency.json +57 -0
- package/src/vibeOS-lib/tests/experiment-scenarios.json +94 -0
- package/src/vibeOS-lib/tests/reports/mode-benchmark-final-2026-05-23T07-01-58Z.json +129 -0
- package/src/vibeOS-lib/tests/reports/mode-calibration-2026-05-23T07-07-08Z.json +440 -0
- package/src/vibeOS-lib/tests/reports/mode-signal-analysis-2026-05-23T06-59-30Z.json +89 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -18,29 +18,25 @@ It also adds guardrails: delegation enforcement, flow and TDD controls, pattern
|
|
|
18
18
|
|
|
19
19
|
### OpenCode plugin
|
|
20
20
|
|
|
21
|
-
1.
|
|
21
|
+
1. Register the plugin with the bundled setup command:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
24
|
+
npx vibeostheog setup --project
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
The package deploys `src/index.js` to `~/.config/opencode/plugins/vibeOS.js`
|
|
29
|
-
and can auto-register `./plugins/vibeOS.js` in `~/.config/opencode/opencode.json`.
|
|
27
|
+
Use `npx vibeostheog setup` for a global OpenCode install under `~/.config/opencode/`.
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
2. The setup command writes the package name into your OpenCode config. OpenCode installs npm plugins automatically at startup:
|
|
32
30
|
|
|
33
31
|
```json
|
|
34
32
|
{
|
|
35
33
|
"plugin": [
|
|
36
|
-
"
|
|
34
|
+
"vibeostheog"
|
|
37
35
|
]
|
|
38
36
|
}
|
|
39
37
|
```
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
If you keep a local checkout of the plugin, point OpenCode at the built file instead:
|
|
39
|
+
3. If you keep a local checkout of the plugin, point OpenCode at the built file instead:
|
|
44
40
|
|
|
45
41
|
```json
|
|
46
42
|
{
|
|
@@ -64,7 +60,7 @@ npm test
|
|
|
64
60
|
npm run release:patch
|
|
65
61
|
```
|
|
66
62
|
|
|
67
|
-
`npm run build` compiles `src/index.ts` to `src/index.js`
|
|
63
|
+
`npm run build` compiles `src/index.ts` to `src/index.js` for the local checkout. `npm run typecheck` validates the TypeScript sources without emitting files.
|
|
68
64
|
|
|
69
65
|
## Core Controls
|
|
70
66
|
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
|
|
7
|
+
const pkgName = "vibeostheog";
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0] ?? "setup";
|
|
10
|
+
const isProject = args.includes("--project");
|
|
11
|
+
|
|
12
|
+
if (command !== "setup") {
|
|
13
|
+
console.error(`Usage: ${pkgName} setup [--project]`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const configPath = isProject
|
|
18
|
+
? resolve(process.cwd(), "opencode.json")
|
|
19
|
+
: resolve(homedir(), ".config", "opencode", "opencode.json");
|
|
20
|
+
|
|
21
|
+
let config = {};
|
|
22
|
+
try {
|
|
23
|
+
config = JSON.parse(await readFile(configPath, "utf8"));
|
|
24
|
+
} catch {
|
|
25
|
+
config = {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
29
|
+
config = {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!config.$schema) {
|
|
33
|
+
config.$schema = "https://opencode.ai/config.json";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!Array.isArray(config.plugin)) {
|
|
37
|
+
config.plugin = [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!config.plugin.includes(pkgName)) {
|
|
41
|
+
config.plugin.push(pkgName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
45
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
46
|
+
|
|
47
|
+
console.log(`${pkgName} registered in ${configPath}`);
|
|
48
|
+
console.log("Restart OpenCode to activate the plugin.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"release": "node scripts/release.mjs",
|
|
@@ -21,10 +21,13 @@
|
|
|
21
21
|
"codex:hook:summary": "bash plugins/vibetheog-codex/hooks/post-command-summary.sh",
|
|
22
22
|
"precommit": "node scripts/pre-commit.mjs",
|
|
23
23
|
"audit-state": "node scripts/audit-state.mjs",
|
|
24
|
-
"migrate-ledger": "node scripts/migrate-ledger.mjs"
|
|
25
|
-
"postinstall": "node scripts/deploy.mjs"
|
|
24
|
+
"migrate-ledger": "node scripts/migrate-ledger.mjs"
|
|
26
25
|
},
|
|
27
26
|
"type": "module",
|
|
27
|
+
"main": "./src/index.js",
|
|
28
|
+
"bin": {
|
|
29
|
+
"vibeostheog": "./bin/setup.js"
|
|
30
|
+
},
|
|
28
31
|
"exports": {
|
|
29
32
|
".": "./src/index.js",
|
|
30
33
|
"./server": "./src/lib/vibeos-mcp-server.js",
|
|
@@ -47,8 +50,9 @@
|
|
|
47
50
|
"@opencode-ai/plugin": "^1.15.7"
|
|
48
51
|
},
|
|
49
52
|
"files": [
|
|
50
|
-
"src
|
|
51
|
-
"src
|
|
53
|
+
"src/**/*.js",
|
|
54
|
+
"src/**/*.json",
|
|
55
|
+
"bin/setup.js",
|
|
52
56
|
".opencode/plugins/vibeOS-tui.tsx",
|
|
53
57
|
"scripts/deploy.mjs",
|
|
54
58
|
"model-tiers.sample.json",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkFlowRules, getFlowWarns, getSessionFlowCounts, recordFlowTodo, resetForTest, } from "./vibeOS-lib/flow-enforcer.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"id": "new-md-file",
|
|
5
|
+
"severity": "warn",
|
|
6
|
+
"trigger": "Write",
|
|
7
|
+
"pattern": "\\.md$",
|
|
8
|
+
"description": "New markdown file created without explicit user request"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "new-file-outside-src",
|
|
12
|
+
"severity": "hint",
|
|
13
|
+
"trigger": "Write",
|
|
14
|
+
"pattern": "^(?!src/|\\.)",
|
|
15
|
+
"description": "New file created outside src/ or hidden file — verify intent"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "compat-shim",
|
|
19
|
+
"severity": "warn",
|
|
20
|
+
"trigger": "Edit",
|
|
21
|
+
"pattern": "_old|_legacy|# removed",
|
|
22
|
+
"description": "Backwards-compat shim pattern detected"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "todo-comment",
|
|
26
|
+
"severity": "hint",
|
|
27
|
+
"trigger": "Edit",
|
|
28
|
+
"pattern": "TODO|FIXME|HACK",
|
|
29
|
+
"description": "TODO/FIXME left in output"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "detect-secrets",
|
|
33
|
+
"severity": "flag",
|
|
34
|
+
"trigger": "Edit",
|
|
35
|
+
"pattern": "(?<![a-zA-Z0-9])(sk-[a-zA-Z0-9_-]{10,}|gh[pousr]_[a-zA-Z0-9]{15,}|github_pat_[a-zA-Z0-9_]{15,}|xox[bpras]-[a-zA-Z0-9-]{10,}|-----BEGIN (?:RSA|EC|OPENSSH|ENCRYPTED) PRIVATE KEY-----)",
|
|
36
|
+
"description": "Potential secret/API key detected in content"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { markApiConnected, markApiDisconnected, resetApiConnection } from "./runtime-state.js";
|
|
7
|
+
const DEFAULT_API_URL = "https://api.vibetheog.com";
|
|
8
|
+
const REQUEST_TIMEOUT = 10000;
|
|
9
|
+
const MAX_RETRIES = 3;
|
|
10
|
+
const BASE_RETRY_DELAY = 1000;
|
|
11
|
+
export class VibeOSAuthError extends Error {
|
|
12
|
+
statusCode;
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, statusCode, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "VibeOSAuthError";
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class VibeOSTimeoutError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "VibeOSTimeoutError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class VibeOSNetworkError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "VibeOSNetworkError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class VibeOSApiClient {
|
|
34
|
+
baseUrl;
|
|
35
|
+
apiToken;
|
|
36
|
+
masterKey;
|
|
37
|
+
timeout;
|
|
38
|
+
fallbackMode;
|
|
39
|
+
fallbackStubs;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.baseUrl = options.baseUrl || process.env.VIBEOS_API_URL || DEFAULT_API_URL;
|
|
42
|
+
this.apiToken = options.apiToken || process.env.VIBEOS_API_TOKEN || null;
|
|
43
|
+
this.masterKey = options.masterKey || process.env.VIBEOS_API_MASTER_KEY || null;
|
|
44
|
+
this.timeout = options.timeout || REQUEST_TIMEOUT;
|
|
45
|
+
this.fallbackMode = false;
|
|
46
|
+
this.fallbackStubs = options.fallbackStubs || null;
|
|
47
|
+
}
|
|
48
|
+
async request(path, body = null, isAdmin = false) {
|
|
49
|
+
if (!this.apiToken && !isAdmin) {
|
|
50
|
+
throw new Error("VIBEOS_API_TOKEN is not set");
|
|
51
|
+
}
|
|
52
|
+
const url = this.baseUrl + path;
|
|
53
|
+
const headers = {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
Authorization: "Bearer " + (isAdmin ? this.masterKey : this.apiToken),
|
|
56
|
+
};
|
|
57
|
+
let lastError = null;
|
|
58
|
+
let attempt = 0;
|
|
59
|
+
while (attempt <= MAX_RETRIES) {
|
|
60
|
+
if (attempt > 0) {
|
|
61
|
+
const delay = BASE_RETRY_DELAY * Math.pow(2, attempt - 1);
|
|
62
|
+
await new Promise(r => setTimeout(r, delay));
|
|
63
|
+
}
|
|
64
|
+
attempt++;
|
|
65
|
+
try {
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
68
|
+
const res = await fetch(url, {
|
|
69
|
+
method: body ? "POST" : "GET",
|
|
70
|
+
headers,
|
|
71
|
+
body: body ? JSON.stringify(body) : null,
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
if (res.status === 401 || res.status === 403) {
|
|
76
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
77
|
+
this.fallbackMode = true;
|
|
78
|
+
throw new VibeOSAuthError(errorBody.message || "Authentication failed", res.status, errorBody.code);
|
|
79
|
+
}
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
82
|
+
if (res.status >= 500 && attempt <= MAX_RETRIES) {
|
|
83
|
+
lastError = new Error("API error " + res.status + ": " + (errorBody.error || res.statusText));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
throw new Error("API error " + res.status + ": " + (errorBody.error || res.statusText));
|
|
87
|
+
}
|
|
88
|
+
this.fallbackMode = false;
|
|
89
|
+
return res.json();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof VibeOSAuthError)
|
|
93
|
+
throw err;
|
|
94
|
+
const error = err;
|
|
95
|
+
if (error.name === "AbortError") {
|
|
96
|
+
if (attempt <= MAX_RETRIES) {
|
|
97
|
+
lastError = new VibeOSTimeoutError("Request to " + url + " timed out after " + this.timeout + "ms");
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
this.fallbackMode = true;
|
|
101
|
+
throw new VibeOSTimeoutError("Request to " + url + " timed out after " + this.timeout + "ms");
|
|
102
|
+
}
|
|
103
|
+
lastError = err;
|
|
104
|
+
if (attempt <= MAX_RETRIES && error.message && (error.message.includes("fetch") || error.message.includes("network") || error.message.includes("ECONNREFUSED"))) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.fallbackMode = true;
|
|
110
|
+
throw new VibeOSNetworkError("Failed to reach API after " + MAX_RETRIES + " retries: " + (lastError ? lastError.message : "unknown error"));
|
|
111
|
+
}
|
|
112
|
+
async delegateCheck(tool, tier, model, prompt, dynamicCache = {}) {
|
|
113
|
+
return this.request("/api/v1/delegate/check", { tool, tier, model, prompt, dynamic_cache: dynamicCache });
|
|
114
|
+
}
|
|
115
|
+
async delegateSoftQuota(tool, currentCount, limit = 5) {
|
|
116
|
+
return this.request("/api/v1/delegate/soft-quota", { tool, current_count: currentCount, limit });
|
|
117
|
+
}
|
|
118
|
+
async delegateCost(model, dynamicCache = {}) {
|
|
119
|
+
return this.request("/api/v1/delegation/cost", { model, dynamic_cache: dynamicCache });
|
|
120
|
+
}
|
|
121
|
+
async routeModel(prompt, currentTier, trinityCheap, trinityMedium, learnedExploratory = [], stressScore = 0) {
|
|
122
|
+
return this.request("/api/v1/route/model", {
|
|
123
|
+
prompt,
|
|
124
|
+
current_tier: currentTier,
|
|
125
|
+
trinity_cheap: trinityCheap,
|
|
126
|
+
trinity_medium: trinityMedium,
|
|
127
|
+
learned_exploratory: learnedExploratory,
|
|
128
|
+
stress_score: stressScore,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async classifyTier(model, customRegex = null) {
|
|
132
|
+
return this.request("/api/v1/tier/classify", { model, custom_regex: customRegex });
|
|
133
|
+
}
|
|
134
|
+
async isExploratory(prompt, learnedExploratory = []) {
|
|
135
|
+
return this.request("/api/v1/tier/exploratory", { prompt, learned_exploratory: learnedExploratory });
|
|
136
|
+
}
|
|
137
|
+
async scoreStress(text) {
|
|
138
|
+
return this.request("/api/v1/stress/score", { text });
|
|
139
|
+
}
|
|
140
|
+
async stressLevel(score) {
|
|
141
|
+
return this.request("/api/v1/stress/level", { score });
|
|
142
|
+
}
|
|
143
|
+
async blackboxAnalyze(sessionId, entry) {
|
|
144
|
+
return this.request("/api/v1/blackbox/analyze", {
|
|
145
|
+
session_id: sessionId,
|
|
146
|
+
project_id: entry.project_id || null,
|
|
147
|
+
user_text: entry.userText || "",
|
|
148
|
+
features: entry.features || {},
|
|
149
|
+
action: entry.action || "explore",
|
|
150
|
+
entropy: entry.entropy ?? 1.0,
|
|
151
|
+
uncertainty: entry.uncertainty ?? 50,
|
|
152
|
+
embedding: entry.embedding || null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async blackboxState(sessionId) {
|
|
156
|
+
return this.request("/api/v1/blackbox/state", { session_id: sessionId });
|
|
157
|
+
}
|
|
158
|
+
async blackboxReset(sessionId) {
|
|
159
|
+
return this.request("/api/v1/blackbox/reset", { session_id: sessionId });
|
|
160
|
+
}
|
|
161
|
+
async blackboxOutcome(sessionId, outcome) {
|
|
162
|
+
return this.request("/api/v1/blackbox/outcome", { session_id: sessionId, outcome });
|
|
163
|
+
}
|
|
164
|
+
async blackboxCalibrate(projectId) {
|
|
165
|
+
return this.request("/api/v1/blackbox/calibrate", { project_id: projectId || "global" });
|
|
166
|
+
}
|
|
167
|
+
async blackboxCalibration(projectId) {
|
|
168
|
+
return this.request("/api/v1/blackbox/calibration?project_id=" + (projectId || "global"), null);
|
|
169
|
+
}
|
|
170
|
+
async blackboxControlVector(state, action, optimizationMode) {
|
|
171
|
+
return this.request("/api/v1/blackbox/control-vector", { ...state, action, optimization_mode: optimizationMode });
|
|
172
|
+
}
|
|
173
|
+
async blackboxSelectMode(subRegime, stressMultiplier) {
|
|
174
|
+
return this.request("/api/v1/blackbox/select-mode", { sub_regime: subRegime, stress_multiplier: stressMultiplier });
|
|
175
|
+
}
|
|
176
|
+
async tddExports(sourceContent, ext) {
|
|
177
|
+
return this.request("/api/v1/tdd/exports", { source_content: sourceContent, ext });
|
|
178
|
+
}
|
|
179
|
+
async tddParams(sourceContent, funcName) {
|
|
180
|
+
return this.request("/api/v1/tdd/params", { source_content: sourceContent, func_name: funcName });
|
|
181
|
+
}
|
|
182
|
+
async tddInferType(paramName, defaultValue) {
|
|
183
|
+
return this.request("/api/v1/tdd/infer-type", { param_name: paramName, default_value: defaultValue });
|
|
184
|
+
}
|
|
185
|
+
async tddSkeleton(language, fileName, exports, options = {}) {
|
|
186
|
+
return this.request("/api/v1/tdd/skeleton", { language, file_name: fileName, exports, options });
|
|
187
|
+
}
|
|
188
|
+
async patternsObserve(sessionId, toolName, input, output, directory) {
|
|
189
|
+
return this.request("/api/v1/patterns/observe", {
|
|
190
|
+
session_id: sessionId,
|
|
191
|
+
tool_name: toolName,
|
|
192
|
+
input,
|
|
193
|
+
output,
|
|
194
|
+
directory,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async patternsRecord(sessionId, kind, key, summary, meta = {}) {
|
|
198
|
+
return this.request("/api/v1/patterns/record", {
|
|
199
|
+
session_id: sessionId,
|
|
200
|
+
kind,
|
|
201
|
+
key,
|
|
202
|
+
summary,
|
|
203
|
+
meta,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async patternsQuery(sessionId, kind = null) {
|
|
207
|
+
return this.request("/api/v1/patterns/query?kind=" + (kind || ""), null);
|
|
208
|
+
}
|
|
209
|
+
async patternsExploratoryWords(sessionId) {
|
|
210
|
+
return this.request("/api/v1/patterns/exploratory-words", null);
|
|
211
|
+
}
|
|
212
|
+
async patternsClear(sessionId) {
|
|
213
|
+
return this.request("/api/v1/patterns/clear", { session_id: sessionId });
|
|
214
|
+
}
|
|
215
|
+
async pricingFetch(openrouterKey, force = false) {
|
|
216
|
+
return this.request("/api/v1/pricing/fetch", { openrouter_key: openrouterKey, force });
|
|
217
|
+
}
|
|
218
|
+
async pricingLookup(model) {
|
|
219
|
+
return this.request("/api/v1/pricing/lookup", { model });
|
|
220
|
+
}
|
|
221
|
+
async pricingStatic() {
|
|
222
|
+
return this.request("/api/v1/pricing/static", null);
|
|
223
|
+
}
|
|
224
|
+
async compressContext(text, threshold = 2000) {
|
|
225
|
+
return this.request("/api/v1/compress/context", { text, threshold });
|
|
226
|
+
}
|
|
227
|
+
async adminCreateSeat(name, email) {
|
|
228
|
+
return this.request("/admin/seats", { name, email }, true);
|
|
229
|
+
}
|
|
230
|
+
async adminCreateSeatWithToken(name, email, tokenLabel = null) {
|
|
231
|
+
return this.request("/admin/seats", { name, email, with_token: tokenLabel || true }, true);
|
|
232
|
+
}
|
|
233
|
+
async adminListSeats() {
|
|
234
|
+
return this.request("/admin/seats", null, true);
|
|
235
|
+
}
|
|
236
|
+
async adminUpdateSeat(seatId, status) {
|
|
237
|
+
return this.request("/admin/seats/" + seatId, { status }, true);
|
|
238
|
+
}
|
|
239
|
+
async adminCreateToken(seatId, label, expiresAt) {
|
|
240
|
+
return this.request("/admin/tokens", { seat_id: seatId, label, expires_at: expiresAt }, true);
|
|
241
|
+
}
|
|
242
|
+
async adminListTokens() {
|
|
243
|
+
return this.request("/admin/tokens", null, true);
|
|
244
|
+
}
|
|
245
|
+
async adminUpdateToken(tokenId, status) {
|
|
246
|
+
return this.request("/admin/tokens/" + tokenId, { status }, true);
|
|
247
|
+
}
|
|
248
|
+
async adminDeleteToken(tokenId) {
|
|
249
|
+
return this.request("/admin/tokens/" + tokenId, null, true);
|
|
250
|
+
}
|
|
251
|
+
async adminUsage(days = 30) {
|
|
252
|
+
return this.request("/admin/usage?days=" + days, null, true);
|
|
253
|
+
}
|
|
254
|
+
async health() {
|
|
255
|
+
return this.request("/health", null, false);
|
|
256
|
+
}
|
|
257
|
+
isFallback() {
|
|
258
|
+
return this.fallbackMode;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ── Remote API client (Phase 2) ─────────────────────────────────────
|
|
262
|
+
export const VIBEOS_API_URL = process.env.VIBEOS_API_URL || "https://api.vibetheog.com";
|
|
263
|
+
const _apiDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
264
|
+
const _envPaths = [homedir() + "/.claude", _apiDir, process.cwd(), homedir()];
|
|
265
|
+
function readTokenFromDisk() {
|
|
266
|
+
for (const dir of _envPaths) {
|
|
267
|
+
try {
|
|
268
|
+
const env = readFileSync(dir + "/.env.production", "utf8");
|
|
269
|
+
const m = env.match(/^VIBEOS_API_TOKEN=(.+)$/m);
|
|
270
|
+
if (m)
|
|
271
|
+
return m[1].trim();
|
|
272
|
+
}
|
|
273
|
+
catch { }
|
|
274
|
+
}
|
|
275
|
+
return "";
|
|
276
|
+
}
|
|
277
|
+
export let VIBEOS_API_TOKEN = readTokenFromDisk() || process.env.VIBEOS_API_TOKEN || "";
|
|
278
|
+
export let VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
279
|
+
export function setApiToken(newToken) {
|
|
280
|
+
try {
|
|
281
|
+
VIBEOS_API_TOKEN = String(newToken || "").trim();
|
|
282
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
283
|
+
const primaryPath = _envPaths[0] + "/.env.production";
|
|
284
|
+
try {
|
|
285
|
+
if (existsSync(primaryPath)) {
|
|
286
|
+
let envContent = readFileSync(primaryPath, "utf8");
|
|
287
|
+
if (/^VIBEOS_API_TOKEN=/m.test(envContent)) {
|
|
288
|
+
envContent = envContent.replace(/^VIBEOS_API_TOKEN=.+$/m, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}`);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
envContent = envContent.trimEnd() + `\nVIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`;
|
|
292
|
+
}
|
|
293
|
+
writeFileSync(primaryPath, envContent, "utf8");
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const parentDir = _envPaths[0];
|
|
297
|
+
if (!existsSync(parentDir))
|
|
298
|
+
mkdirSync(parentDir, { recursive: true });
|
|
299
|
+
writeFileSync(primaryPath, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`, "utf8");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (diskErr) {
|
|
303
|
+
console.error("[vibeOS] Failed to persist API token to disk:", diskErr.message);
|
|
304
|
+
}
|
|
305
|
+
console.error("[vibeOS] API token updated via setApiToken");
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
console.error("[vibeOS] Failed to update API token:", e.message);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let _apiClient = null;
|
|
312
|
+
let _apiFallbackMode = false;
|
|
313
|
+
let _apiFallbackSince = null;
|
|
314
|
+
function syncApiTokenFromDisk() {
|
|
315
|
+
const diskToken = readTokenFromDisk() || "";
|
|
316
|
+
const envToken = process.env.VIBEOS_API_TOKEN || "";
|
|
317
|
+
if (diskToken && diskToken !== VIBEOS_API_TOKEN) {
|
|
318
|
+
VIBEOS_API_TOKEN = diskToken;
|
|
319
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
320
|
+
_apiClient = null;
|
|
321
|
+
_apiFallbackMode = false;
|
|
322
|
+
_apiFallbackSince = null;
|
|
323
|
+
resetApiConnection();
|
|
324
|
+
console.error("[vibeOS] API token synced from disk (disk is newer)");
|
|
325
|
+
}
|
|
326
|
+
else if (!diskToken && VIBEOS_API_TOKEN) {
|
|
327
|
+
const primaryPath = _envPaths[0] + "/.env.production";
|
|
328
|
+
try {
|
|
329
|
+
if (existsSync(primaryPath)) {
|
|
330
|
+
let envContent = readFileSync(primaryPath, "utf8");
|
|
331
|
+
if (/^VIBEOS_API_TOKEN=/m.test(envContent)) {
|
|
332
|
+
envContent = envContent.replace(/^VIBEOS_API_TOKEN=.+$/m, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
envContent = envContent.trimEnd() + `\nVIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`;
|
|
336
|
+
}
|
|
337
|
+
writeFileSync(primaryPath, envContent, "utf8");
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const parentDir = _envPaths[0];
|
|
341
|
+
if (!existsSync(parentDir))
|
|
342
|
+
mkdirSync(parentDir, { recursive: true });
|
|
343
|
+
writeFileSync(primaryPath, `VIBEOS_API_TOKEN=${VIBEOS_API_TOKEN}\n`, "utf8");
|
|
344
|
+
}
|
|
345
|
+
console.error("[vibeOS] API token persisted to disk from memory (disk was empty)");
|
|
346
|
+
}
|
|
347
|
+
catch (diskErr) {
|
|
348
|
+
console.error("[vibeOS] Failed to persist API token to disk from sync:", diskErr.message);
|
|
349
|
+
}
|
|
350
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
351
|
+
}
|
|
352
|
+
else if (envToken && !diskToken && !VIBEOS_API_TOKEN) {
|
|
353
|
+
VIBEOS_API_TOKEN = envToken;
|
|
354
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
355
|
+
console.error("[vibeOS] API token loaded from VIBEOS_API_TOKEN env var");
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
export function getApiClient() {
|
|
362
|
+
syncApiTokenFromDisk();
|
|
363
|
+
if (!_apiClient && VIBEOS_API_ENABLED) {
|
|
364
|
+
_apiClient = new VibeOSApiClient({
|
|
365
|
+
baseUrl: VIBEOS_API_URL,
|
|
366
|
+
apiToken: VIBEOS_API_TOKEN,
|
|
367
|
+
timeout: 5000,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return _apiClient;
|
|
371
|
+
}
|
|
372
|
+
export function isApiFallback() {
|
|
373
|
+
return _apiFallbackMode || !VIBEOS_API_ENABLED;
|
|
374
|
+
}
|
|
375
|
+
export function isApiConnected() {
|
|
376
|
+
return VIBEOS_API_ENABLED && !_apiFallbackMode;
|
|
377
|
+
}
|
|
378
|
+
export async function remoteCall(method, args, fallbackFn) {
|
|
379
|
+
syncApiTokenFromDisk();
|
|
380
|
+
if (!VIBEOS_API_ENABLED || _apiFallbackMode) {
|
|
381
|
+
if (fallbackFn)
|
|
382
|
+
return fallbackFn();
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const client = getApiClient();
|
|
387
|
+
if (!client) {
|
|
388
|
+
if (fallbackFn)
|
|
389
|
+
return fallbackFn();
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const result = await client[method](...args);
|
|
393
|
+
_apiFallbackMode = false;
|
|
394
|
+
_apiFallbackSince = null;
|
|
395
|
+
markApiConnected();
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
if (!_apiFallbackMode) {
|
|
400
|
+
_apiFallbackMode = true;
|
|
401
|
+
_apiFallbackSince = new Date().toISOString();
|
|
402
|
+
console.error(`[vibeOS] API fallback activated: ${err.message}`);
|
|
403
|
+
}
|
|
404
|
+
markApiDisconnected();
|
|
405
|
+
if (fallbackFn) {
|
|
406
|
+
try {
|
|
407
|
+
return fallbackFn();
|
|
408
|
+
}
|
|
409
|
+
catch (fe) {
|
|
410
|
+
console.error(`[vibeOS] fallback also failed: ${fe.message}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|