feat: typescript migration (#40)

* chore: initialize TypeScript configuration and build setup

- Add tsconfig.json for root and mcp-server with strict type checking
- Install typescript and @types/node as devDependencies
- Add npm build script for TypeScript compilation
- Update main entrypoint to compiled dist/shannon.js
- Update Dockerfile to build TypeScript before running
- Configure output directory and module resolution for Node.js

* refactor: migrate codebase from JavaScript to TypeScript

- Convert all 37 JavaScript files to TypeScript (.js -> .ts)
- Add type definitions in src/types/ for agents, config, errors, session
- Update mcp-server with proper TypeScript types
- Move entry point from shannon.mjs to src/shannon.ts
- Update tsconfig.json with rootDir: "./src" for cleaner dist output
- Update Dockerfile to build TypeScript before runtime
- Update package.json paths to use compiled dist/shannon.js

No runtime behavior changes - pure type safety migration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update CLI references from ./shannon.mjs to shannon

- Update help text in src/cli/ui.ts
- Update usage examples in src/cli/command-handler.ts
- Update setup message in src/shannon.ts
- Update CLAUDE.md documentation with TypeScript file structure
- Replace all ./shannon.mjs references with shannon command

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove unnecessary eslint-disable comments

ESLint is not configured in this project, making these comments redundant.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ezl-keygraph
2026-01-08 00:18:25 +05:30
committed by GitHub
parent b4d2c35b91
commit dd18f4629b
55 changed files with 3213 additions and 2057 deletions
@@ -15,7 +15,7 @@
import { tool } from '@anthropic-ai/claude-agent-sdk';
import { createHmac } from 'crypto';
import { z } from 'zod';
import { createToolResult } from '../types/tool-responses.js';
import { createToolResult, type ToolResult, type GenerateTotpResponse } from '../types/tool-responses.js';
import { base32Decode, validateTotpSecret } from '../validation/totp-validator.js';
import { createCryptoError, createGenericError } from '../utils/error-formatter.js';
@@ -30,16 +30,13 @@ export const GenerateTotpInputSchema = z.object({
.describe('Base32-encoded TOTP secret'),
});
export type GenerateTotpInput = z.infer<typeof GenerateTotpInputSchema>;
/**
* Generate HOTP code (RFC 4226)
* Ported from generate-totp-standalone.mjs (lines 74-99)
*
* @param {string} secret - Base32-encoded secret
* @param {number} counter - Counter value
* @param {number} [digits=6] - Number of digits in OTP
* @returns {string} OTP code
*/
function generateHOTP(secret, counter, digits = 6) {
function generateHOTP(secret: string, counter: number, digits: number = 6): string {
const key = base32Decode(secret);
// Convert counter to 8-byte buffer (big-endian)
@@ -52,12 +49,12 @@ function generateHOTP(secret, counter, digits = 6) {
const hash = hmac.digest();
// Dynamic truncation
const offset = hash[hash.length - 1] & 0x0f;
const offset = hash[hash.length - 1]! & 0x0f;
const code =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
((hash[offset]! & 0x7f) << 24) |
((hash[offset + 1]! & 0xff) << 16) |
((hash[offset + 2]! & 0xff) << 8) |
(hash[offset + 3]! & 0xff);
// Generate digits
const otp = (code % Math.pow(10, digits)).toString().padStart(digits, '0');
@@ -67,13 +64,8 @@ function generateHOTP(secret, counter, digits = 6) {
/**
* Generate TOTP code (RFC 6238)
* Ported from generate-totp-standalone.mjs (lines 101-106)
*
* @param {string} secret - Base32-encoded secret
* @param {number} [timeStep=30] - Time step in seconds
* @param {number} [digits=6] - Number of digits in OTP
* @returns {string} OTP code
*/
function generateTOTP(secret, timeStep = 30, digits = 6) {
function generateTOTP(secret: string, timeStep: number = 30, digits: number = 6): string {
const currentTime = Math.floor(Date.now() / 1000);
const counter = Math.floor(currentTime / timeStep);
return generateHOTP(secret, counter, digits);
@@ -81,23 +73,16 @@ function generateTOTP(secret, timeStep = 30, digits = 6) {
/**
* Get seconds until TOTP code expires
*
* @param {number} [timeStep=30] - Time step in seconds
* @returns {number} Seconds until expiration
*/
function getSecondsUntilExpiration(timeStep = 30) {
function getSecondsUntilExpiration(timeStep: number = 30): number {
const currentTime = Math.floor(Date.now() / 1000);
return timeStep - (currentTime % timeStep);
}
/**
* generate_totp tool implementation
*
* @param {Object} args
* @param {string} args.secret - Base32-encoded TOTP secret
* @returns {Promise<Object>} Tool result
*/
export async function generateTotp(args) {
export async function generateTotp(args: GenerateTotpInput): Promise<ToolResult> {
try {
const { secret } = args;
@@ -110,7 +95,7 @@ export async function generateTotp(args) {
const timestamp = new Date().toISOString();
// Success response
const successResponse = {
const successResponse: GenerateTotpResponse = {
status: 'success',
message: 'TOTP code generated successfully',
totpCode,
@@ -14,7 +14,7 @@
import { tool } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';
import { DeliverableType, DELIVERABLE_FILENAMES, isQueueType } from '../types/deliverables.js';
import { createToolResult } from '../types/tool-responses.js';
import { createToolResult, type ToolResult, type SaveDeliverableResponse } from '../types/tool-responses.js';
import { validateQueueJson } from '../validation/queue-validator.js';
import { saveDeliverableFile } from '../utils/file-operations.js';
import { createValidationError, createGenericError } from '../utils/error-formatter.js';
@@ -27,15 +27,12 @@ export const SaveDeliverableInputSchema = z.object({
content: z.string().min(1).describe('File content (markdown for analysis/evidence, JSON for queues)'),
});
export type SaveDeliverableInput = z.infer<typeof SaveDeliverableInputSchema>;
/**
* save_deliverable tool implementation
*
* @param {Object} args
* @param {string} args.deliverable_type - Type of deliverable to save
* @param {string} args.content - File content
* @returns {Promise<Object>} Tool result
*/
export async function saveDeliverable(args) {
export async function saveDeliverable(args: SaveDeliverableInput): Promise<ToolResult> {
try {
const { deliverable_type, content } = args;
@@ -44,7 +41,7 @@ export async function saveDeliverable(args) {
const queueValidation = validateQueueJson(content);
if (!queueValidation.valid) {
const errorResponse = createValidationError(
queueValidation.message,
queueValidation.message ?? 'Invalid queue JSON',
true,
{
deliverableType: deliverable_type,
@@ -60,7 +57,7 @@ export async function saveDeliverable(args) {
const filepath = saveDeliverableFile(filename, content);
// Success response
const successResponse = {
const successResponse: SaveDeliverableResponse = {
status: 'success',
message: `Deliverable saved successfully: ${filename}`,
filepath,