Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ PORT=3000
NODE_ENV=development
LOG_LEVEL=info

# Optional API Key Authentication
# If set, all /api/* endpoints (except /api/health) require an X-API-Key header.
# Leave empty or unset to disable authentication.
API_KEY=

# PostgreSQL Configuration
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=sandeep_ai
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# ⚠️ Set a strong password — required when using Docker Compose
POSTGRES_PASSWORD=YOUR_STRONG_PASSWORD_HERE

# Qdrant Vector Database (Optional)
QDRANT_URL=http://localhost:6333
Expand All @@ -33,12 +39,16 @@ GEMINI_DEFAULT_MODEL=gemini-pro

# Ollama Configuration (for local models)
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_DEFAULT_MODEL=llama2
OLLAMA_DEFAULT_MODEL=llama3.1:8b

# OpenRouter Configuration (single key for Claude, GPT-4o, Gemini, Llama, etc.)
OPENROUTER_API_KEY=
OPENROUTER_DEFAULT_MODEL=anthropic/claude-3.5-haiku

# Embeddings Configuration
EMBEDDINGS_PROVIDER=openai
EMBEDDINGS_MODEL=text-embedding-3-small
EMBEDDINGS_DIMENSION=1536
# Embeddings Configuration (keep on Ollama even when using cloud providers)
EMBEDDINGS_PROVIDER=ollama
EMBEDDINGS_MODEL=nomic-embed-text
EMBEDDINGS_DIMENSION=768

# Memory Configuration
SHORT_TERM_TOKEN_LIMIT=4000
Expand Down
32 changes: 10 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,13 @@ sandeep-ai/
│ ├── shortTerm.ts
│ └── memoryIndex.ts
<<<<<<< HEAD
├── models/ LLM providers
│ ├── baseModel.ts # Interface
│ ├── openaiModel.ts # OpenAI adapter
│ ├── geminiModel.ts # Gemini adapter
│ ├── ollamaModel.ts # Ollama adapter
│ └── index.ts # Provider factory
=======
├── models/
│ ├── baseModel.ts
│ ├── openaiModel.ts
│ ├── geminiModel.ts
│ ├── ollamaModel.ts
│ ├── openRouterModel.ts ← NEW
│ └── index.ts ← UPDATED
>>>>>>> c1af365 (Changed the timps v2.0)
│ ├── baseModel.ts # Interface
│ ├── openaiModel.ts # OpenAI adapter
│ ├── geminiModel.ts # Gemini adapter
│ ├── ollamaModel.ts # Ollama adapter
│ ├── openRouterModel.ts # OpenRouter adapter
│ └── index.ts # Provider factory
├── tools/
│ ├── baseTool.ts
Expand Down Expand Up @@ -374,14 +365,11 @@ curl http://localhost:3000/api/health
| `POSTGRES_USER` | Database user | postgres |
| `POSTGRES_PASSWORD` | Database password | — |
| `QDRANT_URL` | Vector store URL | http://localhost:6333 |
<<<<<<< HEAD
| `NODE_ENV` | Environment | development |
| `PORT` | API server port | 3000
=======
| `EMBEDDINGS_MODEL` | Embedding model | nomic-embed-text |
| `EMBEDDINGS_DIMENSION` | Embedding dimensions | 768 |
| `PORT` | API server port | 3000 |
>>>>>>> c1af365 (Changed the timps v2.0)
| `API_KEY` | Optional API key for authentication | — |

### Database Schema

Expand Down Expand Up @@ -482,7 +470,7 @@ railway up
# render.yaml is included — import repo in Render dashboard
```

### Docker (coming soon)
### Docker
```bash
docker compose up
```
Expand All @@ -500,7 +488,7 @@ docker compose up
- [ ] npm package (`npx timps start`)
- [ ] VS Code extension (Tools 9+10 in-editor)
- [ ] Slack integration (Tool 15 auto-extracts commitments)
- [ ] Docker Compose one-command setup
- [x] Docker Compose one-command setup
- [ ] TIMPS Team — shared engineering team memory
- [ ] TIMPS Guard — security pattern prevention
- [ ] TIMPS Docs — automated documentation
Expand All @@ -513,7 +501,7 @@ Pull requests welcome!

- [x] TUI (v1.0)
- [x] 17 intelligence tools (v2.0)
- [ ] Docker Compose setup
- [x] Docker Compose setup
- [ ] VS Code extension
- [ ] Additional LLM providers
- [ ] Performance optimizations
Expand Down
10 changes: 5 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ services:
container_name: timps-postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: sandeep_ai
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
POSTGRES_DB: ${POSTGRES_DB:-sandeep_ai}
ports:
- '5432:5432'
volumes:
Expand Down Expand Up @@ -48,8 +48,8 @@ services:
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: sandeep_ai
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
QDRANT_URL: http://qdrant:6333
DEFAULT_MODEL_PROVIDER: ${DEFAULT_MODEL_PROVIDER:-ollama}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"gemini",
"ollama"
],
"author": "",
"author": "Sandeeprdy1729",
"license": "MIT",
"dependencies": {
"@qdrant/js-client-rest": "^1.9.0",
Expand Down
31 changes: 16 additions & 15 deletions sandeep-ai/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { memoryIndex } from '../memory/memoryIndex';
import { query, execute } from '../db/postgres';
import { ContradictionTool } from '../tools/contradictionTool';
import { positionStore } from '../tools/positionStore';
import { logger } from '../logger';

const router = Router();
const contradictionTool = new ContradictionTool();
Expand All @@ -27,7 +28,7 @@ async function ensureUser(userId: number, username?: string): Promise<void> {
}
} catch (err) {
// Non-fatal: log and continue — agent can still run without DB user row
console.warn('[ensureUser] Could not upsert user:', err);
logger.warn('[ensureUser] Could not upsert user:', err);
}
}

Expand Down Expand Up @@ -89,7 +90,7 @@ router.post('/chat', async (req: Request, res: Response) => {
planExecuted: response.planExecuted || false,
});
} catch (error: any) {
console.error('Chat error:', error);
logger.error('Chat error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -109,7 +110,7 @@ router.get('/memory/:userId', async (req: Request, res: Response) => {
projects: context.projects,
});
} catch (error: any) {
console.error('Memory retrieval error:', error);
logger.error('Memory retrieval error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -127,7 +128,7 @@ router.get('/goals/:userId', async (req: Request, res: Response) => {
);
res.json({ goals });
} catch (error: any) {
console.error('Goals retrieval error:', error);
logger.error('Goals retrieval error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -147,7 +148,7 @@ router.post('/goals/:userId', async (req: Request, res: Response) => {
);
res.json({ goal });
} catch (error: any) {
console.error('Goal creation error:', error);
logger.error('Goal creation error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -166,7 +167,7 @@ router.put('/goals/:goalId', async (req: Request, res: Response) => {
);
res.json({ success: true });
} catch (error: any) {
console.error('Goal update error:', error);
logger.error('Goal update error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -184,7 +185,7 @@ router.get('/preferences/:userId', async (req: Request, res: Response) => {
);
res.json({ preferences });
} catch (error: any) {
console.error('Preferences retrieval error:', error);
logger.error('Preferences retrieval error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -201,7 +202,7 @@ router.post('/preferences/:userId', async (req: Request, res: Response) => {
const preference = await memoryIndex.storePreference(userId, key, value, category);
res.json({ preference });
} catch (error: any) {
console.error('Preference creation error:', error);
logger.error('Preference creation error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -219,7 +220,7 @@ router.get('/projects/:userId', async (req: Request, res: Response) => {
);
res.json({ projects });
} catch (error: any) {
console.error('Projects retrieval error:', error);
logger.error('Projects retrieval error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -239,7 +240,7 @@ router.post('/conversations/:userId', async (req: Request, res: Response) => {
);
res.json({ conversation: conversation[0] });
} catch (error: any) {
console.error('Conversation creation error:', error);
logger.error('Conversation creation error:', error);
res.status(500).json({ error: error.message });
}
});
Expand Down Expand Up @@ -284,7 +285,7 @@ router.post('/contradiction/check', async (req: Request, res: Response) => {
}
res.json(result);
} catch (error: any) {
console.error('Contradiction check error:', error);
logger.error('Contradiction check error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -300,7 +301,7 @@ router.get('/positions/:userId', async (req: Request, res: Response) => {
const positions = await positionStore.getUserPositions(userId, projectId);
res.json({ positions, total: positions.length });
} catch (error: any) {
console.error('Positions list error:', error);
logger.error('Positions list error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -322,7 +323,7 @@ router.post('/positions/:userId', async (req: Request, res: Response) => {
});
res.json(JSON.parse(raw));
} catch (error: any) {
console.error('Position store error:', error);
logger.error('Position store error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -342,7 +343,7 @@ router.delete('/positions/:userId/:positionId', async (req: Request, res: Respon
});
res.json(JSON.parse(raw));
} catch (error: any) {
console.error('Position delete error:', error);
logger.error('Position delete error:', error);
res.status(500).json({ error: error.message });
}
});
Expand All @@ -357,7 +358,7 @@ router.get('/contradiction/history/:positionId', async (req: Request, res: Respo
const history = await positionStore.getContradictionHistory(positionId);
res.json({ history, total: history.length });
} catch (error: any) {
console.error('Contradiction history error:', error);
logger.error('Contradiction history error:', error);
res.status(500).json({ error: error.message });
}
});
Expand Down
48 changes: 38 additions & 10 deletions sandeep-ai/api/server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import express, { Express } from 'express';
import express, { Express, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import path from 'path';
import fs from 'fs';
import { timingSafeEqual } from 'crypto';
import routes from './routes';
import { config } from '../config/env';
import { initDatabase } from '../db/postgres';
import { initVectorStore } from '../db/vector';
import { initGateWeaveTables } from '../db/gateWeaveDb';
import { positionStore } from '../tools/positionStore';
import { initToolsTables } from '../tools/toolsDb';
import { logger } from '../logger';

export function createApp(): Express {
const app = express();
Expand All @@ -17,9 +20,33 @@ export function createApp(): Express {
app.use(express.urlencoded({ extended: true }));

app.use((req, _res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
logger.info(`${req.method} ${req.path}`);
next();
});

// Optional API key authentication — enabled when API_KEY env var is set.
// The /api/health endpoint is always public so monitoring tools work without auth.
if (config.apiKey) {
const expectedKey = Buffer.from(config.apiKey);
app.use('/api', (req: Request, res: Response, next: NextFunction) => {
if (req.path === '/health') return next();
const provided = req.headers['x-api-key'];
if (typeof provided !== 'string') {
res.status(401).json({ error: 'Unauthorized — valid X-API-Key header required' });
return;
}
const providedKey = Buffer.from(provided);
const valid =
providedKey.length === expectedKey.length &&
timingSafeEqual(providedKey, expectedKey);
if (!valid) {
res.status(401).json({ error: 'Unauthorized — valid X-API-Key header required' });
return;
}
next();
});
logger.info('API key authentication enabled');
}

// Serve static files — resolve correctly for both ts-node and compiled dist
// ts-node: __dirname = sandeep-ai/api → public is at sandeep-ai/public
Expand All @@ -31,7 +58,7 @@ export function createApp(): Express {
path.join(process.cwd(), 'public'), // cwd-relative fallback
];
const publicPath = candidates.find(p => fs.existsSync(p)) || candidates[0];
console.log(`Serving static files from: ${publicPath}`);
logger.info(`Serving static files from: ${publicPath}`);
app.use(express.static(publicPath));

// Explicit HTML fallbacks so direct URL access works
Expand All @@ -46,7 +73,7 @@ export function createApp(): Express {
});

app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error('Unhandled error:', err);
logger.error('Unhandled error:', err);
res.status(500).json({ error: 'Internal server error' });
});

Expand All @@ -59,21 +86,22 @@ export async function startServer(): Promise<void> {
try {
await initDatabase();
await initToolsTables();
console.log('PostgreSQL initialized (core + all 17 tool tables)');
await initGateWeaveTables();
logger.info('PostgreSQL initialized (core + all 17 tool tables + GateWeave)');
} catch (error) {
console.warn('PostgreSQL initialization failed, continuing without DB:', error);
logger.warn('PostgreSQL initialization failed, continuing without DB:', error);
}

try {
await initVectorStore();
await positionStore.initPositionsCollection();
console.log('Qdrant vector store initialized (memories + positions)');
logger.info('Qdrant vector store initialized (memories + positions)');
} catch (error) {
console.warn('Qdrant initialization failed, continuing without vector store:', error);
logger.warn('Qdrant initialization failed, continuing without vector store:', error);
}

app.listen(config.port, () => {
console.log(`
logger.info(`
╔═══════════════════════════════════════════════════════════╗
║ ║
║ TIMPs Server ║
Expand All @@ -99,5 +127,5 @@ export async function startServer(): Promise<void> {
}

if (require.main === module) {
startServer().catch(console.error);
startServer().catch((err) => logger.error('Failed to start server:', err));
}
Loading
Loading