mcp-server-autoshorts 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/catalog.d.ts +74 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +95 -0
- package/dist/catalog.js.map +1 -0
- package/dist/client.d.ts +122 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +214 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +7 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/clone-viral.d.ts +6 -0
- package/dist/prompts/clone-viral.d.ts.map +1 -0
- package/dist/prompts/clone-viral.js +33 -0
- package/dist/prompts/clone-viral.js.map +1 -0
- package/dist/prompts/generate-video.d.ts +6 -0
- package/dist/prompts/generate-video.d.ts.map +1 -0
- package/dist/prompts/generate-video.js +32 -0
- package/dist/prompts/generate-video.js.map +1 -0
- package/dist/resources/credits.d.ts +7 -0
- package/dist/resources/credits.d.ts.map +1 -0
- package/dist/resources/credits.js +16 -0
- package/dist/resources/credits.js.map +1 -0
- package/dist/resources/rate-limit.d.ts +7 -0
- package/dist/resources/rate-limit.d.ts.map +1 -0
- package/dist/resources/rate-limit.js +16 -0
- package/dist/resources/rate-limit.js.map +1 -0
- package/dist/tool-helpers.d.ts +21 -0
- package/dist/tool-helpers.d.ts.map +1 -0
- package/dist/tool-helpers.js +27 -0
- package/dist/tool-helpers.js.map +1 -0
- package/dist/tools/billing.d.ts +7 -0
- package/dist/tools/billing.d.ts.map +1 -0
- package/dist/tools/billing.js +8 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/credits.d.ts +7 -0
- package/dist/tools/credits.d.ts.map +1 -0
- package/dist/tools/credits.js +8 -0
- package/dist/tools/credits.js.map +1 -0
- package/dist/tools/keys.d.ts +8 -0
- package/dist/tools/keys.d.ts.map +1 -0
- package/dist/tools/keys.js +9 -0
- package/dist/tools/keys.js.map +1 -0
- package/dist/tools/niches.d.ts +7 -0
- package/dist/tools/niches.d.ts.map +1 -0
- package/dist/tools/niches.js +8 -0
- package/dist/tools/niches.js.map +1 -0
- package/dist/tools/usage.d.ts +7 -0
- package/dist/tools/usage.d.ts.map +1 -0
- package/dist/tools/usage.js +8 -0
- package/dist/tools/usage.js.map +1 -0
- package/dist/tools/videos.d.ts +7 -0
- package/dist/tools/videos.d.ts.map +1 -0
- package/dist/tools/videos.js +75 -0
- package/dist/tools/videos.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -14
- package/index.js +0 -85
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# mcp-server-autoshorts
|
|
2
|
+
|
|
3
|
+
MCP server for [AutoShort.org](https://autoshort.org) — generate AI videos from Claude, Cursor, and other MCP clients.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx mcp-server-autoshorts --api-key sk_live_your_key_here
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"autoshort": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "mcp-server-autoshorts", "--api-key", "sk_live_your_key_here"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Cursor
|
|
29
|
+
|
|
30
|
+
Add to `.cursor/mcp.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"autoshort": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "mcp-server-autoshorts", "--api-key", "sk_live_your_key_here"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Environment Variable
|
|
44
|
+
|
|
45
|
+
Instead of `--api-key`, set `AUTOSHORT_API_KEY`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export AUTOSHORT_API_KEY=sk_live_your_key_here
|
|
49
|
+
npx mcp-server-autoshorts
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Available Tools
|
|
53
|
+
|
|
54
|
+
| Tool | Description |
|
|
55
|
+
|------|-------------|
|
|
56
|
+
| `autoshort_generate_video` | Generate an AI video from a topic |
|
|
57
|
+
| `autoshort_get_video` | Get video status and details |
|
|
58
|
+
| `autoshort_list_videos` | List your videos with pagination |
|
|
59
|
+
| `autoshort_list_niches` | List available niches, art styles, qualities |
|
|
60
|
+
| `autoshort_get_credit_balance` | Check credit balance and plan |
|
|
61
|
+
| `autoshort_get_usage_stats` | API usage statistics |
|
|
62
|
+
| `autoshort_get_subscription` | Subscription plan status |
|
|
63
|
+
| `autoshort_list_api_keys` | List your API keys |
|
|
64
|
+
|
|
65
|
+
Webhook tools were removed: the server does not deliver video events yet. Poll
|
|
66
|
+
`autoshort_get_video` every 20-30 seconds; a PENDING video that cannot render returns
|
|
67
|
+
`blockedReason` (for example `INSUFFICIENT_CREDITS`) instead of a countdown.
|
|
68
|
+
|
|
69
|
+
New API keys include `credits:read` and `usage:read`. A key created earlier without them gets a
|
|
70
|
+
403 from `autoshort_get_credit_balance` whose message links to the page where you create a key
|
|
71
|
+
with that scope.
|
|
72
|
+
|
|
73
|
+
## Resources
|
|
74
|
+
|
|
75
|
+
| URI | Description |
|
|
76
|
+
|-----|-------------|
|
|
77
|
+
| `autoshort://credits/balance` | Current credit balance |
|
|
78
|
+
| `autoshort://usage/rate-limit` | API rate limit status |
|
|
79
|
+
|
|
80
|
+
## Prompts
|
|
81
|
+
|
|
82
|
+
| Name | Description |
|
|
83
|
+
|------|-------------|
|
|
84
|
+
| `generate_video` | Step-by-step video generation guide |
|
|
85
|
+
| `clone_viral_video` | Clone a viral video concept |
|
|
86
|
+
|
|
87
|
+
## Get Your API Key
|
|
88
|
+
|
|
89
|
+
1. Sign up at [autoshort.org](https://autoshort.org)
|
|
90
|
+
2. Go to [Developers](https://autoshort.org/en/developers)
|
|
91
|
+
3. Create an API key with desired scopes
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values the AutoShort API accepts or charges, as this package advertises them to agents.
|
|
3
|
+
*
|
|
4
|
+
* This package is published on its own and cannot import the app, so these are copies.
|
|
5
|
+
* Each copy is pinned to its source by src/lib/__tests__/mcp-stdio-catalog-sync.test.ts in
|
|
6
|
+
* the app (VideoStatus enum, MOTION_LANGUAGE_CODES, CHAT_MESSAGE_MAX_CHARS, FEATURE_COSTS,
|
|
7
|
+
* VideoQuality). Change the source and that test names the line to change here. Hand-typed
|
|
8
|
+
* values are how the tools came to offer status GENERATING (rejected by the API), languages
|
|
9
|
+
* en/vi only (the API speaks nine) and "40-150 credits" (an API video costs 65).
|
|
10
|
+
*/
|
|
11
|
+
/** Prisma VideoStatus, in schema order. */
|
|
12
|
+
export declare const VIDEO_STATUSES: readonly ["PENDING", "GENERATING_SCRIPT", "GENERATING_VOICE", "FETCHING_MEDIA", "RENDERING", "COMPLETED", "FAILED"];
|
|
13
|
+
/** MOTION_LANGUAGE_CODES: what POST /api/videos/chat honours as an explicit language. */
|
|
14
|
+
export declare const CHAT_VIDEO_LANGUAGES: readonly ["en", "es", "pt", "fr", "de", "it", "id", "tr", "vi"];
|
|
15
|
+
/** CHAT_MESSAGE_MAX_CHARS. */
|
|
16
|
+
export declare const CHAT_MESSAGE_MAX_CHARS = 4000;
|
|
17
|
+
/** Prisma VideoQuality. */
|
|
18
|
+
export declare const VIDEO_QUALITIES: readonly ["HD_720", "FULL_HD_1080"];
|
|
19
|
+
/** FEATURE_COSTS.video_standard and FEATURE_COSTS.voice_over_premium. */
|
|
20
|
+
export declare const CREDIT_COSTS: {
|
|
21
|
+
readonly videoStandard: 40;
|
|
22
|
+
readonly elevenLabsVoice: 25;
|
|
23
|
+
};
|
|
24
|
+
/** Every /api/v1 video is narrated by ElevenLabs, so the render gate asks for both. */
|
|
25
|
+
export declare const API_VIDEO_CREDITS: number;
|
|
26
|
+
export declare const API_VIDEO_COST_TEXT: string;
|
|
27
|
+
export declare const POLL_HINT: string;
|
|
28
|
+
/**
|
|
29
|
+
* FEATURE_COSTS.video_image_motion.
|
|
30
|
+
*
|
|
31
|
+
* Hand-copied like every other number here, and this one is the figure an agent budgets
|
|
32
|
+
* against BEFORE it spends: image-to-video is the only tool whose endpoint charges at submit.
|
|
33
|
+
* Pinned to the app constant by src/lib/__tests__/mcp-stdio-catalog-sync.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
export declare const IMAGE_TO_VIDEO_CREDITS = 35;
|
|
36
|
+
/**
|
|
37
|
+
* No refund promise here, deliberately.
|
|
38
|
+
*
|
|
39
|
+
* This used to say the credits are "refunded if the clip is never produced". That is not an
|
|
40
|
+
* invariant the pipeline holds. The operator-timeout path in cron/process-videos does call
|
|
41
|
+
* refundIfChargedForFailedVideo, and so does the admin refuse route — but TWO cases still end
|
|
42
|
+
* FAILED with money taken: that helper's own `refund-failed` outcome, and
|
|
43
|
+
* cron/retry-stuck-videos' permanentlyStuck `updateMany`, which has no refund call (reachable
|
|
44
|
+
* for one of these rows only once the operator has delivered and the render has then stalled
|
|
45
|
+
* MAX_RETRIES times).
|
|
46
|
+
*
|
|
47
|
+
* A longer list was checked and NARROWED: admin/videos is a GET-only dashboard whose
|
|
48
|
+
* `status: "FAILED"` occurrences are `where` filters, admin/videos/[id]/operator refunds
|
|
49
|
+
* BEFORE calling failVideo, and videos/[id]/render cannot reach its unrefunded FAILED write
|
|
50
|
+
* for these rows at all — that write is gated on `claimedByRequest` while every
|
|
51
|
+
* VEO_3_1_OPERATOR row returns ~230 lines earlier. Two real cases, not five.
|
|
52
|
+
*
|
|
53
|
+
* So the usual case refunds and the edges do not, which makes "it is refunded" a promise we
|
|
54
|
+
* cannot keep. Say what is true and name the check instead, so an agent that was charged for
|
|
55
|
+
* nothing can find out.
|
|
56
|
+
*/
|
|
57
|
+
export declare const IMAGE_TO_VIDEO_COST_TEXT: string;
|
|
58
|
+
/**
|
|
59
|
+
* What an agent actually gets if the clip is fulfilled in operator mode.
|
|
60
|
+
*
|
|
61
|
+
* Image-to-video always renders through VEO_3_1_OPERATOR, which is a human driving a browser
|
|
62
|
+
* (scripts/veo-operator/run.mjs) — not an automatic provider. If nobody fulfils it inside the
|
|
63
|
+
* wait window the video FAILS. The refund wording is hedged for the reason in
|
|
64
|
+
* IMAGE_TO_VIDEO_COST_TEXT above; it previously claimed "any charge is refunded" flatly.
|
|
65
|
+
*/
|
|
66
|
+
export declare const OPERATOR_MODE_WARNING: string;
|
|
67
|
+
/**
|
|
68
|
+
* Image-to-video dedupes on a requestId this package derives from the image bytes and the
|
|
69
|
+
* motion prompt — not on an Idempotency-Key header. The id carries no time component on
|
|
70
|
+
* purpose; the 24h bound is a continuous window on the server's lookup, so no two
|
|
71
|
+
* near-simultaneous retries can fall on different sides of it.
|
|
72
|
+
*/
|
|
73
|
+
export declare const IMAGE_TO_VIDEO_IDEMPOTENCY_HINT: string;
|
|
74
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,cAAc,qHAQjB,CAAC;AAEX,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,iEAAkE,CAAC;AAEpG,8BAA8B;AAC9B,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,2BAA2B;AAC3B,eAAO,MAAM,eAAe,qCAAsC,CAAC;AAEnE,yEAAyE;AACzE,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAEX,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,QAA4D,CAAC;AAE3F,eAAO,MAAM,mBAAmB,QAE2D,CAAC;AAE5F,eAAO,MAAM,SAAS,QAEmE,CAAC;AAE1F;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,QAI8D,CAAC;AAEpG;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QAIe,CAAC;AAElD;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,QAMsB,CAAC"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values the AutoShort API accepts or charges, as this package advertises them to agents.
|
|
3
|
+
*
|
|
4
|
+
* This package is published on its own and cannot import the app, so these are copies.
|
|
5
|
+
* Each copy is pinned to its source by src/lib/__tests__/mcp-stdio-catalog-sync.test.ts in
|
|
6
|
+
* the app (VideoStatus enum, MOTION_LANGUAGE_CODES, CHAT_MESSAGE_MAX_CHARS, FEATURE_COSTS,
|
|
7
|
+
* VideoQuality). Change the source and that test names the line to change here. Hand-typed
|
|
8
|
+
* values are how the tools came to offer status GENERATING (rejected by the API), languages
|
|
9
|
+
* en/vi only (the API speaks nine) and "40-150 credits" (an API video costs 65).
|
|
10
|
+
*/
|
|
11
|
+
/** Prisma VideoStatus, in schema order. */
|
|
12
|
+
export const VIDEO_STATUSES = [
|
|
13
|
+
'PENDING',
|
|
14
|
+
'GENERATING_SCRIPT',
|
|
15
|
+
'GENERATING_VOICE',
|
|
16
|
+
'FETCHING_MEDIA',
|
|
17
|
+
'RENDERING',
|
|
18
|
+
'COMPLETED',
|
|
19
|
+
'FAILED',
|
|
20
|
+
];
|
|
21
|
+
/** MOTION_LANGUAGE_CODES: what POST /api/videos/chat honours as an explicit language. */
|
|
22
|
+
export const CHAT_VIDEO_LANGUAGES = ['en', 'es', 'pt', 'fr', 'de', 'it', 'id', 'tr', 'vi'];
|
|
23
|
+
/** CHAT_MESSAGE_MAX_CHARS. */
|
|
24
|
+
export const CHAT_MESSAGE_MAX_CHARS = 4000;
|
|
25
|
+
/** Prisma VideoQuality. */
|
|
26
|
+
export const VIDEO_QUALITIES = ['HD_720', 'FULL_HD_1080'];
|
|
27
|
+
/** FEATURE_COSTS.video_standard and FEATURE_COSTS.voice_over_premium. */
|
|
28
|
+
export const CREDIT_COSTS = {
|
|
29
|
+
videoStandard: 40,
|
|
30
|
+
elevenLabsVoice: 25,
|
|
31
|
+
};
|
|
32
|
+
/** Every /api/v1 video is narrated by ElevenLabs, so the render gate asks for both. */
|
|
33
|
+
export const API_VIDEO_CREDITS = CREDIT_COSTS.videoStandard + CREDIT_COSTS.elevenLabsVoice;
|
|
34
|
+
export const API_VIDEO_COST_TEXT = `Costs ${API_VIDEO_CREDITS} credits (${CREDIT_COSTS.videoStandard} render + ` +
|
|
35
|
+
`${CREDIT_COSTS.elevenLabsVoice} ElevenLabs voice); art style does not change the price.`;
|
|
36
|
+
export const POLL_HINT = 'Rendering is asynchronous: poll autoshort_get_video every 20-30 seconds until status is COMPLETED or FAILED. ' +
|
|
37
|
+
'If get_video returns blockedReason, stop polling and act on it (e.g. top up credits).';
|
|
38
|
+
/**
|
|
39
|
+
* FEATURE_COSTS.video_image_motion.
|
|
40
|
+
*
|
|
41
|
+
* Hand-copied like every other number here, and this one is the figure an agent budgets
|
|
42
|
+
* against BEFORE it spends: image-to-video is the only tool whose endpoint charges at submit.
|
|
43
|
+
* Pinned to the app constant by src/lib/__tests__/mcp-stdio-catalog-sync.test.ts.
|
|
44
|
+
*/
|
|
45
|
+
export const IMAGE_TO_VIDEO_CREDITS = 35;
|
|
46
|
+
/**
|
|
47
|
+
* No refund promise here, deliberately.
|
|
48
|
+
*
|
|
49
|
+
* This used to say the credits are "refunded if the clip is never produced". That is not an
|
|
50
|
+
* invariant the pipeline holds. The operator-timeout path in cron/process-videos does call
|
|
51
|
+
* refundIfChargedForFailedVideo, and so does the admin refuse route — but TWO cases still end
|
|
52
|
+
* FAILED with money taken: that helper's own `refund-failed` outcome, and
|
|
53
|
+
* cron/retry-stuck-videos' permanentlyStuck `updateMany`, which has no refund call (reachable
|
|
54
|
+
* for one of these rows only once the operator has delivered and the render has then stalled
|
|
55
|
+
* MAX_RETRIES times).
|
|
56
|
+
*
|
|
57
|
+
* A longer list was checked and NARROWED: admin/videos is a GET-only dashboard whose
|
|
58
|
+
* `status: "FAILED"` occurrences are `where` filters, admin/videos/[id]/operator refunds
|
|
59
|
+
* BEFORE calling failVideo, and videos/[id]/render cannot reach its unrefunded FAILED write
|
|
60
|
+
* for these rows at all — that write is gated on `claimedByRequest` while every
|
|
61
|
+
* VEO_3_1_OPERATOR row returns ~230 lines earlier. Two real cases, not five.
|
|
62
|
+
*
|
|
63
|
+
* So the usual case refunds and the edges do not, which makes "it is refunded" a promise we
|
|
64
|
+
* cannot keep. Say what is true and name the check instead, so an agent that was charged for
|
|
65
|
+
* nothing can find out.
|
|
66
|
+
*/
|
|
67
|
+
export const IMAGE_TO_VIDEO_COST_TEXT = `Costs ${IMAGE_TO_VIDEO_CREDITS} credits, and unlike every other tool here it is charged ` +
|
|
68
|
+
'the moment the job is accepted, not when the clip is delivered. A failed clip is normally ' +
|
|
69
|
+
'refunded automatically, but that is not guaranteed on every failure path: if the video ends ' +
|
|
70
|
+
'FAILED, check autoshort_get_credit_balance and report a missing refund rather than assuming it.';
|
|
71
|
+
/**
|
|
72
|
+
* What an agent actually gets if the clip is fulfilled in operator mode.
|
|
73
|
+
*
|
|
74
|
+
* Image-to-video always renders through VEO_3_1_OPERATOR, which is a human driving a browser
|
|
75
|
+
* (scripts/veo-operator/run.mjs) — not an automatic provider. If nobody fulfils it inside the
|
|
76
|
+
* wait window the video FAILS. The refund wording is hedged for the reason in
|
|
77
|
+
* IMAGE_TO_VIDEO_COST_TEXT above; it previously claimed "any charge is refunded" flatly.
|
|
78
|
+
*/
|
|
79
|
+
export const OPERATOR_MODE_WARNING = 'Fulfilment is MANUAL: a human operator animates the image, it is not an automatic render. ' +
|
|
80
|
+
'If nobody fulfils it within VEO_OPERATOR_WAIT_MINUTES (10 in production) the video FAILS ' +
|
|
81
|
+
'with "could not be produced in time" and the charge is normally refunded automatically. ' +
|
|
82
|
+
'Do not build an unattended loop on this tool.';
|
|
83
|
+
/**
|
|
84
|
+
* Image-to-video dedupes on a requestId this package derives from the image bytes and the
|
|
85
|
+
* motion prompt — not on an Idempotency-Key header. The id carries no time component on
|
|
86
|
+
* purpose; the 24h bound is a continuous window on the server's lookup, so no two
|
|
87
|
+
* near-simultaneous retries can fall on different sides of it.
|
|
88
|
+
*/
|
|
89
|
+
export const IMAGE_TO_VIDEO_IDEMPOTENCY_HINT = 'Retry-safe: re-calling with the same image and motionPrompt returns the first job ' +
|
|
90
|
+
'(duplicate: true) for 24 hours instead of charging again. If that attempt FAILED, a retry ' +
|
|
91
|
+
'refunds the dead attempt first and then makes a new clip, so one clip is never billed ' +
|
|
92
|
+
'twice — whatever its age. In the one case where the old credits cannot be returned you get ' +
|
|
93
|
+
'409 STRANDED_CHARGE with the videoId and no new charge: report it, do not loop. Pass ' +
|
|
94
|
+
'idempotencyKey to force a separate clip from identical inputs.';
|
|
95
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS;IACT,mBAAmB;IACnB,kBAAkB;IAClB,gBAAgB;IAChB,WAAW;IACX,WAAW;IACX,QAAQ;CACA,CAAC;AAEX,yFAAyF;AACzF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAEpG,8BAA8B;AAC9B,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAE3C,2BAA2B;AAC3B,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAU,CAAC;AAEnE,yEAAyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,aAAa,EAAE,EAAE;IACjB,eAAe,EAAE,EAAE;CACX,CAAC;AAEX,uFAAuF;AACvF,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC,aAAa,GAAG,YAAY,CAAC,eAAe,CAAC;AAE3F,MAAM,CAAC,MAAM,mBAAmB,GAC9B,SAAS,iBAAiB,aAAa,YAAY,CAAC,aAAa,YAAY;IAC7E,GAAG,YAAY,CAAC,eAAe,0DAA0D,CAAC;AAE5F,MAAM,CAAC,MAAM,SAAS,GACpB,+GAA+G;IAC/G,uFAAuF,CAAC;AAE1F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,SAAS,sBAAsB,2DAA2D;IAC1F,4FAA4F;IAC5F,8FAA8F;IAC9F,iGAAiG,CAAC;AAEpG;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,4FAA4F;IAC5F,2FAA2F;IAC3F,0FAA0F;IAC1F,+CAA+C,CAAC;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAC1C,oFAAoF;IACpF,4FAA4F;IAC5F,wFAAwF;IACxF,6FAA6F;IAC7F,uFAAuF;IACvF,gEAAgE,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoShort API HTTP Client
|
|
3
|
+
*
|
|
4
|
+
* Wraps all v1 API endpoints with typed methods.
|
|
5
|
+
* Captures rate limit headers for MCP resource exposure.
|
|
6
|
+
*/
|
|
7
|
+
import type { RateLimitInfo } from './types.js';
|
|
8
|
+
export declare class AutoShortClient {
|
|
9
|
+
private readonly apiKey;
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
/** Last rate limit info from API response headers */
|
|
12
|
+
lastRateLimit: RateLimitInfo | null;
|
|
13
|
+
constructor(apiKey: string, baseUrl: string);
|
|
14
|
+
/** Generic API request with auth, error handling, rate limit capture */
|
|
15
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
16
|
+
generateVideo(params: {
|
|
17
|
+
topic: string;
|
|
18
|
+
niche?: string;
|
|
19
|
+
duration?: number;
|
|
20
|
+
artStyle?: string;
|
|
21
|
+
quality?: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
id: string;
|
|
24
|
+
status: string;
|
|
25
|
+
}>;
|
|
26
|
+
generateChatVideo(params: {
|
|
27
|
+
message: string;
|
|
28
|
+
videoProvider?: 'FFMPEG' | 'VEO_3_1_OPERATOR';
|
|
29
|
+
artStyle?: string;
|
|
30
|
+
aspectRatio?: '9:16' | '1:1' | '16:9';
|
|
31
|
+
language?: string;
|
|
32
|
+
targetDuration?: number;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
videoId: string;
|
|
35
|
+
reply: string;
|
|
36
|
+
params: Record<string, unknown>;
|
|
37
|
+
}>;
|
|
38
|
+
generateProductAd(params: {
|
|
39
|
+
url: string;
|
|
40
|
+
language?: string;
|
|
41
|
+
artStyle?: string;
|
|
42
|
+
duration?: number;
|
|
43
|
+
}): Promise<Record<string, unknown>>;
|
|
44
|
+
generateImageToVideo(params: {
|
|
45
|
+
imagePath: string;
|
|
46
|
+
motionPrompt: string;
|
|
47
|
+
idempotencyKey?: string;
|
|
48
|
+
}): Promise<Record<string, unknown>>;
|
|
49
|
+
getVideo(id: string): Promise<Record<string, unknown>>;
|
|
50
|
+
listVideos(params?: {
|
|
51
|
+
limit?: number;
|
|
52
|
+
offset?: number;
|
|
53
|
+
status?: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
videos: unknown[];
|
|
56
|
+
pagination: unknown;
|
|
57
|
+
}>;
|
|
58
|
+
listNiches(): Promise<{
|
|
59
|
+
niches: unknown[];
|
|
60
|
+
artStyles: unknown[];
|
|
61
|
+
videoQualities: unknown[];
|
|
62
|
+
durations: number[];
|
|
63
|
+
}>;
|
|
64
|
+
getCreditBalance(): Promise<{
|
|
65
|
+
balance: number;
|
|
66
|
+
monthlyAllocation: number;
|
|
67
|
+
bonusCredits: number;
|
|
68
|
+
plan: string;
|
|
69
|
+
}>;
|
|
70
|
+
getUsageStats(): Promise<Record<string, unknown>>;
|
|
71
|
+
getSubscription(): Promise<Record<string, unknown>>;
|
|
72
|
+
listApiKeys(): Promise<{
|
|
73
|
+
apiKeys: unknown[];
|
|
74
|
+
}>;
|
|
75
|
+
}
|
|
76
|
+
/** Typed API error with HTTP status and error code */
|
|
77
|
+
export declare class AutoShortApiError extends Error {
|
|
78
|
+
readonly status: number;
|
|
79
|
+
readonly code: string;
|
|
80
|
+
constructor(message: string, status: number, code: string);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* MIME from the file's own first bytes, with the extension only as a tiebreaker.
|
|
84
|
+
*
|
|
85
|
+
* It used to be derived from the extension alone, so an extensionless file, a `.jfif` or a
|
|
86
|
+
* `.gif` was declared `image/jpeg` and failed the server's magic-byte check with the unhelpful
|
|
87
|
+
* "Image contents do not match the uploaded file type" / "Invalid image data". Sniffing means a
|
|
88
|
+
* correct file is labelled correctly whatever it is called, and a genuinely unsupported format
|
|
89
|
+
* keeps its real type so the server's 400 names the actual problem.
|
|
90
|
+
*/
|
|
91
|
+
export declare function sniffImageMime(bytes: Uint8Array, imagePath: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Shape a deterministic hash into something the route's STRICT v4 regex accepts.
|
|
94
|
+
*
|
|
95
|
+
* `requestId` is validated against
|
|
96
|
+
* /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, so a bare sha256
|
|
97
|
+
* is refused with 400 "A valid request ID is required". Version nibble pinned to 4 and the
|
|
98
|
+
* variant nibble forced into 8..b; every other nibble comes from the hash.
|
|
99
|
+
*/
|
|
100
|
+
export declare function uuidFromSeed(seed: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* The request id for one image-to-video submission: deterministic, and ALWAYS content-bound.
|
|
103
|
+
*
|
|
104
|
+
* Two rules, each closing a way of being charged 35 credits twice.
|
|
105
|
+
*
|
|
106
|
+
* 1. NO CLOCK IN THE SEED. It used to be `randomUUID()` per call, which defeats all three of
|
|
107
|
+
* the endpoint's dedupe layers (pre-check, pg_advisory_xact_lock, in-transaction re-check)
|
|
108
|
+
* and buys a second clip on every retry. A later attempt salted the seed with a UTC day
|
|
109
|
+
* bucket, which is worse than it looks: a bucket has an edge, so a retry at 23:59:59 and
|
|
110
|
+
* its follow-up at 00:00:01 derive DIFFERENT ids and miss every layer. The time bound
|
|
111
|
+
* belongs on the READER, as a continuous window — see imageToVideoDedupeSince() in
|
|
112
|
+
* src/lib/image-to-video-policy.ts.
|
|
113
|
+
*
|
|
114
|
+
* 2. THE CONTENT IS ALWAYS IN THE SEED, even when the caller supplies a key. A caller key used
|
|
115
|
+
* to replace the content seed outright, so an agent passing one run-scoped key for two
|
|
116
|
+
* different clips got the FIRST video back for the second ask — `duplicate: true`, no
|
|
117
|
+
* fingerprint compared, so not even a 422 to tell it apart. Folding both in keeps each
|
|
118
|
+
* direction working: a distinct key separates two deliberate clips from identical inputs,
|
|
119
|
+
* and distinct inputs separate two clips that happen to share a key.
|
|
120
|
+
*/
|
|
121
|
+
export declare function imageToVideoRequestId(imageBytes: Uint8Array, motionPrompt: string, idempotencyKey?: string): string;
|
|
122
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,qDAAqD;IAC9C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAQ;gBAG/B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;IAGlC,wEAAwE;IAClE,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAqCpE,aAAa,CAAC,MAAM,EAAE;QAC1B,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;KACrC;YAC2B,MAAM;gBAAU,MAAM;;IAG5C,iBAAiB,CAAC,MAAM,EAAE;QAC9B,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,QAAQ,GAAG,kBAAkB,CAAC;QAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;QACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;KAC5C;iBACgC,MAAM;eAAS,MAAM;gBAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;IAKjF,iBAAiB,CAAC,MAAM,EAAE;QAC9B,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACtE;IAIK,oBAAoB,CAAC,MAAM,EAAE;QACjC,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IAgCK,QAAQ,CAAC,EAAE,EAAE,MAAM;IAInB,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;gBAI9C,OAAO,EAAE;oBAAc,OAAO;;IAKxD,UAAU;gBACgB,OAAO,EAAE;mBAAa,OAAO,EAAE;wBAAkB,OAAO,EAAE;mBAAa,MAAM,EAAE;;IAOzG,gBAAgB;iBACW,MAAM;2BAAqB,MAAM;sBAAgB,MAAM;cAAQ,MAAM;;IAOhG,aAAa;IAMb,eAAe;IAMf,WAAW;iBACgB,OAAO,EAAE;;CAE3C;AAED,sDAAsD;AACtD,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAK/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAqB3E;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAWR"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoShort API HTTP Client
|
|
3
|
+
*
|
|
4
|
+
* Wraps all v1 API endpoints with typed methods.
|
|
5
|
+
* Captures rate limit headers for MCP resource exposure.
|
|
6
|
+
*/
|
|
7
|
+
import { USER_AGENT } from './constants.js';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { basename, extname } from 'node:path';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
export class AutoShortClient {
|
|
12
|
+
apiKey;
|
|
13
|
+
baseUrl;
|
|
14
|
+
/** Last rate limit info from API response headers */
|
|
15
|
+
lastRateLimit = null;
|
|
16
|
+
constructor(apiKey, baseUrl) {
|
|
17
|
+
this.apiKey = apiKey;
|
|
18
|
+
this.baseUrl = baseUrl;
|
|
19
|
+
}
|
|
20
|
+
/** Generic API request with auth, error handling, rate limit capture */
|
|
21
|
+
async request(method, path, body) {
|
|
22
|
+
const url = `${this.baseUrl}${path}`;
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'User-Agent': USER_AGENT,
|
|
29
|
+
},
|
|
30
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
31
|
+
});
|
|
32
|
+
// Capture rate limit headers
|
|
33
|
+
const limit = res.headers.get('x-ratelimit-limit');
|
|
34
|
+
const remaining = res.headers.get('x-ratelimit-remaining');
|
|
35
|
+
const resetAt = res.headers.get('x-ratelimit-reset');
|
|
36
|
+
if (limit && remaining && resetAt) {
|
|
37
|
+
this.lastRateLimit = {
|
|
38
|
+
limit: parseInt(limit),
|
|
39
|
+
remaining: parseInt(remaining),
|
|
40
|
+
resetAt,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
let errorBody = {};
|
|
45
|
+
try {
|
|
46
|
+
errorBody = await res.json();
|
|
47
|
+
}
|
|
48
|
+
catch { /* empty */ }
|
|
49
|
+
const msg = errorBody.error || `API error: ${res.status} ${res.statusText}`;
|
|
50
|
+
const code = errorBody.code || `HTTP_${res.status}`;
|
|
51
|
+
throw new AutoShortApiError(msg, res.status, code);
|
|
52
|
+
}
|
|
53
|
+
return res.json();
|
|
54
|
+
}
|
|
55
|
+
// ── Video endpoints ──
|
|
56
|
+
async generateVideo(params) {
|
|
57
|
+
return this.request('POST', '/api/v1/videos/generate', params);
|
|
58
|
+
}
|
|
59
|
+
async generateChatVideo(params) {
|
|
60
|
+
return this.request('POST', '/api/videos/chat', params);
|
|
61
|
+
}
|
|
62
|
+
async generateProductAd(params) {
|
|
63
|
+
return this.request('POST', '/api/videos/product-ad', params);
|
|
64
|
+
}
|
|
65
|
+
async generateImageToVideo(params) {
|
|
66
|
+
const bytes = await readFile(params.imagePath);
|
|
67
|
+
const form = new FormData();
|
|
68
|
+
form.set('image', new Blob([bytes], { type: sniffImageMime(bytes, params.imagePath) }), basename(params.imagePath));
|
|
69
|
+
form.set('motionPrompt', params.motionPrompt);
|
|
70
|
+
// NO `provider` field, deliberately.
|
|
71
|
+
//
|
|
72
|
+
// The route reads `String(form.get("provider") || IMAGE_TO_VIDEO_POLICY.provider)`, so an
|
|
73
|
+
// absent field already resolves to the one token it accepts. We used to send
|
|
74
|
+
// 'VEO_3_1_OPERATOR' — the endpoint's internal RENDER provider, not its submission token —
|
|
75
|
+
// and so EVERY call came back 400 "Image to Video uses its own flow only; direct Veo API is
|
|
76
|
+
// disabled" (reproduced against production 2026-10-16). Hardcoding the correct literal here
|
|
77
|
+
// would fix today's call and re-couple this standalone package to a server constant it
|
|
78
|
+
// cannot import; omitting the field lets the server own its own default.
|
|
79
|
+
form.set('requestId', imageToVideoRequestId(bytes, params.motionPrompt, params.idempotencyKey));
|
|
80
|
+
const res = await fetch(`${this.baseUrl}/api/videos/image-to-video`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'Authorization': `Bearer ${this.apiKey}`, 'User-Agent': USER_AGENT },
|
|
83
|
+
body: form,
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
const body = await res.json().catch(() => ({}));
|
|
87
|
+
throw new AutoShortApiError(body.error || `API error: ${res.status}`, res.status, `HTTP_${res.status}`);
|
|
88
|
+
}
|
|
89
|
+
return res.json();
|
|
90
|
+
}
|
|
91
|
+
async getVideo(id) {
|
|
92
|
+
return this.request('GET', `/api/v1/videos/${encodeURIComponent(id)}`);
|
|
93
|
+
}
|
|
94
|
+
async listVideos(params) {
|
|
95
|
+
const qs = params ? '?' + new URLSearchParams(Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
96
|
+
return this.request('GET', `/api/v1/videos${qs}`);
|
|
97
|
+
}
|
|
98
|
+
// ── Niches ──
|
|
99
|
+
async listNiches() {
|
|
100
|
+
return this.request('GET', '/api/v1/scripts/niches');
|
|
101
|
+
}
|
|
102
|
+
// ── Credits ──
|
|
103
|
+
async getCreditBalance() {
|
|
104
|
+
return this.request('GET', '/api/v1/credits/balance');
|
|
105
|
+
}
|
|
106
|
+
// ── Usage ──
|
|
107
|
+
async getUsageStats() {
|
|
108
|
+
return this.request('GET', '/api/v1/usage/stats');
|
|
109
|
+
}
|
|
110
|
+
// ── Billing ──
|
|
111
|
+
async getSubscription() {
|
|
112
|
+
return this.request('GET', '/api/v1/billing/subscription');
|
|
113
|
+
}
|
|
114
|
+
// ── API Keys ──
|
|
115
|
+
async listApiKeys() {
|
|
116
|
+
return this.request('GET', '/api/v1/keys');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Typed API error with HTTP status and error code */
|
|
120
|
+
export class AutoShortApiError extends Error {
|
|
121
|
+
status;
|
|
122
|
+
code;
|
|
123
|
+
constructor(message, status, code) {
|
|
124
|
+
super(message);
|
|
125
|
+
this.status = status;
|
|
126
|
+
this.code = code;
|
|
127
|
+
this.name = 'AutoShortApiError';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* MIME from the file's own first bytes, with the extension only as a tiebreaker.
|
|
132
|
+
*
|
|
133
|
+
* It used to be derived from the extension alone, so an extensionless file, a `.jfif` or a
|
|
134
|
+
* `.gif` was declared `image/jpeg` and failed the server's magic-byte check with the unhelpful
|
|
135
|
+
* "Image contents do not match the uploaded file type" / "Invalid image data". Sniffing means a
|
|
136
|
+
* correct file is labelled correctly whatever it is called, and a genuinely unsupported format
|
|
137
|
+
* keeps its real type so the server's 400 names the actual problem.
|
|
138
|
+
*/
|
|
139
|
+
export function sniffImageMime(bytes, imagePath) {
|
|
140
|
+
const b = bytes;
|
|
141
|
+
if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
|
|
142
|
+
return 'image/jpeg';
|
|
143
|
+
if (b.length >= 8 &&
|
|
144
|
+
b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47 &&
|
|
145
|
+
b[4] === 0x0d && b[5] === 0x0a && b[6] === 0x1a && b[7] === 0x0a)
|
|
146
|
+
return 'image/png';
|
|
147
|
+
if (b.length >= 12 &&
|
|
148
|
+
String.fromCharCode(b[0], b[1], b[2], b[3]) === 'RIFF' &&
|
|
149
|
+
String.fromCharCode(b[8], b[9], b[10], b[11]) === 'WEBP')
|
|
150
|
+
return 'image/webp';
|
|
151
|
+
if (b.length >= 6 && String.fromCharCode(b[0], b[1], b[2]) === 'GIF')
|
|
152
|
+
return 'image/gif';
|
|
153
|
+
// Unrecognised: fall back to the extension so the server's refusal names a real type rather
|
|
154
|
+
// than one we invented.
|
|
155
|
+
const extension = extname(imagePath).toLowerCase();
|
|
156
|
+
if (extension === '.png')
|
|
157
|
+
return 'image/png';
|
|
158
|
+
if (extension === '.webp')
|
|
159
|
+
return 'image/webp';
|
|
160
|
+
if (extension === '.gif')
|
|
161
|
+
return 'image/gif';
|
|
162
|
+
return 'application/octet-stream';
|
|
163
|
+
}
|
|
164
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
165
|
+
/**
|
|
166
|
+
* Shape a deterministic hash into something the route's STRICT v4 regex accepts.
|
|
167
|
+
*
|
|
168
|
+
* `requestId` is validated against
|
|
169
|
+
* /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, so a bare sha256
|
|
170
|
+
* is refused with 400 "A valid request ID is required". Version nibble pinned to 4 and the
|
|
171
|
+
* variant nibble forced into 8..b; every other nibble comes from the hash.
|
|
172
|
+
*/
|
|
173
|
+
export function uuidFromSeed(seed) {
|
|
174
|
+
const h = createHash('sha256').update(seed).digest('hex');
|
|
175
|
+
const variant = ((parseInt(h[16], 16) & 0x3) | 0x8).toString(16);
|
|
176
|
+
const id = `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-${variant}${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
177
|
+
/* c8 ignore next */
|
|
178
|
+
if (!UUID_V4.test(id))
|
|
179
|
+
throw new Error(`derived request id is not a v4 UUID: ${id}`);
|
|
180
|
+
return id;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The request id for one image-to-video submission: deterministic, and ALWAYS content-bound.
|
|
184
|
+
*
|
|
185
|
+
* Two rules, each closing a way of being charged 35 credits twice.
|
|
186
|
+
*
|
|
187
|
+
* 1. NO CLOCK IN THE SEED. It used to be `randomUUID()` per call, which defeats all three of
|
|
188
|
+
* the endpoint's dedupe layers (pre-check, pg_advisory_xact_lock, in-transaction re-check)
|
|
189
|
+
* and buys a second clip on every retry. A later attempt salted the seed with a UTC day
|
|
190
|
+
* bucket, which is worse than it looks: a bucket has an edge, so a retry at 23:59:59 and
|
|
191
|
+
* its follow-up at 00:00:01 derive DIFFERENT ids and miss every layer. The time bound
|
|
192
|
+
* belongs on the READER, as a continuous window — see imageToVideoDedupeSince() in
|
|
193
|
+
* src/lib/image-to-video-policy.ts.
|
|
194
|
+
*
|
|
195
|
+
* 2. THE CONTENT IS ALWAYS IN THE SEED, even when the caller supplies a key. A caller key used
|
|
196
|
+
* to replace the content seed outright, so an agent passing one run-scoped key for two
|
|
197
|
+
* different clips got the FIRST video back for the second ask — `duplicate: true`, no
|
|
198
|
+
* fingerprint compared, so not even a 422 to tell it apart. Folding both in keeps each
|
|
199
|
+
* direction working: a distinct key separates two deliberate clips from identical inputs,
|
|
200
|
+
* and distinct inputs separate two clips that happen to share a key.
|
|
201
|
+
*/
|
|
202
|
+
export function imageToVideoRequestId(imageBytes, motionPrompt, idempotencyKey) {
|
|
203
|
+
// TRIMMED, to match what the server stores and dedupes on.
|
|
204
|
+
//
|
|
205
|
+
// The route does `String(form.get("motionPrompt") || "").trim()`, so it treats "push in" and
|
|
206
|
+
// "push in " as the same prompt. Hashing the raw string here did not: the two derived
|
|
207
|
+
// DIFFERENT requestIds, missed all three server dedupe layers, and bought two 35-credit
|
|
208
|
+
// charges for two identical clips. An agent replaying a retry with whitespace drift is the
|
|
209
|
+
// realistic caller. Any normalization the server applies has to be applied here too, or the
|
|
210
|
+
// derived id stops being a function of what the server actually keys on.
|
|
211
|
+
const content = `${createHash('sha256').update(imageBytes).digest('hex')}:${motionPrompt.trim()}`;
|
|
212
|
+
return uuidFromSeed(idempotencyKey ? `${idempotencyKey}\u0000${content}` : content);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default AutoShort API base URL */
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://autoshort.org";
|
|
3
|
+
/** MCP server version */
|
|
4
|
+
export declare const VERSION = "2.0.0";
|
|
5
|
+
/** User-Agent header for API requests */
|
|
6
|
+
export declare const USER_AGENT = "autoshort-mcp-server/2.0.0";
|
|
7
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,yBAAyB;AACzB,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,yCAAyC;AACzC,eAAO,MAAM,UAAU,+BAAoC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default AutoShort API base URL */
|
|
2
|
+
export const DEFAULT_BASE_URL = 'https://autoshort.org';
|
|
3
|
+
/** MCP server version */
|
|
4
|
+
export const VERSION = '2.0.0'; // 2.0.0: webhook tools and the webhookUrl param removed
|
|
5
|
+
/** User-Agent header for API requests */
|
|
6
|
+
export const USER_AGENT = `autoshort-mcp-server/${VERSION}`;
|
|
7
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD,yBAAyB;AACzB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,wDAAwD;AAExF,yCAAyC;AACzC,MAAM,CAAC,MAAM,UAAU,GAAG,wBAAwB,OAAO,EAAE,CAAC"}
|