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
778 changes: 710 additions & 68 deletions backend/bin/cli.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@fastify/static": "^6.12.0",
"@lancedb/lancedb": "^0.23.0",
"better-sqlite3": "^11.10.0",
"commander": "^14.0.3",
"diff": "^8.0.3",
"drizzle-orm": "^0.30.0",
"fast-glob": "^3.3.3",
Expand Down
119 changes: 119 additions & 0 deletions cli/notecode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env node

/**
* NoteCode CLI
* Command-line interface for managing tasks, sessions, projects, and agents
*
* Usage:
* notecode Start the NoteCode server (default)
* notecode serve [-p <port>] Start the NoteCode server
* notecode task list [--status <s>] List tasks
* notecode task get <id> [--json] Get task details
* notecode task create --title "..." Create a task
* notecode task update <id> [options] Update a task
* notecode session list [--task-id <id>] List sessions
* notecode session status <id> Get session details
* notecode approval list [--session <id>] List pending approvals
* notecode approval get <id> Get approval details
* notecode approval approve <id> Approve a request
* notecode approval reject <id> -r Reject a request (reason required)
* notecode watch [--json] Real-time activity monitoring
* notecode status [--json] Show system status summary
* notecode project list [--favorite] List all projects
* notecode project get <id> [--json] Get project details
* notecode project switch <id> Switch active project
* notecode project current Show current active project
* notecode agent list [--project <id>] List discovered agents
* notecode agent get <name> Get agent details
* notecode agent skills List available skills
* notecode agent spawn --task <id> Spawn agent (experimental)
*/

import { Command } from 'commander';
import { createTaskCommands } from './src/commands/task.js';
import { createSessionCommands } from './src/commands/session.js';
import { createApprovalCommands } from './src/commands/approval.js';
import { createWatchCommand } from './src/commands/watch.js';
import { createStatusCommand } from './src/commands/status.js';
import { createProjectCommands } from './src/commands/project.js';
import { createAgentCommands } from './src/commands/agent.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { spawn } from 'child_process';

// Get version from root package.json
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

let version = '0.1.0';
try {
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
version = pkg.version;
} catch {
// Ignore
}

/**
* Start the NoteCode server using the existing backend CLI
*/
function startServer(args = []) {
const serverCli = join(__dirname, '../backend/bin/cli.js');
const child = spawn('node', [serverCli, ...args], {
stdio: 'inherit',
env: process.env,
});

child.on('exit', (code) => {
process.exit(code ?? 0);
});
}

// Check if we should start the server (no subcommand, or server-related flags)
const args = process.argv.slice(2);
const serverFlags = ['-p', '--port', '--no-browser'];
const subcommands = ['task', 'session', 'approval', 'watch', 'status', 'serve', 'help', '--help', '-h', '--version', '-V'];

// If no args, or only server flags, start the server
const hasSubcommand = args.some(arg => subcommands.includes(arg));
const hasOnlyServerFlags = args.length > 0 && args.every(arg =>
serverFlags.includes(arg) ||
(args.indexOf(arg) > 0 && serverFlags.includes(args[args.indexOf(arg) - 1]))
);

if (args.length === 0 || (hasOnlyServerFlags && !hasSubcommand)) {
startServer(args);
} else {
// Run management CLI
const program = new Command();

program
.name('notecode')
.description('NoteCode CLI - AI Coding Task Management')
.version(version);

// Server command (explicit)
program
.command('serve')
.description('Start the NoteCode server')
.option('-p, --port <port>', 'Server port')
.option('--no-browser', 'Do not open browser automatically')
.action((opts) => {
const serverArgs = [];
if (opts.port) serverArgs.push('-p', opts.port);
if (opts.browser === false) serverArgs.push('--no-browser');
startServer(serverArgs);
});

// Register management subcommands
program.addCommand(createTaskCommands());
program.addCommand(createSessionCommands());
program.addCommand(createApprovalCommands());
program.addCommand(createWatchCommand());
program.addCommand(createStatusCommand());
program.addCommand(createProjectCommands());
program.addCommand(createAgentCommands());

// Parse and execute
program.parse();
}
3 changes: 3 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
152 changes: 152 additions & 0 deletions cli/src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* NoteCode CLI - API Client
* Communicates with the NoteCode REST API
*/

const API_BASE = process.env.NOTECODE_API_URL || 'http://localhost:41920';

/**
* Make an API request
* @param {string} method - HTTP method
* @param {string} path - API path (e.g., '/api/tasks')
* @param {object} body - Request body (for POST/PATCH)
* @returns {Promise<object>} - API response
*/
async function request(method, path, body = null) {
const url = `${API_BASE}${path}`;

const options = {
method,
headers: {
'Content-Type': 'application/json',
},
};

if (body) {
options.body = JSON.stringify(body);
}

try {
const response = await fetch(url, options);
const data = await response.json();

if (!response.ok) {
const error = new Error(data.error || `HTTP ${response.status}`);
error.status = response.status;
error.data = data;
throw error;
}

return data;
} catch (err) {
if (err.cause?.code === 'ECONNREFUSED') {
throw new Error('Cannot connect to NoteCode server. Is it running on http://localhost:41920?');
}
throw err;
}
}

// Task API
export async function listTasks(filters = {}) {
const params = new URLSearchParams();
if (filters.status) params.set('status', filters.status);
if (filters.priority) params.set('priority', filters.priority);
if (filters.projectId) params.set('projectId', filters.projectId);
if (filters.agentId) params.set('agentId', filters.agentId);
if (filters.search) params.set('search', filters.search);

const query = params.toString();
return request('GET', `/api/tasks${query ? '?' + query : ''}`);
}

export async function getTask(id) {
return request('GET', `/api/tasks/${id}`);
}

export async function createTask(data) {
return request('POST', '/api/tasks', data);
}

export async function updateTask(id, data) {
return request('PATCH', `/api/tasks/${id}`, data);
}

// Session API
export async function listSessions(filters = {}) {
const params = new URLSearchParams();
if (filters.taskId) params.set('taskId', filters.taskId);
if (filters.limit) params.set('limit', filters.limit.toString());

const query = params.toString();
return request('GET', `/api/sessions${query ? '?' + query : ''}`);
}

export async function getSession(id) {
return request('GET', `/api/sessions/${id}`);
}

export async function listRunningSessions() {
return request('GET', '/api/sessions/running');
}

// Approval API
export async function listPendingApprovals() {
return request('GET', '/api/approvals/pending');
}

export async function getApproval(id) {
return request('GET', `/api/approvals/${id}`);
}

export async function approveApproval(id, message) {
return request('POST', `/api/approvals/${id}/approve`, { decidedBy: message || 'cli' });
}

export async function rejectApproval(id, reason) {
return request('POST', `/api/approvals/${id}/reject`, { decidedBy: reason || 'cli' });
}

export async function listApprovalsBySession(sessionId) {
return request('GET', `/api/approvals/session/${sessionId}`);
}

export async function getApprovalStatus(id) {
return request('GET', `/api/approvals/${id}/status`);
}

// Project API
export async function listProjects(filters = {}) {
const params = new URLSearchParams();
if (filters.search) params.set('search', filters.search);
if (filters.favorite) params.set('favorite', 'true');

const query = params.toString();
return request('GET', `/api/projects${query ? '?' + query : ''}`);
}

export async function getProject(id) {
return request('GET', `/api/projects/${id}`);
}

export async function getRecentProjects(limit = 10) {
return request('GET', `/api/projects/recent?limit=${limit}`);
}

export async function switchProject(projectId) {
return request('PATCH', '/api/settings', { currentActiveProjectId: projectId });
}

export async function getSettings() {
return request('GET', '/api/settings');
}

// Agent Discovery API (per-project agent discovery)
export async function discoverAgents(projectId, provider) {
const params = provider ? `?provider=${provider}` : '';
return request('GET', `/api/projects/${projectId}/discovery/agents${params}`);
}

export async function discoverSkills(projectId, provider) {
const params = provider ? `?provider=${provider}` : '';
return request('GET', `/api/projects/${projectId}/discovery/skills${params}`);
}
Loading