octboos 0.1.2

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 xoner
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
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # octboos
2
+
3
+ > One agent to control every AI tool.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/octboos.svg)](https://www.npmjs.com/package/octboos)
6
+ [![license](https://img.shields.io/npm/l/octboos.svg)](https://www.npmjs.com/package/octboos)
7
+ [![node version](https://img.shields.io/node/v/octboos.svg)](https://www.npmjs.com/package/octboos)
8
+
9
+ ## The Problem
10
+ AI coding tools start from zero every session. They don't remember your project structure, dependencies, or conventions, wasting tokens and breaking context.
11
+
12
+ ## The Solution
13
+ octboos creates a `.agent/` directory in your project—a persistent memory bank that any AI tool can read to understand your codebase instantly.
14
+
15
+ ## Quick Start
16
+ ```bash
17
+ npx octboos init
18
+ ```
19
+
20
+ ## What it generates
21
+ The `.agent/` directory contains:
22
+ ```
23
+ .agent/
24
+ ├── config.json # Octboos configuration and selected AI tools
25
+ ├── map.md # Project structure, dependencies, and tech stack
26
+ ├── style.md # Your coding conventions and preferences
27
+ └── wiki/ # Auto-generated documentation for modules and components
28
+ ```
29
+
30
+ ## Supported AI Tools
31
+ | Tool | Configuration File |
32
+ |---------------|--------------------|
33
+ | Claude Code | CLAUDE.md |
34
+ | OpenCode | AGENTS.md |
35
+ | Cursor | .cursor/rules |
36
+ | Aider | .aider.conf.yml |
37
+ | Gemini CLI | GEMINI.md |
38
+ | Codex CLI | (no file, uses context) |
39
+ | Windsurf | .windsurfrules |
40
+ | GitHub Copilot| .github/copilot-instructions.md |
41
+
42
+ ## Commands
43
+ - `octboos init` — Initialize octboos in your project (creates .agent/)
44
+ - `octboos sync` — Update .agent/ with latest project changes
45
+ - `octboos status` — Show current octboos configuration and file stats
46
+
47
+ ## How it works
48
+ 1. **Scan** — octboos analyzes your project structure, dependencies, and files
49
+ 2. **Generate** — creates/updates .agent/ with context for AI tools
50
+ 3. **AI reads** — your AI tool loads .agent/ files to understand your project instantly
51
+
52
+ ## License
53
+ MIT
package/bin/octboos.js ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
9
+
10
+ program
11
+ .name('octboos')
12
+ .description('One agent to control every AI tool')
13
+ .version(pkg.version);
14
+
15
+ // Commands
16
+ const { initCommand } = await import('../src/commands/init.js');
17
+ const { syncCommand } = await import('../src/commands/sync.js');
18
+ const { statusCommand } = await import('../src/commands/status.js');
19
+
20
+ program
21
+ .command('init')
22
+ .description('Initialize Octboos in current project')
23
+ .option('-y, --yes', 'Skip prompts, use defaults')
24
+ .option('--adapters <list>', 'Comma-separated adapters: claude,codex,cursor,windsurf,copilot')
25
+ .action(initCommand);
26
+
27
+ program
28
+ .command('sync')
29
+ .description('Sync and update .agent/ wiki and context')
30
+ .option('--incremental', 'Only update changed files')
31
+ .action(syncCommand);
32
+
33
+ program
34
+ .command('status')
35
+ .description('Show current Octboos status for this project')
36
+ .action(statusCommand);
37
+
38
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "octboos",
3
+ "version": "0.1.2",
4
+ "description": "One agent to control every AI tool",
5
+ "keywords": [
6
+ "ai",
7
+ "agent",
8
+ "claude",
9
+ "codex",
10
+ "cursor",
11
+ "context",
12
+ "memory",
13
+ "wiki"
14
+ ],
15
+ "author": "xoner",
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "bin": {
19
+ "octboos": "bin/octboos.js"
20
+ },
21
+ "main": "./src/index.js",
22
+ "files": [
23
+ "bin/",
24
+ "src/"
25
+ ],
26
+ "scripts": {
27
+ "dev": "node bin/octboos.js",
28
+ "test": "node --test"
29
+ },
30
+ "homepage": "https://github.com/Xoner1/octboos",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/Xoner1/octboos.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/Xoner1/octboos/issues"
37
+ },
38
+ "dependencies": {
39
+ "@inquirer/prompts": "^3.3.0",
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.0.0",
42
+ "fast-glob": "^3.3.2",
43
+ "ora": "^8.0.1"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ }
48
+ }
@@ -0,0 +1,116 @@
1
+ // All supported AI tool adapters
2
+ export const ADAPTERS = {
3
+ claude: {
4
+ name: 'Claude Code',
5
+ file: 'CLAUDE.md',
6
+ description: 'Anthropic Claude Code',
7
+ },
8
+ codex: {
9
+ name: 'Codex / OpenCode',
10
+ file: 'AGENTS.md',
11
+ description: 'OpenAI Codex CLI & OpenCode',
12
+ },
13
+ cursor: {
14
+ name: 'Cursor',
15
+ file: '.cursor/rules',
16
+ description: 'Cursor IDE',
17
+ },
18
+ windsurf: {
19
+ name: 'Windsurf',
20
+ file: '.windsurfrules',
21
+ description: 'Windsurf IDE',
22
+ },
23
+ copilot: {
24
+ name: 'GitHub Copilot',
25
+ file: '.github/copilot-instructions.md',
26
+ description: 'GitHub Copilot',
27
+ },
28
+ gemini: {
29
+ name: 'Gemini CLI',
30
+ file: 'GEMINI.md',
31
+ description: 'Google Gemini CLI',
32
+ },
33
+ };
34
+
35
+ export function generateAdapterContent(adapterKey, projectName) {
36
+ const header = buildHeader(projectName);
37
+
38
+ switch (adapterKey) {
39
+ case 'claude':
40
+ return `${header}
41
+
42
+ ## Octboos Instructions
43
+ > This file is auto-managed by Octboos. Edit \`.agent/\` files instead.
44
+
45
+ ## Before Every Task
46
+ 1. Read \`.agent/map.md\` for project structure
47
+ 2. Check \`.agent/wiki/\` for module documentation
48
+ 3. Follow conventions in \`.agent/style.md\`
49
+ 4. After completing a task, update relevant \`.agent/wiki/\` files
50
+
51
+ ## Quick Reference
52
+ - Project map: \`.agent/map.md\`
53
+ - Code wiki: \`.agent/wiki/\`
54
+ - User style: \`.agent/style.md\`
55
+ - Octboos config: \`.agent/config.json\`
56
+ `;
57
+
58
+ case 'codex':
59
+ return `${header}
60
+
61
+ ## Octboos Context
62
+ > Auto-managed by Octboos. Source of truth: \`.agent/\`
63
+
64
+ ## Required Reading
65
+ Before any task, read:
66
+ - \`.agent/map.md\` — project structure and stack
67
+ - \`.agent/wiki/\` — module documentation
68
+ - \`.agent/style.md\` — coding conventions
69
+
70
+ ## Update Rule
71
+ After completing any task, sync relevant docs:
72
+ \`\`\`
73
+ npx octboos sync --incremental
74
+ \`\`\`
75
+ `;
76
+
77
+ case 'cursor':
78
+ case 'windsurf':
79
+ return `# Octboos Context for ${ADAPTERS[adapterKey].name}
80
+ > Auto-managed. Edit .agent/ files instead.
81
+
82
+ Always read .agent/map.md before starting any task.
83
+ Follow conventions in .agent/style.md.
84
+ Reference .agent/wiki/ for module documentation.
85
+ `;
86
+
87
+ case 'copilot':
88
+ return `# GitHub Copilot Instructions
89
+ > Managed by Octboos
90
+
91
+ ## Context Files
92
+ - \`.agent/map.md\` — project map and stack
93
+ - \`.agent/wiki/\` — module documentation
94
+ - \`.agent/style.md\` — code style and conventions
95
+
96
+ Always reference these files before suggesting code changes.
97
+ `;
98
+
99
+ case 'gemini':
100
+ return `${header}
101
+
102
+ ## Octboos Context
103
+ Read \`.agent/map.md\` and \`.agent/wiki/\` before any task.
104
+ Follow \`.agent/style.md\` for code conventions.
105
+ `;
106
+
107
+ default:
108
+ return header;
109
+ }
110
+ }
111
+
112
+ function buildHeader(projectName) {
113
+ return `# ${projectName || 'Project'} — Octboos Context
114
+ > Last synced: ${new Date().toISOString().split('T')[0]}
115
+ > Managed by Octboos (https://github.com/Octboos1/octboos.git)`;
116
+ }
@@ -0,0 +1,259 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { input, checkbox, confirm } from '@inquirer/prompts';
6
+ import { scanProject, buildFileMap, detectInstalledTools } from '../indexer/scanner.js';
7
+ import { ADAPTERS, generateAdapterContent } from '../adapters/index.js';
8
+
9
+ const AGENT_DIR = '.agent';
10
+
11
+ export async function initCommand(options) {
12
+ console.log('');
13
+ console.log(chalk.bold.cyan(' ╔═══════════════════════════════╗'));
14
+ console.log(chalk.bold.cyan(' ║ Octboos — Init ║'));
15
+ console.log(chalk.bold.cyan(' ║ Transform your AI tool ║'));
16
+ console.log(chalk.bold.cyan(' ╚═══════════════════════════════╝'));
17
+ console.log('');
18
+
19
+ const cwd = process.cwd();
20
+
21
+ // Check if already initialized
22
+ if (existsSync(join(cwd, AGENT_DIR, 'config.json'))) {
23
+ console.log(chalk.yellow(' ⚠ Octboos already initialized in this project.'));
24
+ console.log(chalk.gray(' Run ' + chalk.white('npx octboos sync') + ' to update.'));
25
+ console.log('');
26
+ return;
27
+ }
28
+
29
+ // Step 0 — Detect installed AI tools
30
+ let detectedTools = { detected: [], uncertain: [], notFound: [] };
31
+ try {
32
+ detectedTools = detectInstalledTools();
33
+ if (detectedTools.detected.length > 0) {
34
+ console.log(chalk.gray(` 🔍 Found ${detectedTools.detected.length} tools on your machine`));
35
+ console.log('');
36
+ }
37
+ } catch (err) {
38
+ // Silently fall back to manual selection if detection fails
39
+ }
40
+
41
+ // Step 1 — Project name
42
+ let projectName;
43
+ try {
44
+ const pkgPath = join(cwd, 'package.json');
45
+ projectName = existsSync(pkgPath)
46
+ ? JSON.parse(readFileSync(pkgPath, 'utf8')).name || ''
47
+ : '';
48
+ } catch { projectName = ''; }
49
+
50
+ if (!options.yes) {
51
+ projectName = await input({
52
+ message: 'Project name:',
53
+ default: projectName || 'my-project',
54
+ });
55
+ }
56
+
57
+ // Step 2 — Choose adapters
58
+ let selectedAdapters = [];
59
+
60
+ if (!options.yes && !options.adapters) {
61
+ // Build choices with pre-checked detected tools
62
+ const choices = Object.entries(ADAPTERS).map(([key, val]) => {
63
+ let checked = false;
64
+ let name = `${val.name} (${val.file})`;
65
+
66
+ // Check if detected
67
+ if (detectedTools.detected.includes(key)) {
68
+ checked = true;
69
+ }
70
+ // Check if uncertain
71
+ else if (detectedTools.uncertain.includes(key)) {
72
+ name += ' (not sure)';
73
+ }
74
+
75
+ return {
76
+ name,
77
+ value: key,
78
+ checked
79
+ };
80
+ });
81
+
82
+ selectedAdapters = await checkbox({
83
+ message: 'Which AI tools do you use?',
84
+ choices
85
+ });
86
+ } else if (options.adapters) {
87
+ selectedAdapters = options.adapters.split(',').map(s => s.trim());
88
+ } else {
89
+ // Default fallback when options.yes is true
90
+ selectedAdapters = ['claude', 'codex'];
91
+ }
92
+
93
+ // Step 3 — Scan project
94
+ const spinner = ora({
95
+ text: chalk.gray('Scanning project...'),
96
+ color: 'cyan',
97
+ }).start();
98
+
99
+ let scanResult;
100
+ try {
101
+ scanResult = await scanProject(cwd);
102
+ spinner.succeed(chalk.green(`Scanned ${scanResult.fileCount} files`));
103
+ } catch (err) {
104
+ spinner.fail('Failed to scan project');
105
+ console.error(err);
106
+ return;
107
+ }
108
+
109
+ // Step 4 — Create .agent/ directory
110
+ const agentDir = join(cwd, AGENT_DIR);
111
+ const wikiDir = join(agentDir, 'wiki');
112
+
113
+ mkdirSync(agentDir, { recursive: true });
114
+ mkdirSync(wikiDir, { recursive: true });
115
+
116
+ // Step 5 — Generate files
117
+ const genSpinner = ora({ text: chalk.gray('Generating context files...'), color: 'cyan' }).start();
118
+
119
+ // config.json
120
+ const config = {
121
+ version: '0.1.0',
122
+ projectName,
123
+ adapters: selectedAdapters,
124
+ autoSync: true,
125
+ created: new Date().toISOString(),
126
+ lastSync: new Date().toISOString(),
127
+ };
128
+ writeFileSync(join(agentDir, 'config.json'), JSON.stringify(config, null, 2));
129
+
130
+ // map.md
131
+ const mapContent = buildFileMap(scanResult, cwd);
132
+ writeFileSync(join(agentDir, 'map.md'), mapContent);
133
+
134
+ // style.md
135
+ writeFileSync(join(agentDir, 'style.md'), buildStyleTemplate(projectName));
136
+
137
+ // wiki/INDEX.md
138
+ writeFileSync(join(wikiDir, 'INDEX.md'), buildWikiIndex(projectName, scanResult));
139
+
140
+ genSpinner.succeed(chalk.green('Generated .agent/ context files'));
141
+
142
+ // Step 6 — Generate adapter files
143
+ const adapterSpinner = ora({ text: chalk.gray('Configuring AI tools...'), color: 'cyan' }).start();
144
+
145
+ const generatedAdapters = [];
146
+ for (const adapterKey of selectedAdapters) {
147
+ const adapter = ADAPTERS[adapterKey];
148
+ if (!adapter) continue;
149
+
150
+ const filePath = join(cwd, adapter.file);
151
+ const fileDir = dirname(filePath);
152
+
153
+ mkdirSync(fileDir, { recursive: true });
154
+
155
+ // Don't overwrite existing files — append Octboos section
156
+ if (existsSync(filePath)) {
157
+ const existing = readFileSync(filePath, 'utf8');
158
+ if (!existing.includes('Octboos')) {
159
+ writeFileSync(filePath, existing + '\n\n' + generateAdapterContent(adapterKey, projectName));
160
+ }
161
+ } else {
162
+ writeFileSync(filePath, generateAdapterContent(adapterKey, projectName));
163
+ }
164
+
165
+ generatedAdapters.push(adapter.file);
166
+ }
167
+
168
+ adapterSpinner.succeed(chalk.green(`Configured ${generatedAdapters.length} AI tools`));
169
+
170
+ // Step 7 — Add to .gitignore if needed (keep .agent/ tracked)
171
+ const gitignorePath = join(cwd, '.gitignore');
172
+ if (existsSync(gitignorePath)) {
173
+ const gitignore = readFileSync(gitignorePath, 'utf8');
174
+ if (!gitignore.includes('.agent/graph.html')) {
175
+ writeFileSync(gitignorePath, gitignore + '\n# Octboos - keep .agent/ tracked except large files\n.agent/graph.html\n');
176
+ }
177
+ }
178
+
179
+ // Done!
180
+ console.log('');
181
+ console.log(chalk.bold.green(' ✓ Octboos initialized successfully!'));
182
+ console.log('');
183
+ console.log(chalk.gray(' Created:'));
184
+ console.log(chalk.white(' .agent/'));
185
+ console.log(chalk.gray(' ├── ') + chalk.white('config.json ') + chalk.gray('← Octboos config'));
186
+ console.log(chalk.gray(' ├── ') + chalk.white('map.md ') + chalk.gray('← project structure'));
187
+ console.log(chalk.gray(' ├── ') + chalk.white('style.md ') + chalk.gray('← coding conventions'));
188
+ console.log(chalk.gray(' └── ') + chalk.white('wiki/ ') + chalk.gray('← auto documentation'));
189
+ console.log('');
190
+ console.log(chalk.gray(' AI configs:'));
191
+ for (const f of generatedAdapters) {
192
+ console.log(chalk.gray(' ✓ ') + chalk.white(f));
193
+ }
194
+ console.log('');
195
+ console.log(chalk.bold(' Next steps:'));
196
+ console.log(chalk.cyan(' 1.') + chalk.white(' Edit .agent/style.md with your coding conventions'));
197
+ console.log(chalk.cyan(' 2.') + chalk.white(' Run npx octboos sync to update wiki'));
198
+ console.log(chalk.cyan(' 3.') + chalk.white(' Commit .agent/ to git'));
199
+ console.log('');
200
+ }
201
+
202
+ function buildStyleTemplate(projectName) {
203
+ return `# Coding Style — ${projectName}
204
+ > Edit this file to teach Octboos your preferences.
205
+ > Last updated: ${new Date().toISOString().split('T')[0]}
206
+
207
+ ## General Conventions
208
+ - [ ] Add your naming conventions here
209
+ - [ ] Add your file structure preferences here
210
+
211
+ ## Code Style
212
+ - [ ] Indentation: (tabs / 2 spaces / 4 spaces)
213
+ - [ ] Quotes: (single / double)
214
+ - [ ] Semicolons: (yes / no)
215
+
216
+ ## Architecture Patterns
217
+ - [ ] Describe your patterns here (MVC, Repository, etc.)
218
+
219
+ ## What to Avoid
220
+ - [ ] List anti-patterns specific to this project
221
+
222
+ ## Useful Commands
223
+ \`\`\`bash
224
+ # Add your most-used project commands here
225
+ # npm run dev
226
+ # npm run build
227
+ # npm test
228
+ \`\`\`
229
+ `;
230
+ }
231
+
232
+ function buildWikiIndex(projectName, scanResult) {
233
+ const lines = [
234
+ `# Wiki — ${projectName}`,
235
+ `> Auto-generated by Octboos | ${new Date().toISOString().split('T')[0]}`,
236
+ '',
237
+ '## Modules',
238
+ '> Add module documentation here or run `npx octboos sync` to auto-generate.',
239
+ '',
240
+ '## Stack Summary',
241
+ ];
242
+
243
+ if (scanResult.frameworks.length > 0) {
244
+ lines.push(`**Frameworks:** ${scanResult.frameworks.join(', ')}`);
245
+ }
246
+ if (scanResult.stack.length > 0) {
247
+ lines.push(`**Libraries:** ${scanResult.stack.join(', ')}`);
248
+ }
249
+
250
+ const langs = Object.entries(scanResult.languages)
251
+ .sort((a, b) => b[1] - a[1])
252
+ .slice(0, 3)
253
+ .map(([l]) => l);
254
+ if (langs.length > 0) {
255
+ lines.push(`**Languages:** ${langs.join(', ')}`);
256
+ }
257
+
258
+ return lines.join('\n');
259
+ }
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export async function statusCommand() {
6
+ const cwd = process.cwd();
7
+ const configPath = join(cwd, '.agent', 'config.json');
8
+
9
+ console.log('');
10
+
11
+ if (!existsSync(configPath)) {
12
+ console.log(chalk.yellow(' ✗ Octboos not initialized in this project'));
13
+ console.log(chalk.gray(' Run: ') + chalk.white('npx octboos init'));
14
+ console.log('');
15
+ return;
16
+ }
17
+
18
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
19
+ const lastSync = new Date(config.lastSync);
20
+ const hoursSince = Math.floor((Date.now() - lastSync) / 1000 / 60 / 60);
21
+
22
+ console.log(chalk.bold.cyan(' Octboos Status'));
23
+ console.log('');
24
+ console.log(chalk.gray(' Project: ') + chalk.white(config.projectName));
25
+ console.log(chalk.gray(' Version: ') + chalk.white(config.version));
26
+ console.log(chalk.gray(' Last sync: ') + (
27
+ hoursSince < 24
28
+ ? chalk.green(`${hoursSince}h ago`)
29
+ : chalk.yellow(`${Math.floor(hoursSince / 24)}d ago — run octboos sync`)
30
+ ));
31
+
32
+ // Files
33
+ console.log('');
34
+ console.log(chalk.gray(' Context files:'));
35
+ const agentFiles = [
36
+ ['map.md', 'Project structure'],
37
+ ['style.md', 'Coding style'],
38
+ ['config.json', 'Octboos config'],
39
+ ];
40
+ for (const [file, desc] of agentFiles) {
41
+ const exists = existsSync(join(cwd, '.agent', file));
42
+ console.log(
43
+ ' ' + (exists ? chalk.green('✓') : chalk.red('✗')) + ' ' +
44
+ chalk.white(`.agent/${file}`) + chalk.gray(` — ${desc}`)
45
+ );
46
+ }
47
+
48
+ // Wiki
49
+ const wikiDir = join(cwd, '.agent', 'wiki');
50
+ if (existsSync(wikiDir)) {
51
+ const wikiFiles = readdirSync(wikiDir).filter(f => f.endsWith('.md'));
52
+ console.log('');
53
+ console.log(chalk.gray(` Wiki: ${wikiFiles.length} files`));
54
+ for (const f of wikiFiles.slice(0, 5)) {
55
+ console.log(chalk.gray(' · ') + chalk.white(`.agent/wiki/${f}`));
56
+ }
57
+ if (wikiFiles.length > 5) {
58
+ console.log(chalk.gray(` ... and ${wikiFiles.length - 5} more`));
59
+ }
60
+ }
61
+
62
+ // Adapters
63
+ console.log('');
64
+ console.log(chalk.gray(' AI tools configured:'));
65
+ const { ADAPTERS } = await import('../adapters/index.js');
66
+ for (const adapterKey of config.adapters) {
67
+ const adapter = ADAPTERS[adapterKey];
68
+ if (!adapter) continue;
69
+ const exists = existsSync(join(cwd, adapter.file));
70
+ console.log(
71
+ ' ' + (exists ? chalk.green('✓') : chalk.red('✗')) + ' ' +
72
+ chalk.white(adapter.name) + chalk.gray(` (${adapter.file})`)
73
+ );
74
+ }
75
+
76
+ console.log('');
77
+ }
@@ -0,0 +1,55 @@
1
+ import { existsSync, writeFileSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { scanProject, buildFileMap } from '../indexer/scanner.js';
6
+ import { ADAPTERS, generateAdapterContent } from '../adapters/index.js';
7
+
8
+ export async function syncCommand(options) {
9
+ const cwd = process.cwd();
10
+ const configPath = join(cwd, '.agent', 'config.json');
11
+
12
+ if (!existsSync(configPath)) {
13
+ console.log('');
14
+ console.log(chalk.yellow(' ⚠ Octboos not initialized.'));
15
+ console.log(chalk.gray(' Run ' + chalk.white('npx octboos init') + ' first.'));
16
+ console.log('');
17
+ return;
18
+ }
19
+
20
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
21
+ console.log('');
22
+ console.log(chalk.bold.cyan(` Octboos Sync — ${config.projectName}`));
23
+ console.log('');
24
+
25
+ // Scan project
26
+ const spinner = ora({ text: chalk.gray('Scanning project...'), color: 'cyan' }).start();
27
+ const scanResult = await scanProject(cwd);
28
+ spinner.succeed(chalk.green(`Scanned ${scanResult.fileCount} files`));
29
+
30
+ // Update map.md
31
+ const mapSpinner = ora({ text: chalk.gray('Updating map.md...'), color: 'cyan' }).start();
32
+ const mapContent = buildFileMap(scanResult, cwd);
33
+ writeFileSync(join(cwd, '.agent', 'map.md'), mapContent);
34
+ mapSpinner.succeed(chalk.green('Updated .agent/map.md'));
35
+
36
+ // Update adapter files
37
+ const adapterSpinner = ora({ text: chalk.gray('Updating AI tool configs...'), color: 'cyan' }).start();
38
+ for (const adapterKey of config.adapters) {
39
+ const adapter = ADAPTERS[adapterKey];
40
+ if (!adapter) continue;
41
+ const filePath = join(cwd, adapter.file);
42
+ if (existsSync(filePath)) {
43
+ writeFileSync(filePath, generateAdapterContent(adapterKey, config.projectName));
44
+ }
45
+ }
46
+ adapterSpinner.succeed(chalk.green('Updated AI tool configs'));
47
+
48
+ // Update lastSync in config
49
+ config.lastSync = new Date().toISOString();
50
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
51
+
52
+ console.log('');
53
+ console.log(chalk.bold.green(' ✓ Sync complete!'));
54
+ console.log('');
55
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { scanProject, buildFileMap } from './indexer/scanner.js';
2
+ export { ADAPTERS, generateAdapterContent } from './adapters/index.js';
@@ -0,0 +1,285 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join, extname, relative } from 'path';
3
+ import fg from 'fast-glob';
4
+ import { execSync } from 'child_process';
5
+
6
+ // File extensions by language
7
+ const LANG_MAP = {
8
+ '.ts': 'TypeScript', '.tsx': 'TypeScript/React',
9
+ '.js': 'JavaScript', '.jsx': 'JavaScript/React',
10
+ '.py': 'Python', '.go': 'Go',
11
+ '.rs': 'Rust', '.java': 'Java',
12
+ '.php': 'PHP', '.rb': 'Ruby',
13
+ '.vue': 'Vue', '.svelte': 'Svelte',
14
+ '.css': 'CSS', '.scss': 'SCSS',
15
+ '.sql': 'SQL', '.prisma': 'Prisma',
16
+ };
17
+
18
+ // Framework detection by file presence
19
+ const FRAMEWORK_SIGNALS = [
20
+ { file: 'next.config.js', name: 'Next.js' },
21
+ { file: 'next.config.ts', name: 'Next.js' },
22
+ { file: 'nuxt.config.ts', name: 'Nuxt.js' },
23
+ { file: 'vite.config.ts', name: 'Vite' },
24
+ { file: 'remix.config.js', name: 'Remix' },
25
+ { file: 'astro.config.mjs', name: 'Astro' },
26
+ { file: 'svelte.config.js', name: 'SvelteKit' },
27
+ { file: 'angular.json', name: 'Angular' },
28
+ { file: 'manage.py', name: 'Django' },
29
+ { file: 'requirements.txt', name: 'Python' },
30
+ { file: 'Cargo.toml', name: 'Rust' },
31
+ { file: 'go.mod', name: 'Go' },
32
+ { file: 'composer.json', name: 'PHP/Laravel' },
33
+ { file: 'artisan', name: 'Laravel' },
34
+ ];
35
+
36
+ const IGNORE_DIRS = [
37
+ '**/node_modules/**', '**/.git/**', '**/dist/**',
38
+ '**/build/**', '**/.next/**', '**/coverage/**',
39
+ '**/.agent/**', '**/vendor/**', '**/__pycache__/**'
40
+ ];
41
+
42
+ export async function scanProject(cwd = process.cwd()) {
43
+ const result = {
44
+ stack: [],
45
+ frameworks: [],
46
+ languages: {},
47
+ structure: [],
48
+ fileCount: 0,
49
+ hasTests: false,
50
+ hasPrisma: false,
51
+ hasDocker: false,
52
+ packageJson: null,
53
+ gitIgnore: null,
54
+ };
55
+
56
+ // Read package.json if exists
57
+ const pkgPath = join(cwd, 'package.json');
58
+ if (existsSync(pkgPath)) {
59
+ try {
60
+ result.packageJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
61
+ result.stack.push(...detectFromPackageJson(result.packageJson));
62
+ } catch {}
63
+ }
64
+
65
+ // Detect frameworks by file presence
66
+ for (const signal of FRAMEWORK_SIGNALS) {
67
+ if (existsSync(join(cwd, signal.file))) {
68
+ result.frameworks.push(signal.name);
69
+ }
70
+ }
71
+
72
+ // Scan all files
73
+ const files = await fg('**/*', {
74
+ cwd,
75
+ ignore: IGNORE_DIRS,
76
+ onlyFiles: true,
77
+ dot: false,
78
+ });
79
+
80
+ result.fileCount = files.length;
81
+
82
+ // Count languages
83
+ for (const file of files) {
84
+ const ext = extname(file);
85
+ if (LANG_MAP[ext]) {
86
+ result.languages[LANG_MAP[ext]] = (result.languages[LANG_MAP[ext]] || 0) + 1;
87
+ }
88
+ if (file.includes('.test.') || file.includes('.spec.') || file.includes('__tests__')) {
89
+ result.hasTests = true;
90
+ }
91
+ if (file.endsWith('.prisma')) result.hasPrisma = true;
92
+ if (file === 'Dockerfile' || file === 'docker-compose.yml') result.hasDocker = true;
93
+ }
94
+
95
+ // Build top-level structure
96
+ const topLevel = await fg('*', {
97
+ cwd,
98
+ ignore: IGNORE_DIRS,
99
+ onlyDirectories: true,
100
+ });
101
+ result.structure = topLevel;
102
+
103
+ return result;
104
+ }
105
+
106
+ function detectFromPackageJson(pkg) {
107
+ const stack = [];
108
+ const allDeps = {
109
+ ...pkg.dependencies || {},
110
+ ...pkg.devDependencies || {}
111
+ };
112
+
113
+ const checks = [
114
+ ['react', 'React'],
115
+ ['vue', 'Vue'],
116
+ ['svelte', 'Svelte'],
117
+ ['express', 'Express'],
118
+ ['fastify', 'Fastify'],
119
+ ['prisma', 'Prisma'],
120
+ ['mongoose', 'MongoDB/Mongoose'],
121
+ ['typeorm', 'TypeORM'],
122
+ ['drizzle-orm', 'Drizzle ORM'],
123
+ ['tailwindcss', 'Tailwind CSS'],
124
+ ['@mui/material', 'Material UI'],
125
+ ['next-auth', 'NextAuth'],
126
+ ['@clerk/nextjs', 'Clerk Auth'],
127
+ ['zod', 'Zod'],
128
+ ['trpc', 'tRPC'],
129
+ ['graphql', 'GraphQL'],
130
+ ['socket.io', 'Socket.io'],
131
+ ['jest', 'Jest'],
132
+ ['vitest', 'Vitest'],
133
+ ['playwright', 'Playwright'],
134
+ ];
135
+
136
+ for (const [dep, name] of checks) {
137
+ if (allDeps[dep]) stack.push(name);
138
+ }
139
+
140
+ return stack;
141
+ }
142
+
143
+ export function buildFileMap(scanResult, cwd) {
144
+ const lines = [];
145
+
146
+ lines.push('# Project File Map');
147
+ lines.push(`> Generated by Octboos | ${new Date().toISOString().split('T')[0]}`);
148
+ lines.push('');
149
+
150
+ // Frameworks & Stack
151
+ if (scanResult.frameworks.length > 0) {
152
+ lines.push('## Frameworks');
153
+ for (const f of scanResult.frameworks) lines.push(`- ${f}`);
154
+ lines.push('');
155
+ }
156
+
157
+ if (scanResult.stack.length > 0) {
158
+ lines.push('## Stack & Libraries');
159
+ for (const s of scanResult.stack) lines.push(`- ${s}`);
160
+ lines.push('');
161
+ }
162
+
163
+ // Languages
164
+ const langs = Object.entries(scanResult.languages)
165
+ .sort((a, b) => b[1] - a[1])
166
+ .slice(0, 5);
167
+ if (langs.length > 0) {
168
+ lines.push('## Languages');
169
+ for (const [lang, count] of langs) {
170
+ lines.push(`- ${lang}: ${count} files`);
171
+ }
172
+ lines.push('');
173
+ }
174
+
175
+ // Directory structure
176
+ if (scanResult.structure.length > 0) {
177
+ lines.push('## Directory Structure');
178
+ for (const dir of scanResult.structure) {
179
+ lines.push(`- \`${dir}/\``);
180
+ }
181
+ lines.push('');
182
+ }
183
+
184
+ // Features detected
185
+ const features = [];
186
+ if (scanResult.hasTests) features.push('Tests');
187
+ if (scanResult.hasPrisma) features.push('Prisma ORM');
188
+ if (scanResult.hasDocker) features.push('Docker');
189
+ if (features.length > 0) {
190
+ lines.push('## Detected Features');
191
+ for (const f of features) lines.push(`- ${f}`);
192
+ lines.push('');
193
+ }
194
+
195
+ lines.push(`## Stats`);
196
+ lines.push(`- Total files: ${scanResult.fileCount}`);
197
+
198
+ return lines.join('\n');
199
+ }
200
+
201
+ export function detectInstalledTools() {
202
+ const result = {
203
+ detected: [],
204
+ uncertain: [],
205
+ notFound: ['claude', 'opencode', 'cursor', 'aider', 'gemini', 'codex', 'windsurf']
206
+ };
207
+
208
+ // Tools to check via which/where
209
+ const cliTools = [
210
+ { name: 'claude', command: 'claude' },
211
+ { name: 'opencode', command: 'opencode' },
212
+ { name: 'cursor', command: 'cursor' },
213
+ { name: 'aider', command: 'aider' },
214
+ { name: 'gemini', command: 'gemini' },
215
+ { name: 'codex', command: 'codex' },
216
+ { name: 'windsurf', command: 'windsurf' }
217
+ ];
218
+
219
+ // Check CLI tools
220
+ for (const tool of cliTools) {
221
+ try {
222
+ const platform = process.platform;
223
+ let command;
224
+ if (platform === 'win32') {
225
+ command = `where ${tool.command}`;
226
+ } else {
227
+ command = `which ${tool.command}`;
228
+ }
229
+ execSync(command, { stdio: 'ignore' });
230
+ // Move from notFound to detected
231
+ result.detected.push(tool.name);
232
+ result.notFound = result.notFound.filter(t => t !== tool.name);
233
+ } catch (err) {
234
+ // Tool not found via CLI, keep in notFound for now
235
+ }
236
+ }
237
+
238
+ // Check common paths
239
+ const home = process.env.HOME || process.env.USERPROFILE;
240
+ const localAppData = process.env.LOCALAPPDATA;
241
+ const appData = process.env.APPDATA;
242
+
243
+ // Mac paths
244
+ const macPaths = [
245
+ { tool: 'cursor', path: '/Applications/Cursor.app' },
246
+ { tool: 'windsurf', path: '/Applications/Windsurf.app' },
247
+ { tool: 'claude', path: `${home}/.claude/` },
248
+ { tool: 'aider', path: `${home}/.config/aider/` }
249
+ ];
250
+
251
+ // Windows paths
252
+ const winPaths = [
253
+ { tool: 'cursor', path: `${localAppData}\\Programs\\cursor` },
254
+ { tool: 'claude', path: `${appData}\\Claude\\` }
255
+ ];
256
+
257
+ const pathsToCheck = process.platform === 'win32' ? winPaths : macPaths;
258
+
259
+ for (const { tool, path } of pathsToCheck) {
260
+ try {
261
+ if (existsSync(path)) {
262
+ // Move from notFound to detected if not already there
263
+ if (!result.detected.includes(tool)) {
264
+ result.detected.push(tool);
265
+ result.notFound = result.notFound.filter(t => t !== tool);
266
+ }
267
+ // If we found it via path but not CLI, mark as uncertain
268
+ if (!result.detected.includes(tool)) {
269
+ result.uncertain.push(tool);
270
+ result.notFound = result.notFound.filter(t => t !== tool);
271
+ result.detected = result.detected.filter(t => t !== tool);
272
+ }
273
+ }
274
+ } catch (err) {
275
+ // Path doesn't exist or other error
276
+ }
277
+ }
278
+
279
+ // Remove duplicates
280
+ result.detected = [...new Set(result.detected)];
281
+ result.uncertain = [...new Set(result.uncertain)];
282
+ result.notFound = [...new Set(result.notFound)];
283
+
284
+ return result;
285
+ }