Skip to content
Merged
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
63 changes: 63 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Publish to NPM

on:
release:
types: [created]
workflow_dispatch:
inputs:
version:
description: 'Version type (patch, minor, major)'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

- name: Generate static files
run: npm run generate

- name: Update version (manual trigger)
if: github.event_name == 'workflow_dispatch'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
npm version ${{ github.event.inputs.version }} -m "chore: bump version to %s"
git push

- name: Publish to NPM
run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

- name: Create GitHub Release (manual trigger)
if: github.event_name == 'workflow_dispatch'
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.package-version.outputs.version }}
release_name: Release v${{ steps.package-version.outputs.version }}
draft: false
prerelease: false
62 changes: 62 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Source files (only include built output)
.nuxt/
node_modules/
server/
pages/
components/
composables/
layouts/
middleware/
plugins/
utils/
assets/
static/
tests/
coverage/

# Development files
.git/
.github/
.vscode/
.idea/
*.log
*.tmp
.DS_Store
.env
.env.*

# Config files (except necessary ones)
.eslintrc*
.prettierrc*
.gitignore
.gitlab-ci.yml
.travis.yml
tsconfig.json
vitest.config.ts
nuxt.config.ts
tailwind.config.js
postcss.config.js

# Documentation (keep essential docs)
docs/
*.md
!README.md
!LICENSE

# Build artifacts we don't need
.nitro/
.cache/
.output/

# Test files
*.test.ts
*.spec.ts
*.test.js
*.spec.js

# Keep only what's needed for npm package
!dist/
!cli.js
!package.json
!README.md
!LICENSE
2 changes: 2 additions & 0 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@

<!-- Global UX Systems -->
<NotificationContainer />
<HealthCheck />
</div>
</template>

<script setup lang="ts">
// Import the NotificationContainer component
import NotificationContainer from './components/ui/NotificationContainer.vue'
import HealthCheck from './components/HealthCheck.vue'

// Global state for accessibility announcements
const statusMessage = ref('')
Expand Down
2 changes: 1 addition & 1 deletion badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
187 changes: 187 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#!/usr/bin/env node

import http from 'http';
import path from 'path';
import fs from 'fs';
import url from 'url';
import { fileURLToPath } from 'url';
import open from 'open';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const PORT = process.env.PORT || 3000;
const HOST = process.env.HOST || 'localhost';

function checkPort(port, callback) {
const server = http.createServer();
server.once('error', (err) => {
if (err.code === 'EADDRINUSE') {
callback(false);
} else {
callback(true);
}
});
server.once('listening', () => {
server.close();
callback(true);
});
server.listen(port);
}

function findAvailablePort(startPort, callback) {
checkPort(startPort, (isAvailable) => {
if (isAvailable) {
callback(startPort);
} else {
findAvailablePort(startPort + 1, callback);
}
});
}

function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
const mimeTypes = {
'.html': 'text/html',
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.webp': 'image/webp',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.otf': 'font/otf'
};
return mimeTypes[ext] || 'application/octet-stream';
}

function startServer() {
const distPath = path.join(__dirname, 'dist');

if (!fs.existsSync(distPath)) {
console.error('Error: Built files not found. Please ensure the package was installed correctly.');
console.error('Expected path:', distPath);
process.exit(1);
}

console.log('🚀 Starting ContextMax...');
console.log(`📁 Serving from: ${distPath}`);

findAvailablePort(PORT, (availablePort) => {
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url);
let pathname = parsedUrl.pathname;

// Default to index.html for root path
if (pathname === '/') {
pathname = '/index.html';
}

// Security: prevent directory traversal
pathname = pathname.replace(/\.\./g, '');

let filePath = path.join(distPath, pathname);

// Try to serve the file
fs.readFile(filePath, (err, data) => {
if (err) {
// If file not found, try with .html extension
if (!pathname.endsWith('.html')) {
filePath = path.join(distPath, pathname + '.html');
fs.readFile(filePath, (err2, data2) => {
if (err2) {
// For client-side routing, serve index.html
fs.readFile(path.join(distPath, 'index.html'), (err3, data3) => {
if (err3) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data3);
}
});
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data2);
}
});
} else {
// Serve 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
}
} else {
// Success - serve the file with appropriate content type
const mimeType = getMimeType(filePath);
res.writeHead(200, { 'Content-Type': mimeType });
res.end(data);
}
});
});

server.listen(availablePort, HOST, () => {
const serverUrl = `http://${HOST}:${availablePort}`;
console.log(`\n✨ ContextMax is running at: ${serverUrl}`);
console.log('📋 Press Ctrl+C to stop the server\n');

// Open browser
open(serverUrl).catch(() => {
console.log('📌 Could not open browser automatically. Please open the URL manually.');
});
});

process.on('SIGINT', () => {
console.log('\n👋 Shutting down ContextMax...');
server.close();
process.exit(0);
});
});
}

function showHelp() {
console.log(`
ContextMax - Privacy-first context sets for LLMs

Usage:
npx contextmax [command]

Commands:
run, start Start the ContextMax server (default)
help Show this help message

Environment Variables:
PORT Server port (default: 3000)
HOST Server host (default: localhost)

Examples:
npx contextmax
npx contextmax run
PORT=8080 npx contextmax
`);
}

const command = process.argv[2];

switch (command) {
case undefined:
case 'run':
case 'start':
startServer();
break;
case 'help':
case '--help':
case '-h':
showHelp();
break;
default:
console.error(`Unknown command: ${command}`);
showHelp();
process.exit(1);
}
Loading