chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,228 @@
import type {
GreenhouseGetApplicationParams,
GreenhouseGetApplicationResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseGetApplicationTool: ToolConfig<
GreenhouseGetApplicationParams,
GreenhouseGetApplicationResponse
> = {
id: 'greenhouse_get_application',
name: 'Greenhouse Get Application',
description:
'Retrieves a specific application by ID with full details including source, stage, answers, and attachments',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
applicationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the application to retrieve',
},
},
request: {
url: (params: GreenhouseGetApplicationParams) =>
`https://harvest.greenhouse.io/v1/applications/${params.applicationId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetApplicationParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseGetApplicationResponse> => {
if (!response.ok) {
return {
success: false,
output: {
id: 0,
candidate_id: 0,
prospect: false,
status: null,
applied_at: null,
rejected_at: null,
last_activity_at: null,
location: null,
source: null,
credited_to: null,
recruiter: null,
coordinator: null,
current_stage: null,
rejection_reason: null,
jobs: [],
job_post_id: null,
answers: [],
attachments: [],
custom_fields: {},
},
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const a = await response.json()
return {
success: true,
output: {
id: a.id ?? 0,
candidate_id: a.candidate_id ?? 0,
prospect: a.prospect ?? false,
status: a.status ?? null,
applied_at: a.applied_at ?? null,
rejected_at: a.rejected_at ?? null,
last_activity_at: a.last_activity_at ?? null,
location: a.location ?? null,
source: a.source ?? null,
credited_to: a.credited_to ?? null,
recruiter: a.recruiter ?? null,
coordinator: a.coordinator ?? null,
current_stage: a.current_stage ?? null,
rejection_reason: a.rejection_reason ?? null,
jobs: a.jobs ?? [],
job_post_id: a.job_post_id ?? null,
answers: a.answers ?? [],
attachments: (a.attachments ?? []).map((att: Record<string, unknown>) => ({
filename: att.filename ?? '',
url: att.url ?? '',
type: att.type ?? '',
created_at: (att.created_at as string) ?? null,
})),
custom_fields: a.custom_fields ?? {},
},
}
},
outputs: {
id: { type: 'number', description: 'Application ID' },
candidate_id: { type: 'number', description: 'Associated candidate ID' },
prospect: { type: 'boolean', description: 'Whether this is a prospect application' },
status: { type: 'string', description: 'Status (active, converted, hired, rejected)' },
applied_at: { type: 'string', description: 'Application date (ISO 8601)' },
rejected_at: { type: 'string', description: 'Rejection date (ISO 8601)', optional: true },
last_activity_at: { type: 'string', description: 'Last activity date (ISO 8601)' },
location: {
type: 'object',
description: 'Candidate location',
optional: true,
properties: {
address: { type: 'string', description: 'Location address', optional: true },
},
},
source: {
type: 'object',
description: 'Application source',
optional: true,
properties: {
id: { type: 'number', description: 'Source ID' },
public_name: { type: 'string', description: 'Source name' },
},
},
credited_to: {
type: 'object',
description: 'User credited for the application',
optional: true,
properties: {
id: { type: 'number', description: 'User ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
name: { type: 'string', description: 'Full name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
recruiter: {
type: 'object',
description: 'Assigned recruiter',
optional: true,
properties: {
id: { type: 'number', description: 'User ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
name: { type: 'string', description: 'Full name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
coordinator: {
type: 'object',
description: 'Assigned coordinator',
optional: true,
properties: {
id: { type: 'number', description: 'User ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
name: { type: 'string', description: 'Full name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
current_stage: {
type: 'object',
description: 'Current interview stage (null when hired)',
optional: true,
properties: {
id: { type: 'number', description: 'Stage ID' },
name: { type: 'string', description: 'Stage name' },
},
},
rejection_reason: {
type: 'object',
description: 'Rejection reason',
optional: true,
properties: {
id: { type: 'number', description: 'Rejection reason ID' },
name: { type: 'string', description: 'Rejection reason name' },
type: {
type: 'object',
description: 'Rejection reason type',
properties: {
id: { type: 'number', description: 'Type ID' },
name: { type: 'string', description: 'Type name' },
},
},
},
},
jobs: {
type: 'array',
description: 'Associated jobs',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Job ID' },
name: { type: 'string', description: 'Job name' },
},
},
},
job_post_id: { type: 'number', description: 'Job post ID', optional: true },
answers: {
type: 'array',
description: 'Application question answers',
items: {
type: 'object',
properties: {
question: { type: 'string', description: 'Question text' },
answer: { type: 'string', description: 'Answer text' },
},
},
},
attachments: {
type: 'array',
description: 'File attachments (URLs expire after 7 days)',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File name' },
url: { type: 'string', description: 'Download URL (expires after 7 days)' },
type: { type: 'string', description: 'Type (resume, cover_letter, offer_packet, other)' },
created_at: { type: 'string', description: 'Upload timestamp', optional: true },
},
},
},
custom_fields: { type: 'object', description: 'Custom field values' },
},
}
+267
View File
@@ -0,0 +1,267 @@
import type {
GreenhouseGetCandidateParams,
GreenhouseGetCandidateResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseGetCandidateTool: ToolConfig<
GreenhouseGetCandidateParams,
GreenhouseGetCandidateResponse
> = {
id: 'greenhouse_get_candidate',
name: 'Greenhouse Get Candidate',
description:
'Retrieves a specific candidate by ID with full details including contact info, education, and employment history',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
candidateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the candidate to retrieve',
},
},
request: {
url: (params: GreenhouseGetCandidateParams) =>
`https://harvest.greenhouse.io/v1/candidates/${params.candidateId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetCandidateParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseGetCandidateResponse> => {
if (!response.ok) {
return {
success: false,
output: {
id: 0,
first_name: null,
last_name: null,
company: null,
title: null,
is_private: false,
can_email: false,
created_at: null,
updated_at: null,
last_activity: null,
email_addresses: [],
phone_numbers: [],
addresses: [],
website_addresses: [],
social_media_addresses: [],
tags: [],
application_ids: [],
recruiter: null,
coordinator: null,
attachments: [],
educations: [],
employments: [],
custom_fields: {},
},
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const c = await response.json()
return {
success: true,
output: {
id: c.id ?? 0,
first_name: c.first_name ?? null,
last_name: c.last_name ?? null,
company: c.company ?? null,
title: c.title ?? null,
is_private: c.is_private ?? false,
can_email: c.can_email ?? false,
created_at: c.created_at ?? null,
updated_at: c.updated_at ?? null,
last_activity: c.last_activity ?? null,
email_addresses: c.email_addresses ?? [],
phone_numbers: c.phone_numbers ?? [],
addresses: c.addresses ?? [],
website_addresses: c.website_addresses ?? [],
social_media_addresses: c.social_media_addresses ?? [],
tags: c.tags ?? [],
application_ids: c.application_ids ?? [],
recruiter: c.recruiter ?? null,
coordinator: c.coordinator ?? null,
attachments: (c.attachments ?? []).map((a: Record<string, unknown>) => ({
filename: a.filename ?? '',
url: a.url ?? '',
type: a.type ?? '',
created_at: (a.created_at as string) ?? null,
})),
educations: (c.educations ?? []).map((e: Record<string, unknown>) => ({
id: e.id ?? 0,
school_name: e.school_name ?? null,
degree: e.degree ?? null,
discipline: e.discipline ?? null,
start_date: (e.start_date as string) ?? null,
end_date: (e.end_date as string) ?? null,
})),
employments: (c.employments ?? []).map((e: Record<string, unknown>) => ({
id: e.id ?? 0,
company_name: e.company_name ?? null,
title: e.title ?? null,
start_date: (e.start_date as string) ?? null,
end_date: (e.end_date as string) ?? null,
})),
custom_fields: c.custom_fields ?? {},
},
}
},
outputs: {
id: { type: 'number', description: 'Candidate ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
company: { type: 'string', description: 'Current employer', optional: true },
title: { type: 'string', description: 'Current job title', optional: true },
is_private: { type: 'boolean', description: 'Whether candidate is private' },
can_email: { type: 'boolean', description: 'Whether candidate can be emailed' },
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
last_activity: {
type: 'string',
description: 'Last activity timestamp (ISO 8601)',
optional: true,
},
email_addresses: {
type: 'array',
description: 'Email addresses',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Type (personal, work, other)' },
},
},
},
phone_numbers: {
type: 'array',
description: 'Phone numbers',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Phone number' },
type: { type: 'string', description: 'Type (home, work, mobile, skype, other)' },
},
},
},
addresses: {
type: 'array',
description: 'Addresses',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Address' },
type: { type: 'string', description: 'Type (home, work, other)' },
},
},
},
website_addresses: {
type: 'array',
description: 'Website addresses',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'URL' },
type: { type: 'string', description: 'Type (personal, company, portfolio, blog, other)' },
},
},
},
social_media_addresses: {
type: 'array',
description: 'Social media profiles',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'URL or handle' },
},
},
},
tags: { type: 'array', description: 'Tags', items: { type: 'string', description: 'Tag' } },
application_ids: {
type: 'array',
description: 'Associated application IDs',
items: { type: 'number', description: 'Application ID' },
},
recruiter: {
type: 'object',
description: 'Assigned recruiter',
optional: true,
properties: {
id: { type: 'number', description: 'User ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
name: { type: 'string', description: 'Full name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
coordinator: {
type: 'object',
description: 'Assigned coordinator',
optional: true,
properties: {
id: { type: 'number', description: 'User ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
name: { type: 'string', description: 'Full name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
attachments: {
type: 'array',
description: 'File attachments (URLs expire after 7 days)',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File name' },
url: { type: 'string', description: 'Download URL (expires after 7 days)' },
type: { type: 'string', description: 'Type (resume, cover_letter, offer_packet, other)' },
created_at: { type: 'string', description: 'Upload timestamp', optional: true },
},
},
},
educations: {
type: 'array',
description: 'Education history',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Education record ID' },
school_name: { type: 'string', description: 'School name', optional: true },
degree: { type: 'string', description: 'Degree type', optional: true },
discipline: { type: 'string', description: 'Field of study', optional: true },
start_date: { type: 'string', description: 'Start date (ISO 8601)', optional: true },
end_date: { type: 'string', description: 'End date (ISO 8601)', optional: true },
},
},
},
employments: {
type: 'array',
description: 'Employment history',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Employment record ID' },
company_name: { type: 'string', description: 'Company name', optional: true },
title: { type: 'string', description: 'Job title', optional: true },
start_date: { type: 'string', description: 'Start date (ISO 8601)', optional: true },
end_date: { type: 'string', description: 'End date (ISO 8601)', optional: true },
},
},
},
custom_fields: { type: 'object', description: 'Custom field values' },
},
}
+185
View File
@@ -0,0 +1,185 @@
import type { GreenhouseGetJobParams, GreenhouseGetJobResponse } from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseGetJobTool: ToolConfig<GreenhouseGetJobParams, GreenhouseGetJobResponse> = {
id: 'greenhouse_get_job',
name: 'Greenhouse Get Job',
description:
'Retrieves a specific job by ID with full details including hiring team, openings, and custom fields',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
jobId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the job to retrieve',
},
},
request: {
url: (params: GreenhouseGetJobParams) =>
`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetJobParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseGetJobResponse> => {
if (!response.ok) {
return {
success: false,
output: {
id: 0,
name: null,
requisition_id: null,
status: null,
confidential: false,
created_at: null,
opened_at: null,
closed_at: null,
updated_at: null,
is_template: null,
notes: null,
departments: [],
offices: [],
hiring_team: { hiring_managers: [], recruiters: [], coordinators: [], sourcers: [] },
openings: [],
custom_fields: {},
},
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const j = await response.json()
return {
success: true,
output: {
id: j.id ?? 0,
name: j.name ?? null,
requisition_id: j.requisition_id ?? null,
status: j.status ?? null,
confidential: j.confidential ?? false,
created_at: j.created_at ?? null,
opened_at: j.opened_at ?? null,
closed_at: j.closed_at ?? null,
updated_at: j.updated_at ?? null,
is_template: j.is_template ?? null,
notes: j.notes ?? null,
departments: (j.departments ?? []).map((d: Record<string, unknown>) => ({
id: d.id ?? 0,
name: (d.name as string) ?? '',
parent_id: (d.parent_id as number) ?? null,
})),
offices: (j.offices ?? []).map((o: Record<string, unknown>) => ({
id: o.id ?? 0,
name: (o.name as string) ?? '',
location: { name: ((o.location as Record<string, unknown>)?.name as string) ?? null },
})),
hiring_team: {
hiring_managers: j.hiring_team?.hiring_managers ?? [],
recruiters: j.hiring_team?.recruiters ?? [],
coordinators: j.hiring_team?.coordinators ?? [],
sourcers: j.hiring_team?.sourcers ?? [],
},
openings: (j.openings ?? []).map((o: Record<string, unknown>) => ({
id: o.id ?? 0,
opening_id: (o.opening_id as string) ?? null,
status: (o.status as string) ?? 'open',
opened_at: (o.opened_at as string) ?? null,
closed_at: (o.closed_at as string) ?? null,
application_id: (o.application_id as number) ?? null,
close_reason: (o.close_reason as { id: number; name: string }) ?? null,
})),
custom_fields: j.custom_fields ?? {},
},
}
},
outputs: {
id: { type: 'number', description: 'Job ID' },
name: { type: 'string', description: 'Job title' },
requisition_id: { type: 'string', description: 'External requisition ID', optional: true },
status: { type: 'string', description: 'Job status (open, closed, draft)' },
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
opened_at: { type: 'string', description: 'Date job was opened (ISO 8601)', optional: true },
closed_at: { type: 'string', description: 'Date job was closed (ISO 8601)', optional: true },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
is_template: { type: 'boolean', description: 'Whether this is a job template', optional: true },
notes: { type: 'string', description: 'Hiring plan notes (may contain HTML)', optional: true },
departments: {
type: 'array',
description: 'Associated departments',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Department ID' },
name: { type: 'string', description: 'Department name' },
parent_id: { type: 'number', description: 'Parent department ID', optional: true },
},
},
},
offices: {
type: 'array',
description: 'Associated offices',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Office ID' },
name: { type: 'string', description: 'Office name' },
location: {
type: 'object',
description: 'Office location',
properties: {
name: { type: 'string', description: 'Location name', optional: true },
},
},
},
},
},
hiring_team: {
type: 'object',
description: 'Hiring team members',
properties: {
hiring_managers: { type: 'array', description: 'Hiring managers' },
recruiters: { type: 'array', description: 'Recruiters (includes responsible flag)' },
coordinators: { type: 'array', description: 'Coordinators (includes responsible flag)' },
sourcers: { type: 'array', description: 'Sourcers' },
},
},
openings: {
type: 'array',
description: 'Job openings/slots',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Opening internal ID' },
opening_id: { type: 'string', description: 'Custom opening identifier', optional: true },
status: { type: 'string', description: 'Opening status (open, closed)' },
opened_at: { type: 'string', description: 'Date opened (ISO 8601)', optional: true },
closed_at: { type: 'string', description: 'Date closed (ISO 8601)', optional: true },
application_id: { type: 'number', description: 'Hired application ID', optional: true },
close_reason: {
type: 'object',
description: 'Reason for closing',
optional: true,
properties: {
id: { type: 'number', description: 'Close reason ID' },
name: { type: 'string', description: 'Close reason name' },
},
},
},
},
},
custom_fields: { type: 'object', description: 'Custom field values' },
},
}
+100
View File
@@ -0,0 +1,100 @@
import type { GreenhouseGetUserParams, GreenhouseGetUserResponse } from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseGetUserTool: ToolConfig<GreenhouseGetUserParams, GreenhouseGetUserResponse> =
{
id: 'greenhouse_get_user',
name: 'Greenhouse Get User',
description: 'Retrieves a specific Greenhouse user by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the user to retrieve',
},
},
request: {
url: (params: GreenhouseGetUserParams) =>
`https://harvest.greenhouse.io/v1/users/${params.userId.trim()}`,
method: 'GET',
headers: (params: GreenhouseGetUserParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseGetUserResponse> => {
if (!response.ok) {
return {
success: false,
output: {
id: 0,
name: null,
first_name: null,
last_name: null,
primary_email_address: null,
disabled: false,
site_admin: false,
emails: [],
employee_id: null,
linked_candidate_ids: [],
created_at: null,
updated_at: null,
},
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const u = await response.json()
return {
success: true,
output: {
id: u.id ?? 0,
name: u.name ?? null,
first_name: u.first_name ?? null,
last_name: u.last_name ?? null,
primary_email_address: u.primary_email_address ?? null,
disabled: u.disabled ?? false,
site_admin: u.site_admin ?? false,
emails: u.emails ?? [],
employee_id: u.employee_id ?? null,
linked_candidate_ids: u.linked_candidate_ids ?? [],
created_at: u.created_at ?? null,
updated_at: u.updated_at ?? null,
},
}
},
outputs: {
id: { type: 'number', description: 'User ID' },
name: { type: 'string', description: 'Full name' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
primary_email_address: { type: 'string', description: 'Primary email address' },
disabled: { type: 'boolean', description: 'Whether the user is disabled' },
site_admin: { type: 'boolean', description: 'Whether the user is a site admin' },
emails: {
type: 'array',
description: 'All email addresses',
items: { type: 'string', description: 'Email address' },
},
employee_id: { type: 'string', description: 'Employee ID', optional: true },
linked_candidate_ids: {
type: 'array',
description: 'IDs of candidates linked to this user',
items: { type: 'number', description: 'Candidate ID' },
},
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
},
}
+25
View File
@@ -0,0 +1,25 @@
import { greenhouseGetApplicationTool } from '@/tools/greenhouse/get_application'
import { greenhouseGetCandidateTool } from '@/tools/greenhouse/get_candidate'
import { greenhouseGetJobTool } from '@/tools/greenhouse/get_job'
import { greenhouseGetUserTool } from '@/tools/greenhouse/get_user'
import { greenhouseListApplicationsTool } from '@/tools/greenhouse/list_applications'
import { greenhouseListCandidatesTool } from '@/tools/greenhouse/list_candidates'
import { greenhouseListDepartmentsTool } from '@/tools/greenhouse/list_departments'
import { greenhouseListJobStagesTool } from '@/tools/greenhouse/list_job_stages'
import { greenhouseListJobsTool } from '@/tools/greenhouse/list_jobs'
import { greenhouseListOfficesTool } from '@/tools/greenhouse/list_offices'
import { greenhouseListUsersTool } from '@/tools/greenhouse/list_users'
export {
greenhouseGetApplicationTool,
greenhouseGetCandidateTool,
greenhouseGetJobTool,
greenhouseGetUserTool,
greenhouseListApplicationsTool,
greenhouseListCandidatesTool,
greenhouseListDepartmentsTool,
greenhouseListJobStagesTool,
greenhouseListJobsTool,
greenhouseListOfficesTool,
greenhouseListUsersTool,
}
@@ -0,0 +1,156 @@
import type {
GreenhouseApplicationSummary,
GreenhouseListApplicationsParams,
GreenhouseListApplicationsResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListApplicationsTool: ToolConfig<
GreenhouseListApplicationsParams,
GreenhouseListApplicationsResponse
> = {
id: 'greenhouse_list_applications',
name: 'Greenhouse List Applications',
description: 'Lists applications from Greenhouse with optional filtering by job, status, or date',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
job_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter applications by job ID',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by status (active, converted, hired, rejected)',
},
created_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only applications created at or after this ISO 8601 timestamp',
},
created_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only applications created before this ISO 8601 timestamp',
},
last_activity_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only applications with activity at or after this ISO 8601 timestamp',
},
},
request: {
url: (params: GreenhouseListApplicationsParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/applications')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
if (params.job_id) url.searchParams.append('job_id', params.job_id)
if (params.status) url.searchParams.append('status', params.status)
if (params.created_after) url.searchParams.append('created_after', params.created_after)
if (params.created_before) url.searchParams.append('created_before', params.created_before)
if (params.last_activity_after)
url.searchParams.append('last_activity_after', params.last_activity_after)
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListApplicationsParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListApplicationsResponse> => {
if (!response.ok) {
return {
success: false,
output: { applications: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const applications: GreenhouseApplicationSummary[] = (Array.isArray(data) ? data : []).map(
(a: Record<string, unknown>) => ({
id: (a.id as number) ?? 0,
candidate_id: (a.candidate_id as number) ?? null,
prospect: (a.prospect as boolean) ?? false,
status: (a.status as string) ?? null,
current_stage: (a.current_stage as { id: number; name: string }) ?? null,
jobs: (a.jobs as Array<{ id: number; name: string }>) ?? [],
applied_at: (a.applied_at as string) ?? null,
rejected_at: (a.rejected_at as string) ?? null,
last_activity_at: (a.last_activity_at as string) ?? null,
})
)
return {
success: true,
output: { applications, count: applications.length },
}
},
outputs: {
applications: {
type: 'array',
description: 'List of applications',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Application ID' },
candidate_id: { type: 'number', description: 'Associated candidate ID' },
prospect: { type: 'boolean', description: 'Whether this is a prospect application' },
status: { type: 'string', description: 'Status (active, converted, hired, rejected)' },
current_stage: {
type: 'object',
description: 'Current interview stage',
optional: true,
properties: {
id: { type: 'number', description: 'Stage ID' },
name: { type: 'string', description: 'Stage name' },
},
},
jobs: {
type: 'array',
description: 'Associated jobs',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Job ID' },
name: { type: 'string', description: 'Job name' },
},
},
},
applied_at: { type: 'string', description: 'Application date (ISO 8601)' },
rejected_at: { type: 'string', description: 'Rejection date (ISO 8601)', optional: true },
last_activity_at: { type: 'string', description: 'Last activity date (ISO 8601)' },
},
},
},
count: { type: 'number', description: 'Number of applications returned' },
},
}
@@ -0,0 +1,181 @@
import type {
GreenhouseCandidateSummary,
GreenhouseListCandidatesParams,
GreenhouseListCandidatesResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListCandidatesTool: ToolConfig<
GreenhouseListCandidatesParams,
GreenhouseListCandidatesResponse
> = {
id: 'greenhouse_list_candidates',
name: 'Greenhouse List Candidates',
description: 'Lists candidates from Greenhouse with optional filtering by date, job, or email',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
created_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only candidates created at or after this ISO 8601 timestamp',
},
created_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only candidates created before this ISO 8601 timestamp',
},
updated_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only candidates updated at or after this ISO 8601 timestamp',
},
updated_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only candidates updated before this ISO 8601 timestamp',
},
job_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to candidates who applied to this job ID (excludes prospects)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to candidates with this email address',
},
candidate_ids: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated candidate IDs to retrieve (max 50)',
},
},
request: {
url: (params: GreenhouseListCandidatesParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/candidates')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
if (params.created_after) url.searchParams.append('created_after', params.created_after)
if (params.created_before) url.searchParams.append('created_before', params.created_before)
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
if (params.job_id) url.searchParams.append('job_id', params.job_id)
if (params.email) url.searchParams.append('email_address', params.email)
if (params.candidate_ids) url.searchParams.append('candidate_ids', params.candidate_ids)
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListCandidatesParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListCandidatesResponse> => {
if (!response.ok) {
return {
success: false,
output: { candidates: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const candidates: GreenhouseCandidateSummary[] = (Array.isArray(data) ? data : []).map(
(c: Record<string, unknown>) => ({
id: (c.id as number) ?? 0,
first_name: (c.first_name as string) ?? null,
last_name: (c.last_name as string) ?? null,
company: (c.company as string) ?? null,
title: (c.title as string) ?? null,
is_private: (c.is_private as boolean) ?? false,
can_email: (c.can_email as boolean) ?? false,
email_addresses: (c.email_addresses as Array<{ value: string; type: string }>) ?? [],
tags: (c.tags as string[]) ?? [],
application_ids: (c.application_ids as number[]) ?? [],
created_at: (c.created_at as string) ?? null,
updated_at: (c.updated_at as string) ?? null,
last_activity: (c.last_activity as string) ?? null,
})
)
return {
success: true,
output: { candidates, count: candidates.length },
}
},
outputs: {
candidates: {
type: 'array',
description: 'List of candidates',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Candidate ID' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
company: { type: 'string', description: 'Current employer', optional: true },
title: { type: 'string', description: 'Current job title', optional: true },
is_private: { type: 'boolean', description: 'Whether candidate is private' },
can_email: { type: 'boolean', description: 'Whether candidate can be emailed' },
email_addresses: {
type: 'array',
description: 'Email addresses',
items: {
type: 'object',
properties: {
value: { type: 'string', description: 'Email address' },
type: { type: 'string', description: 'Email type (personal, work, other)' },
},
},
},
tags: {
type: 'array',
description: 'Candidate tags',
items: { type: 'string', description: 'Tag' },
},
application_ids: {
type: 'array',
description: 'Associated application IDs',
items: { type: 'number', description: 'Application ID' },
},
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
last_activity: {
type: 'string',
description: 'Last activity timestamp (ISO 8601)',
optional: true,
},
},
},
},
count: { type: 'number', description: 'Number of candidates returned' },
},
}
@@ -0,0 +1,98 @@
import type {
GreenhouseDepartment,
GreenhouseListDepartmentsParams,
GreenhouseListDepartmentsResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListDepartmentsTool: ToolConfig<
GreenhouseListDepartmentsParams,
GreenhouseListDepartmentsResponse
> = {
id: 'greenhouse_list_departments',
name: 'Greenhouse List Departments',
description: 'Lists all departments configured in Greenhouse',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params: GreenhouseListDepartmentsParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/departments')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListDepartmentsParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListDepartmentsResponse> => {
if (!response.ok) {
return {
success: false,
output: { departments: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const departments: GreenhouseDepartment[] = (Array.isArray(data) ? data : []).map(
(d: Record<string, unknown>) => ({
id: (d.id as number) ?? 0,
name: (d.name as string) ?? null,
parent_id: (d.parent_id as number) ?? null,
child_ids: (d.child_ids as number[]) ?? [],
external_id: (d.external_id as string) ?? null,
})
)
return {
success: true,
output: { departments, count: departments.length },
}
},
outputs: {
departments: {
type: 'array',
description: 'List of departments',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Department ID' },
name: { type: 'string', description: 'Department name' },
parent_id: { type: 'number', description: 'Parent department ID', optional: true },
child_ids: {
type: 'array',
description: 'Child department IDs',
items: { type: 'number', description: 'Department ID' },
},
external_id: { type: 'string', description: 'External system ID', optional: true },
},
},
},
count: { type: 'number', description: 'Number of departments returned' },
},
}
@@ -0,0 +1,189 @@
import type {
GreenhouseJobStage,
GreenhouseJobStageInterview,
GreenhouseListJobStagesParams,
GreenhouseListJobStagesResponse,
GreenhouseUserRef,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListJobStagesTool: ToolConfig<
GreenhouseListJobStagesParams,
GreenhouseListJobStagesResponse
> = {
id: 'greenhouse_list_job_stages',
name: 'Greenhouse List Job Stages',
description: 'Lists all interview stages for a specific job in Greenhouse',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
jobId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The job ID to list stages for',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params: GreenhouseListJobStagesParams) => {
const url = new URL(`https://harvest.greenhouse.io/v1/jobs/${params.jobId.trim()}/stages`)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListJobStagesParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListJobStagesResponse> => {
if (!response.ok) {
return {
success: false,
output: { stages: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const stages: GreenhouseJobStage[] = (Array.isArray(data) ? data : []).map(
(s: Record<string, unknown>) => ({
id: (s.id as number) ?? 0,
name: (s.name as string) ?? null,
created_at: (s.created_at as string) ?? null,
updated_at: (s.updated_at as string) ?? null,
job_id: (s.job_id as number) ?? 0,
priority: (s.priority as number) ?? 0,
active: (s.active as boolean) ?? true,
interviews: (Array.isArray(s.interviews) ? s.interviews : []).map(
(i: Record<string, unknown>): GreenhouseJobStageInterview => ({
id: (i.id as number) ?? 0,
name: (i.name as string) ?? null,
schedulable: (i.schedulable as boolean) ?? false,
estimated_minutes: (i.estimated_minutes as number) ?? null,
default_interviewer_users: (Array.isArray(i.default_interviewer_users)
? i.default_interviewer_users
: []
).map(
(u: Record<string, unknown>): GreenhouseUserRef => ({
id: (u.id as number) ?? 0,
first_name: (u.first_name as string) ?? '',
last_name: (u.last_name as string) ?? '',
name: (u.name as string) ?? '',
employee_id: (u.employee_id as string) ?? null,
})
),
interview_kit: i.interview_kit
? {
id: ((i.interview_kit as Record<string, unknown>).id as number) ?? 0,
content: ((i.interview_kit as Record<string, unknown>).content as string) ?? null,
questions: (Array.isArray((i.interview_kit as Record<string, unknown>).questions)
? (i.interview_kit as Record<string, unknown>).questions
: []) as Array<{ id: number; question: string }>,
}
: null,
})
),
})
)
return {
success: true,
output: { stages, count: stages.length },
}
},
outputs: {
stages: {
type: 'array',
description: 'List of job stages in order',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Stage ID' },
name: { type: 'string', description: 'Stage name' },
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
job_id: { type: 'number', description: 'Associated job ID' },
priority: { type: 'number', description: 'Stage order priority' },
active: { type: 'boolean', description: 'Whether the stage is active' },
interviews: {
type: 'array',
description: 'Interview steps in this stage',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Interview ID' },
name: { type: 'string', description: 'Interview name' },
schedulable: {
type: 'boolean',
description: 'Whether the interview is schedulable',
},
estimated_minutes: {
type: 'number',
description: 'Estimated duration in minutes',
optional: true,
},
default_interviewer_users: {
type: 'array',
description: 'Default interviewers',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'User ID' },
name: { type: 'string', description: 'Full name' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
employee_id: { type: 'string', description: 'Employee ID', optional: true },
},
},
},
interview_kit: {
type: 'object',
description: 'Interview kit details',
optional: true,
properties: {
id: { type: 'number', description: 'Kit ID' },
content: { type: 'string', description: 'Kit content (HTML)', optional: true },
questions: {
type: 'array',
description: 'Interview kit questions',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Question ID' },
question: { type: 'string', description: 'Question text' },
},
},
},
},
},
},
},
},
},
},
},
count: { type: 'number', description: 'Number of stages returned' },
},
}
+182
View File
@@ -0,0 +1,182 @@
import type {
GreenhouseJobSummary,
GreenhouseListJobsParams,
GreenhouseListJobsResponse,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListJobsTool: ToolConfig<
GreenhouseListJobsParams,
GreenhouseListJobsResponse
> = {
id: 'greenhouse_list_jobs',
name: 'Greenhouse List Jobs',
description:
'Lists jobs from Greenhouse with optional filtering by status, department, or office',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by job status (open, closed, draft)',
},
created_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only jobs created at or after this ISO 8601 timestamp',
},
created_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only jobs created before this ISO 8601 timestamp',
},
updated_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only jobs updated at or after this ISO 8601 timestamp',
},
updated_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only jobs updated before this ISO 8601 timestamp',
},
department_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to jobs in this department ID',
},
office_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter to jobs in this office ID',
},
},
request: {
url: (params: GreenhouseListJobsParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/jobs')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
if (params.status) url.searchParams.append('status', params.status)
if (params.created_after) url.searchParams.append('created_after', params.created_after)
if (params.created_before) url.searchParams.append('created_before', params.created_before)
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
if (params.department_id) url.searchParams.append('department_id', params.department_id)
if (params.office_id) url.searchParams.append('office_id', params.office_id)
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListJobsParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListJobsResponse> => {
if (!response.ok) {
return {
success: false,
output: { jobs: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const jobs: GreenhouseJobSummary[] = (Array.isArray(data) ? data : []).map(
(j: Record<string, unknown>) => ({
id: (j.id as number) ?? 0,
name: (j.name as string) ?? null,
status: (j.status as string) ?? null,
confidential: (j.confidential as boolean) ?? false,
departments: (j.departments as Array<{ id: number; name: string }>) ?? [],
offices: (j.offices as Array<{ id: number; name: string }>) ?? [],
opened_at: (j.opened_at as string) ?? null,
closed_at: (j.closed_at as string) ?? null,
created_at: (j.created_at as string) ?? null,
updated_at: (j.updated_at as string) ?? null,
})
)
return {
success: true,
output: { jobs, count: jobs.length },
}
},
outputs: {
jobs: {
type: 'array',
description: 'List of jobs',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Job ID' },
name: { type: 'string', description: 'Job title' },
status: { type: 'string', description: 'Job status (open, closed, draft)' },
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
departments: {
type: 'array',
description: 'Associated departments',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Department ID' },
name: { type: 'string', description: 'Department name' },
},
},
},
offices: {
type: 'array',
description: 'Associated offices',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Office ID' },
name: { type: 'string', description: 'Office name' },
},
},
},
opened_at: {
type: 'string',
description: 'Date job was opened (ISO 8601)',
optional: true,
},
closed_at: {
type: 'string',
description: 'Date job was closed (ISO 8601)',
optional: true,
},
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
},
},
},
count: { type: 'number', description: 'Number of jobs returned' },
},
}
+114
View File
@@ -0,0 +1,114 @@
import type {
GreenhouseListOfficesParams,
GreenhouseListOfficesResponse,
GreenhouseOffice,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListOfficesTool: ToolConfig<
GreenhouseListOfficesParams,
GreenhouseListOfficesResponse
> = {
id: 'greenhouse_list_offices',
name: 'Greenhouse List Offices',
description: 'Lists all offices configured in Greenhouse',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},
request: {
url: (params: GreenhouseListOfficesParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/offices')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListOfficesParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListOfficesResponse> => {
if (!response.ok) {
return {
success: false,
output: { offices: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const offices: GreenhouseOffice[] = (Array.isArray(data) ? data : []).map(
(o: Record<string, unknown>) => ({
id: (o.id as number) ?? 0,
name: (o.name as string) ?? null,
location: {
name: ((o.location as Record<string, unknown>)?.name as string) ?? null,
},
primary_contact_user_id: (o.primary_contact_user_id as number) ?? null,
parent_id: (o.parent_id as number) ?? null,
child_ids: (o.child_ids as number[]) ?? [],
external_id: (o.external_id as string) ?? null,
})
)
return {
success: true,
output: { offices, count: offices.length },
}
},
outputs: {
offices: {
type: 'array',
description: 'List of offices',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Office ID' },
name: { type: 'string', description: 'Office name' },
location: {
type: 'object',
description: 'Office location',
properties: {
name: { type: 'string', description: 'Location name', optional: true },
},
},
primary_contact_user_id: {
type: 'number',
description: 'Primary contact user ID',
optional: true,
},
parent_id: { type: 'number', description: 'Parent office ID', optional: true },
child_ids: {
type: 'array',
description: 'Child office IDs',
items: { type: 'number', description: 'Office ID' },
},
external_id: { type: 'string', description: 'External system ID', optional: true },
},
},
},
count: { type: 'number', description: 'Number of offices returned' },
},
}
+152
View File
@@ -0,0 +1,152 @@
import type {
GreenhouseListUsersParams,
GreenhouseListUsersResponse,
GreenhouseUser,
} from '@/tools/greenhouse/types'
import type { ToolConfig } from '@/tools/types'
export const greenhouseListUsersTool: ToolConfig<
GreenhouseListUsersParams,
GreenhouseListUsersResponse
> = {
id: 'greenhouse_list_users',
name: 'Greenhouse List Users',
description:
'Lists Greenhouse users (recruiters, hiring managers, admins) with optional filtering',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greenhouse Harvest API key',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (1-500, default 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
created_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only users created at or after this ISO 8601 timestamp',
},
created_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only users created before this ISO 8601 timestamp',
},
updated_after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only users updated at or after this ISO 8601 timestamp',
},
updated_before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Return only users updated before this ISO 8601 timestamp',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by email address',
},
},
request: {
url: (params: GreenhouseListUsersParams) => {
const url = new URL('https://harvest.greenhouse.io/v1/users')
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
if (params.created_after) url.searchParams.append('created_after', params.created_after)
if (params.created_before) url.searchParams.append('created_before', params.created_before)
if (params.updated_after) url.searchParams.append('updated_after', params.updated_after)
if (params.updated_before) url.searchParams.append('updated_before', params.updated_before)
if (params.email) url.searchParams.append('email', params.email)
return url.toString()
},
method: 'GET',
headers: (params: GreenhouseListUsersParams) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:`)}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GreenhouseListUsersResponse> => {
if (!response.ok) {
return {
success: false,
output: { users: [], count: 0 },
error: `Greenhouse API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
const users: GreenhouseUser[] = (Array.isArray(data) ? data : []).map(
(u: Record<string, unknown>) => ({
id: (u.id as number) ?? 0,
name: (u.name as string) ?? null,
first_name: (u.first_name as string) ?? null,
last_name: (u.last_name as string) ?? null,
primary_email_address: (u.primary_email_address as string) ?? null,
disabled: (u.disabled as boolean) ?? false,
site_admin: (u.site_admin as boolean) ?? false,
emails: (u.emails as string[]) ?? [],
employee_id: (u.employee_id as string) ?? null,
linked_candidate_ids: (u.linked_candidate_ids as number[]) ?? [],
created_at: (u.created_at as string) ?? null,
updated_at: (u.updated_at as string) ?? null,
})
)
return {
success: true,
output: { users, count: users.length },
}
},
outputs: {
users: {
type: 'array',
description: 'List of Greenhouse users',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'User ID' },
name: { type: 'string', description: 'Full name' },
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
primary_email_address: { type: 'string', description: 'Primary email' },
disabled: { type: 'boolean', description: 'Whether the user is disabled' },
site_admin: { type: 'boolean', description: 'Whether the user is a site admin' },
emails: {
type: 'array',
description: 'All email addresses',
items: { type: 'string', description: 'Email address' },
},
employee_id: { type: 'string', description: 'Employee ID', optional: true },
linked_candidate_ids: {
type: 'array',
description: 'IDs of candidates linked to this user',
items: { type: 'number', description: 'Candidate ID' },
},
created_at: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updated_at: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
},
},
},
count: { type: 'number', description: 'Number of users returned' },
},
}
+410
View File
@@ -0,0 +1,410 @@
import type { ToolResponse } from '@/tools/types'
/**
* Base parameters shared by all Greenhouse tools
*/
interface GreenhouseBaseParams {
apiKey: string
}
// ── List Candidates ──
export interface GreenhouseListCandidatesParams extends GreenhouseBaseParams {
per_page?: number
page?: number
created_before?: string
created_after?: string
updated_before?: string
updated_after?: string
job_id?: string
email?: string
candidate_ids?: string
}
export interface GreenhouseListCandidatesResponse extends ToolResponse {
output: {
candidates: GreenhouseCandidateSummary[]
count: number
}
}
// ── Get Candidate ──
export interface GreenhouseGetCandidateParams extends GreenhouseBaseParams {
candidateId: string
}
export interface GreenhouseGetCandidateResponse extends ToolResponse {
output: {
id: number
first_name: string | null
last_name: string | null
company: string | null
title: string | null
is_private: boolean
can_email: boolean
created_at: string | null
updated_at: string | null
last_activity: string | null
email_addresses: Array<{ value: string; type: string }>
phone_numbers: Array<{ value: string; type: string }>
addresses: Array<{ value: string; type: string }>
website_addresses: Array<{ value: string; type: string }>
social_media_addresses: Array<{ value: string }>
tags: string[]
application_ids: number[]
recruiter: GreenhouseUserRef | null
coordinator: GreenhouseUserRef | null
attachments: GreenhouseAttachment[]
educations: GreenhouseEducation[]
employments: GreenhouseEmployment[]
custom_fields: Record<string, unknown>
}
}
// ── List Jobs ──
export interface GreenhouseListJobsParams extends GreenhouseBaseParams {
per_page?: number
page?: number
status?: string
created_before?: string
created_after?: string
updated_before?: string
updated_after?: string
department_id?: string
office_id?: string
}
export interface GreenhouseListJobsResponse extends ToolResponse {
output: {
jobs: GreenhouseJobSummary[]
count: number
}
}
// ── Get Job ──
export interface GreenhouseGetJobParams extends GreenhouseBaseParams {
jobId: string
}
export interface GreenhouseGetJobResponse extends ToolResponse {
output: {
id: number
name: string | null
requisition_id: string | null
status: string | null
confidential: boolean
created_at: string | null
opened_at: string | null
closed_at: string | null
updated_at: string | null
is_template: boolean | null
notes: string | null
departments: Array<{ id: number; name: string; parent_id: number | null }>
offices: Array<{ id: number; name: string; location: { name: string | null } }>
hiring_team: GreenhouseHiringTeam
openings: GreenhouseOpening[]
custom_fields: Record<string, unknown>
}
}
// ── List Applications ──
export interface GreenhouseListApplicationsParams extends GreenhouseBaseParams {
per_page?: number
page?: number
job_id?: string
status?: string
created_before?: string
created_after?: string
last_activity_after?: string
}
export interface GreenhouseListApplicationsResponse extends ToolResponse {
output: {
applications: GreenhouseApplicationSummary[]
count: number
}
}
// ── Get Application ──
export interface GreenhouseGetApplicationParams extends GreenhouseBaseParams {
applicationId: string
}
export interface GreenhouseGetApplicationResponse extends ToolResponse {
output: {
id: number
candidate_id: number
prospect: boolean
status: string | null
applied_at: string | null
rejected_at: string | null
last_activity_at: string | null
location: { address: string | null } | null
source: { id: number; public_name: string } | null
credited_to: GreenhouseUserRef | null
recruiter: GreenhouseUserRef | null
coordinator: GreenhouseUserRef | null
current_stage: { id: number; name: string } | null
rejection_reason: { id: number; name: string; type: { id: number; name: string } } | null
jobs: Array<{ id: number; name: string }>
job_post_id: number | null
answers: Array<{ question: string; answer: string }>
attachments: GreenhouseAttachment[]
custom_fields: Record<string, unknown>
}
}
// ── List Users ──
export interface GreenhouseListUsersParams extends GreenhouseBaseParams {
per_page?: number
page?: number
created_before?: string
created_after?: string
updated_before?: string
updated_after?: string
email?: string
}
export interface GreenhouseListUsersResponse extends ToolResponse {
output: {
users: GreenhouseUser[]
count: number
}
}
// ── Get User ──
export interface GreenhouseGetUserParams extends GreenhouseBaseParams {
userId: string
}
export interface GreenhouseGetUserResponse extends ToolResponse {
output: {
id: number
name: string | null
first_name: string | null
last_name: string | null
primary_email_address: string | null
disabled: boolean
site_admin: boolean
emails: string[]
employee_id: string | null
linked_candidate_ids: number[]
created_at: string | null
updated_at: string | null
}
}
// ── List Departments ──
export interface GreenhouseListDepartmentsParams extends GreenhouseBaseParams {
per_page?: number
page?: number
}
export interface GreenhouseListDepartmentsResponse extends ToolResponse {
output: {
departments: GreenhouseDepartment[]
count: number
}
}
// ── List Offices ──
export interface GreenhouseListOfficesParams extends GreenhouseBaseParams {
per_page?: number
page?: number
}
export interface GreenhouseListOfficesResponse extends ToolResponse {
output: {
offices: GreenhouseOffice[]
count: number
}
}
// ── List Job Stages ──
export interface GreenhouseListJobStagesParams extends GreenhouseBaseParams {
jobId: string
per_page?: number
page?: number
}
export interface GreenhouseListJobStagesResponse extends ToolResponse {
output: {
stages: GreenhouseJobStage[]
count: number
}
}
// ── Shared Types ──
export interface GreenhouseUserRef {
id: number
first_name: string
last_name: string
name: string
employee_id: string | null
}
interface GreenhouseAttachment {
filename: string
url: string
type: string
created_at: string | null
}
interface GreenhouseEducation {
id: number
school_name: string | null
degree: string | null
discipline: string | null
start_date: string | null
end_date: string | null
}
interface GreenhouseEmployment {
id: number
company_name: string | null
title: string | null
start_date: string | null
end_date: string | null
}
export interface GreenhouseUser {
id: number
name: string | null
first_name: string | null
last_name: string | null
primary_email_address: string | null
disabled: boolean
site_admin: boolean
emails: string[]
employee_id: string | null
linked_candidate_ids: number[]
created_at: string | null
updated_at: string | null
}
export interface GreenhouseDepartment {
id: number
name: string | null
parent_id: number | null
child_ids: number[]
external_id: string | null
}
export interface GreenhouseOffice {
id: number
name: string | null
location: { name: string | null }
primary_contact_user_id: number | null
parent_id: number | null
child_ids: number[]
external_id: string | null
}
export interface GreenhouseJobStageInterview {
id: number
name: string | null
schedulable: boolean
estimated_minutes: number | null
default_interviewer_users: GreenhouseUserRef[]
interview_kit: {
id: number
content: string | null
questions: Array<{ id: number; question: string }>
} | null
}
export interface GreenhouseJobStage {
id: number
name: string | null
created_at: string | null
updated_at: string | null
job_id: number
priority: number
active: boolean
interviews: GreenhouseJobStageInterview[]
}
export interface GreenhouseCandidateSummary {
id: number
first_name: string | null
last_name: string | null
company: string | null
title: string | null
is_private: boolean
can_email: boolean
email_addresses: Array<{ value: string; type: string }>
tags: string[]
application_ids: number[]
created_at: string | null
updated_at: string | null
last_activity: string | null
}
export interface GreenhouseJobSummary {
id: number
name: string | null
status: string | null
confidential: boolean
departments: Array<{ id: number; name: string }>
offices: Array<{ id: number; name: string }>
opened_at: string | null
closed_at: string | null
created_at: string | null
updated_at: string | null
}
export interface GreenhouseApplicationSummary {
id: number
candidate_id: number | null
prospect: boolean
status: string | null
current_stage: { id: number; name: string } | null
jobs: Array<{ id: number; name: string }>
applied_at: string | null
rejected_at: string | null
last_activity_at: string | null
}
interface GreenhouseHiringTeam {
hiring_managers: GreenhouseUserRef[]
recruiters: Array<GreenhouseUserRef & { responsible: boolean }>
coordinators: Array<GreenhouseUserRef & { responsible: boolean }>
sourcers: GreenhouseUserRef[]
}
interface GreenhouseOpening {
id: number
opening_id: string | null
status: string
opened_at: string | null
closed_at: string | null
application_id: number | null
close_reason: { id: number; name: string } | null
}
/**
* Union type of all Greenhouse responses
*/
export type GreenhouseResponse =
| GreenhouseListCandidatesResponse
| GreenhouseGetCandidateResponse
| GreenhouseListJobsResponse
| GreenhouseGetJobResponse
| GreenhouseListApplicationsResponse
| GreenhouseGetApplicationResponse
| GreenhouseListUsersResponse
| GreenhouseGetUserResponse
| GreenhouseListDepartmentsResponse
| GreenhouseListOfficesResponse
| GreenhouseListJobStagesResponse