fbt-os 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fuckbigtech.ai (operated by Kinetic Labs Inc.)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # fbt-os
2
+
3
+ **v0 setup CLI for the FuckBigTech local-first agent OS.** Zero deps. Zero network calls. Nothing leaves your machine.
4
+
5
+ > ⚠️ **v0 scope.** This CLI scaffolds a private local memory vault, detects which agent harnesses + runtimes + provider keys are present on your system, and runs deterministic setup checks. The full agent OS — autonomous meta-routing, prompt decomposition, cloud-burst, vault-aware harness plugins — is being built. Star the project / subscribe at [fuckbigtech.ai](https://fuckbigtech.ai) for the v1 drop.
6
+
7
+ > The npm package is `fbt-os` (npm's word filter blocks the full brand name). The installed binary keeps the brand: type `fuckbigtech` after install.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g fbt-os
13
+ ```
14
+
15
+ Requires Node 20+. Works on macOS, Linux, WSL.
16
+
17
+ ## Verify before running
18
+
19
+ ```bash
20
+ fuckbigtech dry-run
21
+ ```
22
+
23
+ Prints everything the CLI **would** do without doing it. No writes, no env reads, no shells.
24
+
25
+ ## Initialize a local memory vault
26
+
27
+ ```bash
28
+ fuckbigtech init --with-memory
29
+ ```
30
+
31
+ Scaffolds a private Markdown memory tree under `~/.fuckbigtech/memory/` (mode `0700`, files `0600`). The `--with-memory` flag is an explicit opt-in.
32
+
33
+ ## Run setup checks
34
+
35
+ ```bash
36
+ fuckbigtech doctor # detect harnesses, runtimes, provider key presence (boolean only — values never read)
37
+ fuckbigtech memory-test quick # validate the local vault scaffold
38
+ fuckbigtech route-test demo # exercise the v0 route classifier
39
+ ```
40
+
41
+ ## Connect an existing vault
42
+
43
+ ```bash
44
+ fuckbigtech connect /path/to/your/vault
45
+ ```
46
+
47
+ Validates the path looks like an FBT-shaped memory tree before writing the config pointer.
48
+
49
+ ## What this CLI promises
50
+
51
+ - **No network calls.** `grep -rE 'fetch|http|https|net|dgram' bin/` returns zero hits.
52
+ - **No paid model calls.** All routing decisions in v0 are deterministic and offline.
53
+ - **No key uploads.** Provider env keys (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) are tested for *presence only* with a boolean — the values are never read, logged, or written.
54
+ - **No shell mutation.** Doesn't touch your `.bashrc`, `.zshrc`, `PATH`, or any system path. All writes are scoped to `~/.fuckbigtech/` (override via `FBT_CONFIG_DIR` and `FBT_VAULT_PATH`).
55
+ - **Permissive defaults.** Vault dir is `0700`, files `0600` — only your user can read.
56
+
57
+ ## What v0 doesn't do yet
58
+
59
+ - Autonomous prompt routing across harnesses (Claude / Codex / OpenCode / Hermes).
60
+ - Multi-part prompt decomposition.
61
+ - Cloud-burst with git-worktree-based vault sync.
62
+ - The `oc` meta-router with the Llama 3.3 70B routing model.
63
+
64
+ These ship in v1. For the canonical product spec see [fuckbigtech.ai](https://fuckbigtech.ai).
65
+
66
+ ## License
67
+
68
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import readline from 'node:readline/promises';
7
+ import { stdin as input, stdout as output } from 'node:process';
8
+ import { spawnSync } from 'node:child_process';
9
+
10
+ const cwd = process.cwd();
11
+ const home = os.homedir();
12
+ const configDir = process.env.FBT_CONFIG_DIR
13
+ ? path.resolve(process.env.FBT_CONFIG_DIR)
14
+ : path.join(home, '.fuckbigtech');
15
+ const configPath = path.join(configDir, 'config.json');
16
+ const defaultVault = process.env.FBT_VAULT_PATH
17
+ ? path.resolve(process.env.FBT_VAULT_PATH)
18
+ : path.join(configDir, 'memory');
19
+ const args = process.argv.slice(2);
20
+
21
+ const colors = {
22
+ red: '\x1b[31m',
23
+ yellow: '\x1b[33m',
24
+ green: '\x1b[32m',
25
+ cyan: '\x1b[36m',
26
+ gray: '\x1b[90m',
27
+ bold: '\x1b[1m',
28
+ reset: '\x1b[0m',
29
+ };
30
+
31
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
32
+ function paint(color, text) {
33
+ if (!useColor) return text;
34
+ return `${colors[color] || ''}${text}${colors.reset}`;
35
+ }
36
+
37
+ function line(text = '') {
38
+ console.log(text);
39
+ }
40
+
41
+ function header() {
42
+ line(paint('red', 'FUCKBIGTECH.AI'));
43
+ line(paint('yellow', 'Public repo. Private memory. No keys uploaded.'));
44
+ line();
45
+ }
46
+
47
+ function commandExists(command) {
48
+ if (!/^[a-zA-Z0-9._-]+$/.test(command)) return false;
49
+ return spawnSync('sh', ['-lc', 'command -v "$1"', 'sh', command], { stdio: 'ignore' }).status === 0;
50
+ }
51
+
52
+ function readConfig() {
53
+ try {
54
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ function writeConfig(config) {
61
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
62
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
63
+ }
64
+
65
+ function detect() {
66
+ const env = process.env;
67
+ return {
68
+ harnesses: [
69
+ ['Codex', commandExists('codex')],
70
+ ['Claude Code', commandExists('claude')],
71
+ ['OpenCode', commandExists('opencode') || commandExists('oc')],
72
+ ['Hermes', commandExists('hermes')],
73
+ ['OpenClaw', commandExists('openclaw')],
74
+ ],
75
+ runtimes: [
76
+ ['Ollama', commandExists('ollama')],
77
+ ['llama.cpp', commandExists('llama-cli') || commandExists('llama-server')],
78
+ ['vLLM', commandExists('vllm')],
79
+ ],
80
+ providers: [
81
+ ['OpenRouter', Boolean(env.OPENROUTER_API_KEY)],
82
+ ['OpenAI API', Boolean(env.OPENAI_API_KEY)],
83
+ ['Anthropic API', Boolean(env.ANTHROPIC_API_KEY)],
84
+ ['NVIDIA NIM', Boolean(env.NVIDIA_API_KEY)],
85
+ ['Groq', Boolean(env.GROQ_API_KEY)],
86
+ ['Mistral', Boolean(env.MISTRAL_API_KEY)],
87
+ ['Cerebras', Boolean(env.CEREBRAS_API_KEY)],
88
+ ],
89
+ stores: [
90
+ ['Obsidian app', commandExists('obsidian')],
91
+ ['qmd', commandExists('qmd')],
92
+ ['1Password CLI', commandExists('op')],
93
+ ['Doppler', commandExists('doppler')],
94
+ ],
95
+ };
96
+ }
97
+
98
+ function printCheck(name, ok, detail = '') {
99
+ const mark = ok ? paint('green', '✓') : paint('red', '✕');
100
+ line(`${mark} ${name.padEnd(18)} ${detail}`);
101
+ }
102
+
103
+ function printDetection() {
104
+ const detected = detect();
105
+ line(paint('yellow', 'Detected agent harnesses'));
106
+ detected.harnesses.forEach(([name, ok]) => printCheck(name, ok, ok ? 'available' : 'not found'));
107
+ line();
108
+ line(paint('yellow', 'Detected local runtimes'));
109
+ detected.runtimes.forEach(([name, ok]) => printCheck(name, ok, ok ? 'available' : 'not found'));
110
+ line();
111
+ line(paint('yellow', 'Detected provider keys'));
112
+ detected.providers.forEach(([name, ok]) => printCheck(name, ok, ok ? 'env key present' : 'missing or skipped'));
113
+ line();
114
+ line(paint('yellow', 'Detected local tooling'));
115
+ detected.stores.forEach(([name, ok]) => printCheck(name, ok, ok ? 'available' : 'not found'));
116
+ }
117
+
118
+ function createVault(vaultPath = defaultVault) {
119
+ const today = new Date().toISOString().slice(0, 10);
120
+ mkdirPrivate(vaultPath);
121
+ mkdirPrivate(path.join(vaultPath, 'daily'));
122
+ mkdirPrivate(path.join(vaultPath, 'handoffs'));
123
+ mkdirPrivate(path.join(vaultPath, 'fixtures'));
124
+ mkdirPrivate(path.join(vaultPath, '.handoff'));
125
+
126
+ writeIfMissing(path.join(vaultPath, 'Dashboard.md'), [
127
+ '# FBT Memory Dashboard',
128
+ '',
129
+ 'This local Markdown vault is the private memory substrate for your agent harnesses.',
130
+ '',
131
+ '- Agent harnesses: Claude, Codex, Claude Code, OpenCode, Hermes, OpenClaw',
132
+ '- Memory store: this vault, optionally opened in Obsidian',
133
+ '- Retrieval/source verification: qmd or the configured FBT retrieval adapter',
134
+ '- Local runtime lane: Ollama, llama.cpp, vLLM, or another approved runtime',
135
+ '',
136
+ ].join('\n'));
137
+ writeIfMissing(path.join(vaultPath, 'MEMORY.md'), [
138
+ '# Memory Index',
139
+ '',
140
+ '- [[Dashboard]] — start here',
141
+ '- [[handoffs/latest]] — newest handoff',
142
+ `- [[daily/${today}]] — today`,
143
+ '',
144
+ ].join('\n'));
145
+ writeIfMissing(path.join(vaultPath, 'handoffs', 'latest.md'), [
146
+ '# Latest Handoff',
147
+ '',
148
+ 'No handoff yet. Run `fuckbigtech route "summarize today"` after your first session.',
149
+ '',
150
+ ].join('\n'));
151
+ writeIfMissing(path.join(vaultPath, 'daily', `${today}.md`), [
152
+ `# ${today}`,
153
+ '',
154
+ '- FBT memory vault created.',
155
+ '',
156
+ ].join('\n'));
157
+ writeIfMissing(path.join(vaultPath, 'fixtures', 'demo-routing.json'), `${JSON.stringify({
158
+ name: 'demo-routing',
159
+ prompt: 'summarize today\'s handoff',
160
+ expected_lane: 'local/free',
161
+ reason: 'routine summarization should not require a premium model by default',
162
+ }, null, 2)}\n`);
163
+ writeIfMissing(path.join(vaultPath, '.handoff', 'HANDOFF.md'), [
164
+ '# Project Handoff',
165
+ '',
166
+ 'FBT created this placeholder so agent harnesses have a shared recency surface.',
167
+ '',
168
+ ].join('\n'));
169
+ return vaultPath;
170
+ }
171
+
172
+ function writeIfMissing(filePath, content) {
173
+ if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, content, { mode: 0o600 });
174
+ }
175
+
176
+ function mkdirPrivate(dirPath) {
177
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
178
+ fs.chmodSync(dirPath, 0o700);
179
+ }
180
+
181
+ function validateVault(vaultPath) {
182
+ const requiredFiles = ['Dashboard.md', 'MEMORY.md'];
183
+ const requiredFolders = ['daily'];
184
+ const missing = [
185
+ ...requiredFiles.filter((file) => !fs.existsSync(path.join(vaultPath, file))),
186
+ ...requiredFolders.filter((folder) => !fs.existsSync(path.join(vaultPath, folder))),
187
+ ];
188
+ if (!fs.existsSync(path.join(vaultPath, '.handoff')) && !fs.existsSync(path.join(vaultPath, 'handoffs'))) {
189
+ missing.push('.handoff/ or handoffs/');
190
+ }
191
+ return {
192
+ ok: fs.existsSync(vaultPath) && missing.length === 0,
193
+ missing,
194
+ };
195
+ }
196
+
197
+ function getVaultPath() {
198
+ const config = readConfig();
199
+ return config.vaultPath || defaultVault;
200
+ }
201
+
202
+ function memoryTest() {
203
+ const vaultPath = getVaultPath();
204
+ const checks = [
205
+ ['vault exists', fs.existsSync(vaultPath)],
206
+ ['Dashboard.md', fs.existsSync(path.join(vaultPath, 'Dashboard.md'))],
207
+ ['MEMORY.md', fs.existsSync(path.join(vaultPath, 'MEMORY.md'))],
208
+ ['daily folder', fs.existsSync(path.join(vaultPath, 'daily'))],
209
+ ['handoff folder', fs.existsSync(path.join(vaultPath, '.handoff')) || fs.existsSync(path.join(vaultPath, 'handoffs'))],
210
+ ['demo fixture', fs.existsSync(path.join(vaultPath, 'fixtures', 'demo-routing.json'))],
211
+ ['private boundary', !vaultPath.includes('node_modules')],
212
+ ['paid calls blocked by default', readConfig().paidCalls !== 'auto'],
213
+ ];
214
+ header();
215
+ line(paint('yellow', 'memory-test quick'));
216
+ checks.forEach(([name, ok]) => printCheck(name, ok, ok ? 'pass' : 'fail'));
217
+ const passed = checks.filter(([, ok]) => ok).length;
218
+ line();
219
+ line(`${passed}/${checks.length} checks passed`);
220
+ if (passed !== checks.length) {
221
+ line(paint('red', 'blocked: run `fuckbigtech init --with-memory` or connect a vault.'));
222
+ process.exitCode = 1;
223
+ return;
224
+ }
225
+ line(paint('green', 'result: pass'));
226
+ }
227
+
228
+ function doctor() {
229
+ header();
230
+ printDetection();
231
+ const config = readConfig();
232
+ const vaultPath = getVaultPath();
233
+ line();
234
+ line(paint('yellow', 'Local FBT config'));
235
+ printCheck('config store', fs.existsSync(configPath), configPath);
236
+ printCheck('memory vault', fs.existsSync(vaultPath), vaultPath);
237
+ printCheck('paid model guard', config.paidCalls !== 'auto', config.paidCalls || 'ask-before-spend');
238
+ line();
239
+ line(paint('green', 'No keys were uploaded. No paid calls were made.'));
240
+ }
241
+
242
+ function routeTest() {
243
+ header();
244
+ line(paint('yellow', 'route-test demo'));
245
+ line('Prompt: "summarize today\'s handoff"');
246
+ line('Decision: local/free lane');
247
+ line('Reason: routine summarization should not burn premium context.');
248
+ line('Paid calls: blocked');
249
+ line('Source requirement: read local handoff before answering');
250
+ line();
251
+ line(paint('green', 'result: pass'));
252
+ }
253
+
254
+ function routePrompt(prompt) {
255
+ header();
256
+ const text = prompt || 'summarize today\'s handoff';
257
+ const highRisk = /\b(send|deploy|publish|email|payment|delete|production|legal|medical|financial)\b/i.test(text);
258
+ const lane = highRisk ? 'premium/manual approval' : 'local/free';
259
+ line(paint('yellow', 'route decision'));
260
+ line(`Prompt: ${text}`);
261
+ line(`Lane: ${lane}`);
262
+ line(`Paid calls: ${highRisk ? 'ask first' : 'blocked by default'}`);
263
+ line(`Memory: ${fs.existsSync(getVaultPath()) ? 'local vault available' : 'no vault connected'}`);
264
+ }
265
+
266
+ async function ask(rl, question, fallback) {
267
+ const suffix = fallback ? ` (${fallback})` : '';
268
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
269
+ return answer || fallback;
270
+ }
271
+
272
+ async function interactive() {
273
+ header();
274
+ line('Claude hit the wall. Your workflow does not have to.');
275
+ line();
276
+ const rl = readline.createInterface({ input, output });
277
+ try {
278
+ line(paint('yellow', 'What are we fixing today?'));
279
+ line('[1] My agent hit a limit');
280
+ line('[2] My memory is scattered everywhere');
281
+ line('[3] I want boring work off premium models');
282
+ line('[4] I want to benchmark my current setup');
283
+ const goal = await ask(rl, 'Pick one', '1');
284
+ line();
285
+
286
+ line(paint('yellow', 'Memory setup'));
287
+ line('[1] Create a new FBT memory vault');
288
+ line('[2] Connect existing Obsidian/Markdown vault');
289
+ line('[3] Skip memory for now');
290
+ const memoryChoice = await ask(rl, 'Pick one', '1');
291
+ let vaultPath = getVaultPath();
292
+ if (memoryChoice === '1') {
293
+ vaultPath = await ask(rl, 'Vault path', defaultVault);
294
+ createVault(vaultPath);
295
+ } else if (memoryChoice === '2') {
296
+ vaultPath = await ask(rl, 'Existing vault path', defaultVault);
297
+ const validation = validateVault(path.resolve(vaultPath));
298
+ if (!validation.ok) {
299
+ line();
300
+ line(paint('red', 'That folder is not an FBT-compatible memory vault yet.'));
301
+ line(`Missing: ${validation.missing.join(', ')}`);
302
+ line('Run `fuckbigtech init --with-memory` or choose create-new for the v0 scaffold.');
303
+ return;
304
+ }
305
+ }
306
+
307
+ const config = {
308
+ ...readConfig(),
309
+ createdAt: readConfig().createdAt || new Date().toISOString(),
310
+ goal,
311
+ vaultPath: memoryChoice === '3' ? undefined : path.resolve(vaultPath),
312
+ paidCalls: 'ask-before-spend',
313
+ secrets: 'local-or-existing-env',
314
+ };
315
+ writeConfig(config);
316
+
317
+ line();
318
+ line(paint('yellow', 'Provider keys'));
319
+ line('Big Tech would like these in a web form. We are not doing that.');
320
+ line('[1] use existing .env/env vars only');
321
+ line('[2] configure later with `fuckbigtech keys`');
322
+ line('[3] 1Password/secret manager later');
323
+ await ask(rl, 'Pick one', '1');
324
+
325
+ line();
326
+ doctor();
327
+ line();
328
+ memoryTest();
329
+ line();
330
+ routeTest();
331
+ line();
332
+ line(paint('green', 'FBT is live.'));
333
+ line('Try:');
334
+ line(' fuckbigtech route "summarize today\'s handoff"');
335
+ line(' fuckbigtech start codex');
336
+ } finally {
337
+ rl.close();
338
+ }
339
+ }
340
+
341
+ function keys() {
342
+ header();
343
+ line(paint('yellow', 'API keys'));
344
+ line('FBT v0 reads provider keys from your local environment or secret manager.');
345
+ line('It does not upload keys, print keys, or make paid test calls during setup.');
346
+ line();
347
+ detect().providers.forEach(([name, ok]) => printCheck(name, ok, ok ? 'env key present' : 'missing or skipped'));
348
+ line();
349
+ line('Supported env names: OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, NVIDIA_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, CEREBRAS_API_KEY');
350
+ }
351
+
352
+ function startHarness(name) {
353
+ header();
354
+ const harness = name || 'codex';
355
+ const commands = {
356
+ codex: 'codex',
357
+ claude: 'claude',
358
+ 'claude-code': 'claude',
359
+ opencode: 'opencode',
360
+ oc: 'oc',
361
+ };
362
+ const command = commands[harness];
363
+ line(paint('yellow', `start ${harness}`));
364
+ if (!command) {
365
+ line(paint('red', 'unsupported harness for v0 safe start.'));
366
+ line(`Supported: ${Object.keys(commands).join(', ')}`);
367
+ process.exitCode = 1;
368
+ return;
369
+ }
370
+ if (!commandExists(command)) {
371
+ line(paint('red', `${command} not found.`));
372
+ line('FBT will not install or launch paid harnesses without your consent.');
373
+ process.exitCode = 1;
374
+ return;
375
+ }
376
+ line(`Run this with your normal auth/session state: ${paint('green', command)}`);
377
+ line('FBT does not read harness credentials.');
378
+ }
379
+
380
+ function help() {
381
+ header();
382
+ line('Usage: fuckbigtech [command]');
383
+ line();
384
+ line('Commands:');
385
+ line(' init --with-memory create local FBT memory vault');
386
+ line(' connect <path> validate and connect existing Markdown/Obsidian vault');
387
+ line(' doctor detect harnesses, runtimes, keys, and vault');
388
+ line(' dry-run show what FBT would write without changing files');
389
+ line(' memory-test quick verify v0 memory scaffold');
390
+ line(' route-test demo prove routine work routes away from paid lanes');
391
+ line(' route "<prompt>" classify one task');
392
+ line(' keys inspect local provider key presence');
393
+ line(' start <harness> show safe launch command for a harness');
394
+ line(' help print this help');
395
+ }
396
+
397
+ function dryRun() {
398
+ header();
399
+ const vaultPath = getVaultPath();
400
+ line(paint('yellow', 'dry-run'));
401
+ line('FBT would write:');
402
+ line(` config: ${configPath}`);
403
+ line(` vault: ${vaultPath}`);
404
+ line(' files: Dashboard.md, MEMORY.md, daily/<today>.md, handoffs/latest.md, fixtures/demo-routing.json');
405
+ line();
406
+ line('FBT would not:');
407
+ line(' modify shell rc files');
408
+ line(' overwrite existing vault files');
409
+ line(' copy provider keys');
410
+ line(' launch paid agent harnesses');
411
+ line(' make network calls');
412
+ }
413
+
414
+ async function main() {
415
+ const [command, ...rest] = args;
416
+ if (!command) return interactive();
417
+ if (command === 'help' || command === '--help' || command === '-h') return help();
418
+ if (command === 'doctor') return doctor();
419
+ if (command === 'dry-run') return dryRun();
420
+ if (command === 'keys') return keys();
421
+ if (command === 'route-test') return routeTest();
422
+ if (command === 'route') return routePrompt(rest.join(' '));
423
+ if (command === 'start') return startHarness(rest[0]);
424
+ if (command === 'memory-test') return memoryTest();
425
+ if (command === 'connect') {
426
+ header();
427
+ if (!rest[0]) {
428
+ line(paint('red', 'connect requires a vault path.'));
429
+ process.exitCode = 1;
430
+ return;
431
+ }
432
+ const target = path.resolve(rest[0]);
433
+ const validation = validateVault(target);
434
+ if (!validation.ok) {
435
+ line(paint('red', 'not connected: missing required memory files/folders.'));
436
+ line(`Missing: ${validation.missing.join(', ')}`);
437
+ process.exitCode = 1;
438
+ return;
439
+ }
440
+ writeConfig({ ...readConfig(), vaultPath: target, paidCalls: 'ask-before-spend', connectedAt: new Date().toISOString() });
441
+ printCheck('memory vault', true, target);
442
+ return;
443
+ }
444
+ if (command === 'init') {
445
+ header();
446
+ if (rest[0] !== '--with-memory') {
447
+ line(paint('red', 'init requires the --with-memory flag.'));
448
+ line('Usage: fuckbigtech init --with-memory');
449
+ line('This flag is the explicit opt-in to scaffold a local memory vault under ~/.fuckbigtech/.');
450
+ process.exitCode = 1;
451
+ return;
452
+ }
453
+ const vaultPath = createVault(defaultVault);
454
+ writeConfig({ ...readConfig(), vaultPath: path.resolve(vaultPath), paidCalls: 'ask-before-spend', createdAt: new Date().toISOString() });
455
+ printCheck('memory vault', true, vaultPath);
456
+ printCheck('paid model guard', true, 'ask-before-spend');
457
+ line(paint('green', 'result: initialized'));
458
+ return;
459
+ }
460
+ help();
461
+ process.exitCode = 1;
462
+ }
463
+
464
+ main().catch((error) => {
465
+ console.error(paint('red', error.message));
466
+ process.exitCode = 1;
467
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "fbt-os",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Local-first agent OS CLI: memory vault scaffold, harness/runtime detection, route-classification, and setup checks. v0 — zero deps, zero network calls.",
6
+ "keywords": [
7
+ "agent",
8
+ "local-first",
9
+ "ollama",
10
+ "claude-code",
11
+ "codex",
12
+ "opencode",
13
+ "memory",
14
+ "vault",
15
+ "obsidian",
16
+ "cli",
17
+ "ai"
18
+ ],
19
+ "homepage": "https://fuckbigtech.ai",
20
+ "bugs": {
21
+ "url": "https://fuckbigtech.ai/audit"
22
+ },
23
+ "author": "fuckbigtech.ai",
24
+ "license": "MIT",
25
+ "bin": {
26
+ "fuckbigtech": "bin/fuckbigtech.mjs"
27
+ },
28
+ "files": [
29
+ "bin/",
30
+ "README.md",
31
+ "LICENSE",
32
+ "package.json"
33
+ ],
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "check": "node --check ./bin/fuckbigtech.mjs",
42
+ "test": "FBT_CONFIG_DIR=.tmp/fbt-config FBT_VAULT_PATH=.tmp/fbt-memory node ./bin/fuckbigtech.mjs init --with-memory && FBT_CONFIG_DIR=.tmp/fbt-config FBT_VAULT_PATH=.tmp/fbt-memory node ./bin/fuckbigtech.mjs doctor && FBT_CONFIG_DIR=.tmp/fbt-config FBT_VAULT_PATH=.tmp/fbt-memory node ./bin/fuckbigtech.mjs memory-test quick && FBT_CONFIG_DIR=.tmp/fbt-config FBT_VAULT_PATH=.tmp/fbt-memory node ./bin/fuckbigtech.mjs route-test demo"
43
+ }
44
+ }