Skip to content

Commit 12d44f2

Browse files
authored
Merge pull request #6 from arabcoders/dev
Add LLM friendly API & Client
2 parents 2785143 + fde6cdf commit 12d44f2

23 files changed

Lines changed: 4312 additions & 2936 deletions

.env.example

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,18 @@ AUTH_PASSWORD=my_super_secret_password
2828
SESSION_RESTORE_ENABLED=false
2929

3030
# Enable raw full URL feature (disabled by default).
31-
RAW_FULL_URL=false
31+
RAW_FULL_URL=false
32+
33+
# Base URL of the HTTP Inspector instance
34+
# Default: http://localhost:3000
35+
HTTP_INSPECTOR_URL=http://localhost:3000
36+
37+
# Default token ID to use for operations (optional)
38+
# Can be either the full UUID or 8-character friendly ID
39+
# Example: 550e8400-e29b-41d4-a716-446655440000 or abc12345
40+
HTTP_INSPECTOR_TOKEN=
41+
42+
# Secret for accessing user tokens (optional)
43+
# Required when accessing tokens created via the web UI
44+
# Must be the session ID (UUID) that created the token
45+
HTTP_INSPECTOR_SECRET=

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ prisma/dev.*
2525
nextjs-based/*
2626
llm*.txt
2727
nextjs-based/
28+
__pycache__

README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ The UI is available at http://localhost:3001 and the container persists until yo
7272
| **AUTH_PASSWORD** | No | **-** | Password required for login when authentication is enabled |
7373
| **SESSION_RESTORE_ENABLED** | No | **true** | Enable restoring previous sessions by friendly ID |
7474
| **RAW_FULL_URL** | No | **false** | Include full URL in raw request output |
75+
| **ENABLE_LLM_ENDPOINT** | No | **false** | Enable LLM API endpoints for programmatic access |
76+
77+
## Python Client
78+
79+
A Python client library and CLI tool is available for programmatic access to the LLM API endpoints. The client provides:
80+
81+
- **Token Management**: Create, get, update, and delete webhook tokens
82+
- **Request Inspection**: View captured requests and their details
83+
- **Response Configuration**: Set custom responses for webhook endpoints
84+
- **Environment Configuration**: Configure via environment variables
85+
86+
### Quick Start
87+
88+
```bash
89+
# Get API information
90+
./bin/http_inspector_client info
91+
92+
# Create a webhook token
93+
./bin/http_inspector_client create
94+
95+
# View captured requests
96+
./bin/http_inspector_client get <token-id>
97+
98+
# Get latest request
99+
./bin/http_inspector_client latest <token-id>
100+
101+
# Configure custom response
102+
./bin/http_inspector_client update <token-id> --status 201 --body "Created"
103+
```
75104

76105
## API Reference
77106

@@ -393,6 +422,124 @@ Report authentication status.
393422
}
394423
```
395424

425+
### LLM API
426+
427+
The LLM API provides programmatic access designed for automation tools and LLMs. **Must be enabled with `ENABLE_LLM_ENDPOINT=true`.**
428+
429+
**Key Features:**
430+
- Tokens created via this API are associated with a static LLM session
431+
- No authentication required for LLM-created tokens
432+
- User tokens can be accessed read-only with `?secret=UUID` parameter
433+
- Tokens identified by UUID or 8-character friendlyId
434+
435+
#### GET /api/llm
436+
Get API information and documentation.
437+
438+
**Response:**
439+
```json
440+
{
441+
"name": "HTTP Inspector LLM API",
442+
"version": "1.0.0",
443+
"description": "API endpoints designed for programmatic access",
444+
"endpoints": [...],
445+
"notes": [...]
446+
}
447+
```
448+
449+
#### POST /api/llm/token
450+
Create a new payload token in the LLM session.
451+
452+
**Response:**
453+
```json
454+
{
455+
"id": "550e8400-e29b-41d4-a716-446655440000",
456+
"friendlyId": "abc12345",
457+
"sessionId": "llm-session-static-id",
458+
"createdAt": "2025-01-15T10:30:00.000Z",
459+
"payloadUrl": "http://localhost:3000/api/payload/abc12345",
460+
"responseEnabled": false,
461+
"responseStatus": 200,
462+
"responseHeaders": null,
463+
"responseBody": null
464+
}
465+
```
466+
467+
#### GET /api/llm/token/:token
468+
Get token details and all captured requests.
469+
470+
**Parameters:**
471+
- `:token` - Token ID (UUID) or friendlyId (8-char)
472+
- `?secret=UUID` - Required for user tokens (read-only access)
473+
474+
**Response:**
475+
```json
476+
{
477+
"token": {
478+
"id": "550e8400-e29b-41d4-a716-446655440000",
479+
"friendlyId": "abc12345",
480+
"createdAt": "2025-01-15T10:30:00.000Z",
481+
"payloadUrl": "http://localhost:3000/api/payload/abc12345"
482+
},
483+
"requests": [
484+
{
485+
"id": "650e8400-e29b-41d4-a716-446655440000",
486+
"method": "POST",
487+
"url": "/api/payload/abc12345?test=1",
488+
"headers": {"content-type": "application/json"},
489+
"contentType": "application/json",
490+
"contentLength": 42,
491+
"isBinary": false,
492+
"body": "{\"test\":\"data\"}",
493+
"clientIp": "192.168.1.100",
494+
"remoteIp": "203.0.113.1",
495+
"createdAt": "2025-01-15T10:30:00.000Z"
496+
}
497+
],
498+
"total": 1
499+
}
500+
```
501+
502+
**Notes:** Binary data in request bodies is marked as `[Binary data not included]`.
503+
504+
#### GET /api/llm/token/:token/latest
505+
Get the most recent request for a token.
506+
507+
**Parameters:**
508+
- `:token` - Token ID (UUID) or friendlyId (8-char)
509+
- `?secret=UUID` - Required for user tokens (read-only access)
510+
511+
**Response:** Single request object (same format as requests array above).
512+
513+
**Returns `404`** if no requests exist for the token.
514+
515+
#### PATCH /api/llm/token/:token
516+
Update token response settings. **LLM tokens only** (user tokens not allowed).
517+
518+
**Parameters:**
519+
- `:token` - Token ID (UUID) or friendlyId (8-char)
520+
521+
**Request Body:**
522+
```json
523+
{
524+
"responseEnabled": true,
525+
"responseStatus": 201,
526+
"responseHeaders": "{\"Content-Type\":\"application/json\"}",
527+
"responseBody": "{\"status\":\"created\"}"
528+
}
529+
```
530+
531+
All fields are optional. Configure what the webhook endpoint returns when it receives requests.
532+
533+
**Response:** `{ "ok": true }`
534+
535+
#### DELETE /api/llm/token/:token
536+
Delete a token and all its associated requests. **LLM tokens only** (user tokens not allowed).
537+
538+
**Parameters:**
539+
- `:token` - Token ID (UUID) or friendlyId (8-char)
540+
541+
**Response:** `{ "ok": true }`
542+
396543
### Standard Responses
397544

398545
Common status codes:

app/components/RequestSidebar.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,6 @@ const handleCopyIp = async (request: RequestSummary) => {
147147
return
148148
}
149149
150-
if (false === (await copyText(clientIp))) {
151-
return
152-
}
153-
154-
notify({ title: 'Client IP copied', description: clientIp, color: 'success' })
150+
await copyText(clientIp)
155151
}
156152
</script>

app/components/token/ApiUrlsCard.vue

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,35 @@
1616
</template>
1717

1818
<div v-if="isOpen" class="flex flex-col gap-4 p-4 border-t border-gray-200 dark:border-gray-700">
19-
<!-- Payload URL -->
2019
<div class="space-y-2">
2120
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
2221
Webhook URL
2322
</label>
2423
<div class="flex gap-2">
2524
<UInput :model-value="payloadUrl" readonly size="md" class="flex-1 font-mono text-xs" />
2625
<UTooltip :text="copyPayloadState === 'copied' ? 'Copied!' : 'Copy URL'">
27-
<UButton
28-
:icon="copyPayloadState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
29-
color="neutral"
30-
variant="soft"
31-
@click="handleCopyPayload" />
26+
<UButton :icon="copyPayloadState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
27+
color="neutral" variant="soft" @click="handleCopyPayload" />
3228
</UTooltip>
3329
</div>
3430
<p class="text-xs text-gray-500 dark:text-gray-400">
3531
Use this URL to capture requests.
3632
</p>
3733
</div>
3834

39-
<!-- View API URL -->
4035
<div class="space-y-2">
4136
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
4237
Automation API URL
4338
</label>
4439
<div class="flex gap-2">
4540
<UInput :model-value="viewUrl" readonly size="md" class="flex-1 font-mono text-xs" />
4641
<UTooltip :text="copyViewState === 'copied' ? 'Copied!' : 'Copy URL'">
47-
<UButton
48-
:icon="copyViewState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
49-
color="neutral"
50-
variant="soft"
51-
@click="handleCopyView" />
42+
<UButton :icon="copyViewState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'" color="neutral"
43+
variant="soft" @click="handleCopyView" />
5244
</UTooltip>
5345
</div>
5446
<p class="text-xs text-gray-500 dark:text-gray-400">
55-
Read-only API for automation/LLMs. Returns all requests with bodies in JSON format.
47+
Read-only API for automation/LLMs.
5648
</p>
5749
</div>
5850
</div>
@@ -83,14 +75,13 @@ const payloadUrl = computed(() => {
8375
8476
const viewUrl = computed(() => {
8577
const friendlyId = token.value?.friendlyId || ''
86-
return `${origin.value}/api/view/${friendlyId}?secret=${props.tokenId}`
78+
return `${origin.value}/api/llm/token/${friendlyId}?secret=${props.tokenId}`
8779
})
8880
8981
const handleCopyPayload = async () => {
9082
try {
9183
await copyText(payloadUrl.value)
9284
copyPayloadState.value = 'copied'
93-
notify({ title: 'Webhook URL copied', description: payloadUrl.value, variant: 'success' })
9485
setTimeout(() => copyPayloadState.value = 'idle', 1200)
9586
} catch (error) {
9687
console.error('Failed to copy URL:', error)
@@ -102,7 +93,6 @@ const handleCopyView = async () => {
10293
try {
10394
await copyText(viewUrl.value)
10495
copyViewState.value = 'copied'
105-
notify({ title: 'API URL copied', description: viewUrl.value, variant: 'success' })
10696
setTimeout(() => copyViewState.value = 'idle', 1200)
10797
} catch (error) {
10898
console.error('Failed to copy URL:', error)

app/pages/index.vue

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,6 @@ const confirmDeleteToken = async () => {
8989
tokenToDelete.value = null
9090
9191
await deleteTokenMutation(id)
92-
93-
notify({
94-
title: 'Token deleted',
95-
description: 'The token and its requests have been removed',
96-
color: 'success',
97-
})
9892
}
9993
10094
const copyPayloadURL = async (id: string) => {
@@ -103,7 +97,6 @@ const copyPayloadURL = async (id: string) => {
10397
const friendlyId = token?.friendlyId ?? shortSlug(id)
10498
const url = `${origin}/api/payload/${friendlyId}`
10599
await copyText(url)
106-
notify({ title: 'URL copied', description: url, color: 'success', })
107100
}
108101
109102
const deleteToken = (id: string) => {

app/pages/token/[id].vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
</div>
3333
</div>
3434
<div class="grid gap-6 px-6 pb-6 lg:p-6">
35-
<ApiUrlsCard :token-id="tokenId" />
35+
<ApiUrlsCard v-if="llmEndpointEnabled" :token-id="tokenId" />
3636
<ResponseSettingsCard :token-id="tokenId" />
3737
<RawRequestCard :request="selectedRequest" :request-number="selectedRequestNumber" :token-id="tokenId" />
3838
<RequestDetailsCard :request="selectedRequest" :request-number="selectedRequestNumber"
@@ -78,6 +78,8 @@ const { data: requests } = requestsStore.useRequestsList(tokenId)
7878
const { mutateAsync: deleteRequestMutation } = requestsStore.useDeleteRequest()
7979
const { mutateAsync: deleteAllRequestsMutation } = requestsStore.useDeleteAllRequests()
8080
81+
const llmEndpointEnabled = useRuntimeConfig().public?.llmEndpointEnabled === true
82+
8183
const selectedRequestId = ref<string | null>(null)
8284
const incomingIds = ref<Set<string>>(new Set())
8385
const copyState = ref<'idle' | 'copied'>('idle')
@@ -159,8 +161,6 @@ const handleDeleteRequest = async (id: string) => {
159161
const firstRequest = requests.value && requests.value.length > 0 ? requests.value[0] : null
160162
selectedRequestId.value = firstRequest ? firstRequest.id : null
161163
}
162-
163-
notify({ title: 'Request deleted', variant: 'success' })
164164
} catch (error) {
165165
console.error('Failed to delete request:', error)
166166
notify({ title: 'Failed to delete request', variant: 'error' })
@@ -175,7 +175,6 @@ const copyPayloadURL = async () => {
175175
try {
176176
await copyText(url)
177177
copyState.value = 'copied'
178-
notify({ title: 'Payload URL copied', description: url, variant: 'success' })
179178
setTimeout(() => copyState.value = 'idle', 1200)
180179
} catch (error) {
181180
console.error('Failed to copy URL:', error)

0 commit comments

Comments
 (0)