getagenthook 1.0.0 → 1.0.1

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # agenthook
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/getagenthook?color=c6fe1e&label=getagenthook&style=flat-square)](https://www.npmjs.com/package/getagenthook)
4
+
3
5
  CLI for the AgentHook hosted media-generation API. Built for agents:
4
6
  progress goes to stderr, output URLs go to stdout, exit codes are meaningful.
5
7
 
@@ -18,7 +20,7 @@ agenthook history
18
20
 
19
21
  - Credentials live at `~/.agenthook/credentials.json` (chmod 600).
20
22
  - API base: `--api-url` flag > `AGENTHOOK_API_URL` env > stored value >
21
- `https://localhost:3000`; all requests hit `<base>/api/v1/…`.
23
+ `https://getagenthook.com`; all requests hit `<base>/api/v1/…`.
22
24
  - Reference images (`--ref`, repeatable) require `--owns-references`: you attest
23
25
  you own, or have the rights to use, the likeness of every person appearing in
24
26
  the referenced images.
@@ -27,4 +29,28 @@ agenthook history
27
29
  submitted: consent, prompt length caps, enum values, ranges, and
28
30
  `nano-banana-2` (edit-only) without references.
29
31
 
30
- Develop: `npm run build` (emits `dist/`), `npx vitest run`, `npx tsc --noEmit`.
32
+ ## MCP server
33
+
34
+ If your agent speaks MCP, it can call AgentHook directly instead of going through
35
+ the CLI. The server ships as `@getagenthook/mcp` and runs over `npx`. Add this to
36
+ your MCP client config (Claude Code, Cursor, and similar):
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "agenthook": {
42
+ "command": "npx",
43
+ "args": ["-y", "@getagenthook/mcp"],
44
+ "env": { "AGENTHOOK_API_KEY": "ah_your_key" }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ It exposes `make_video`, `make_image`, `caption_video`, and `create_influencer`,
51
+ plus `get_run`. The generation tools return a `run_id`; pass it to `get_run` to
52
+ fetch the finished URL once the job is done.
53
+
54
+ ## Develop
55
+
56
+ `npm run build` (emits `dist/`), `npx vitest run`, `npx tsc --noEmit`.
@@ -38,40 +38,12 @@ const crypto = __importStar(require("node:crypto"));
38
38
  const args_1 = require("../args");
39
39
  const config_1 = require("../config");
40
40
  const exit_1 = require("../exit");
41
+ const flags_1 = require("../flags");
41
42
  const http_1 = require("../http");
42
43
  const jobs_1 = require("../jobs");
43
44
  const schemas_1 = require("../schemas");
44
45
  const types_1 = require("../types");
45
46
  const validate_1 = require("../validate");
46
- const RUN_FLAGS = {
47
- "api-url": "string",
48
- key: "string",
49
- json: "boolean",
50
- "dry-run": "boolean",
51
- prompt: "string",
52
- ref: "array",
53
- "owns-references": "boolean",
54
- model: "string",
55
- quality: "string",
56
- duration: "number",
57
- "aspect-ratio": "string",
58
- "no-audio": "boolean",
59
- captions: "boolean",
60
- "caption-style": "string",
61
- "caption-size": "string",
62
- "caption-placement": "string",
63
- "enhance-prompt": "boolean",
64
- "video-url": "string",
65
- style: "string",
66
- size: "string",
67
- placement: "string",
68
- count: "number",
69
- resolution: "string",
70
- language: "string",
71
- name: "string",
72
- slug: "string",
73
- influencer: "string",
74
- };
75
47
  // Poll cadence: 5s (spec); overridable for tests. Every watcher gets a
76
48
  // give-up (donor rule): a wall-clock deadline + bounded consecutive misses.
77
49
  const pollIntervalMs = () => Number(process.env.AGENTHOOK_POLL_MS || 5000);
@@ -81,7 +53,14 @@ const MAX_POLL_MISSES = 5;
81
53
  // submit must never double-charge; the server dedupes on the key).
82
54
  const SUBMIT_RETRIES = 1;
83
55
  async function run(argv) {
84
- const { positionals, flags, errors } = (0, args_1.parseArgs)(argv, RUN_FLAGS);
56
+ // Flags are DERIVED from the live tool schema (so a new API param is a usable
57
+ // flag without a CLI upgrade), and the schema source is per-api-url — so the
58
+ // api-url is pulled from argv BEFORE the full parse, the schema is resolved,
59
+ // then the flag spec is built to parse against.
60
+ const apiUrl = (0, config_1.resolveApiUrl)(preScanApiUrl(argv));
61
+ const schemas = await (0, schemas_1.resolveRunSchemas)(apiUrl);
62
+ const { spec, paramForFlag, flagFor } = (0, flags_1.deriveRunFlags)(schemas);
63
+ const { positionals, flags, errors } = (0, args_1.parseArgs)(argv, spec);
85
64
  if (errors.length) {
86
65
  errors.forEach((e) => console.error(e));
87
66
  return exit_1.EXIT.GENERIC;
@@ -92,19 +71,16 @@ async function run(argv) {
92
71
  console.error("Usage: agenthook run <tool> [flags] — see `agenthook tools`");
93
72
  return exit_1.EXIT.GENERIC;
94
73
  }
95
- const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
96
74
  const key = (0, config_1.resolveApiKey)(flags["key"]);
97
75
  if (!key) {
98
76
  emitError(asJson, "Not logged in — run `agenthook auth:login` first.", exit_1.EXIT.AUTH);
99
77
  return exit_1.EXIT.AUTH;
100
78
  }
101
- // Deterministic pre-validation BEFORE any network call (spec §3) — schemas
102
- // resolve locally in every case: cached /v1/tools fetch if one exists,
103
- // otherwise the bundled snapshot (fresh install, offline). The server
104
- // re-validates authoritatively on submit.
105
- const schemas = (0, schemas_1.getLocalToolSchemas)(apiUrl);
106
- const input = (0, validate_1.buildToolInput)(flags);
107
- const problems = (0, validate_1.preValidate)(tool, input, schemas);
79
+ // Deterministic pre-validation BEFORE the run is submitted (spec §3): every
80
+ // locally-checkable rule the server would 400 on. The server re-validates
81
+ // authoritatively on submit.
82
+ const input = (0, validate_1.buildToolInput)(flags, paramForFlag);
83
+ const problems = (0, validate_1.preValidate)(tool, input, schemas, flagFor);
108
84
  if (problems.length) {
109
85
  if (asJson)
110
86
  emitError(true, problems.join("; "), exit_1.EXIT.VALIDATION);
@@ -235,6 +211,19 @@ async function submitWithRetry(apiUrl, tool, input, key, idempotencyKey) {
235
211
  }
236
212
  }
237
213
  }
214
+ /** Pull --api-url out of raw argv before the flag spec exists (spec derivation
215
+ * needs the schema, fetched per api-url). Mirrors parseArgs' two forms; full
216
+ * precedence (flag > env > file > default) is applied by resolveApiUrl. */
217
+ function preScanApiUrl(argv) {
218
+ for (let i = 0; i < argv.length; i++) {
219
+ const a = argv[i];
220
+ if (a === "--api-url")
221
+ return argv[i + 1];
222
+ if (a.startsWith("--api-url="))
223
+ return a.slice("--api-url=".length);
224
+ }
225
+ return undefined;
226
+ }
238
227
  const topUpUrl = (apiUrl) => `${apiUrl}/credits`;
239
228
  /** Machine JSON to stdout (one object per line, ndjson). */
240
229
  function emit(obj) {
@@ -4,9 +4,9 @@ exports.tools = tools;
4
4
  const args_1 = require("../args");
5
5
  const config_1 = require("../config");
6
6
  const exit_1 = require("../exit");
7
+ const flags_1 = require("../flags");
7
8
  const json_error_1 = require("../json-error");
8
9
  const schemas_1 = require("../schemas");
9
- const validate_1 = require("../validate");
10
10
  async function tools(argv) {
11
11
  const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string", key: "string", json: "boolean" });
12
12
  if (errors.length) {
@@ -22,10 +22,13 @@ async function tools(argv) {
22
22
  console.log(JSON.stringify({ tools: schemas }));
23
23
  return exit_1.EXIT.OK;
24
24
  }
25
+ // Same derivation `run` parses against — the flag spelling shown here is
26
+ // exactly the one that will work.
27
+ const { flagFor } = (0, flags_1.deriveRunFlags)(schemas);
25
28
  for (const tool of schemas) {
26
29
  console.log(`${tool.name} — ${tool.description}`);
27
30
  for (const [name, spec] of Object.entries(tool.params)) {
28
- console.log(` ${renderParam(name, spec)}`);
31
+ console.log(` ${renderParam(name, spec, flagFor)}`);
29
32
  }
30
33
  console.log("");
31
34
  }
@@ -33,8 +36,8 @@ async function tools(argv) {
33
36
  return exit_1.EXIT.OK;
34
37
  });
35
38
  }
36
- function renderParam(name, spec) {
37
- const flagName = validate_1.FLAG_FOR[name] ?? `--${name.replace(/_/g, "-")}`;
39
+ function renderParam(name, spec, flagFor) {
40
+ const flagName = flagFor[name] ?? `--${name.replace(/_/g, "-")}`;
38
41
  const hint = spec.type === "boolean"
39
42
  ? ""
40
43
  : spec.enum
package/dist/flags.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FLAG_ALIAS = exports.CLI_FLAGS = void 0;
4
+ exports.deriveRunFlags = deriveRunFlags;
5
+ /** Flags that are NOT tool params and must always parse (any command shape). */
6
+ exports.CLI_FLAGS = {
7
+ "api-url": "string",
8
+ key: "string",
9
+ json: "boolean",
10
+ "dry-run": "boolean",
11
+ };
12
+ /** The two places the CLI flag intentionally differs from a mechanical
13
+ * snake→kebab of the param name (everything else is derived). */
14
+ exports.FLAG_ALIAS = {
15
+ reference_images: { flag: "ref" },
16
+ audio: { flag: "no-audio", invertBoolean: true }, // --no-audio sets audio:false
17
+ };
18
+ const FLAG_TYPES = ["string", "number", "boolean", "array"];
19
+ /** Union every tool's params into the run flag spec + flag⇄param maps. Params
20
+ * repeat across tools (same flag name each time) — the union is idempotent. */
21
+ function deriveRunFlags(schemas) {
22
+ const spec = { ...exports.CLI_FLAGS };
23
+ const paramForFlag = {};
24
+ const flagFor = {};
25
+ for (const schema of schemas) {
26
+ for (const [param, p] of Object.entries(schema.params)) {
27
+ const alias = exports.FLAG_ALIAS[param];
28
+ const flag = alias?.flag ?? param.replace(/_/g, "-");
29
+ let type = FLAG_TYPES.includes(p.type) ? p.type : "string";
30
+ if (alias?.invertBoolean)
31
+ type = "boolean";
32
+ spec[flag] = type;
33
+ paramForFlag[flag] = { param, invert: alias?.invertBoolean };
34
+ flagFor[param] = "--" + flag;
35
+ }
36
+ }
37
+ return { spec, paramForFlag, flagFor };
38
+ }
package/dist/http.js CHANGED
@@ -5,6 +5,7 @@ exports.timeoutSignal = timeoutSignal;
5
5
  exports.api = api;
6
6
  exports.describeApiError = describeApiError;
7
7
  const config_1 = require("./config");
8
+ const version_1 = require("./version");
8
9
  // Timeout policy mirrors the frozen packages/core/net.ts (DEFAULT_TIMEOUT_MS +
9
10
  // AbortSignal.timeout). Duplicated here because the published CLI cannot
10
11
  // runtime-import workspace TypeScript; test/parity.test.ts pins the value.
@@ -36,7 +37,7 @@ async function api(apiUrl, pathname, opts = {}) {
36
37
  if (v !== undefined && v !== "")
37
38
  url.searchParams.set(k, v);
38
39
  }
39
- const headers = { ...opts.headers };
40
+ const headers = { "user-agent": `agenthook-cli/${version_1.VERSION}`, ...opts.headers };
40
41
  if (opts.key)
41
42
  headers.authorization = `Bearer ${opts.key}`;
42
43
  if (opts.body !== undefined)
package/dist/main.js CHANGED
File without changes
package/dist/schemas.js CHANGED
@@ -36,12 +36,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.TOOLS_CACHE_TTL_MS = void 0;
37
37
  exports.toolsCachePath = toolsCachePath;
38
38
  exports.getLocalToolSchemas = getLocalToolSchemas;
39
+ exports.resolveRunSchemas = resolveRunSchemas;
39
40
  exports.getToolSchemas = getToolSchemas;
40
- // Tool-schema source for deterministic pre-validation: GET /v1/tools is the
41
- // single live schema source (the `tools` command fetches + caches it), and
42
- // `run` validates against a purely LOCAL resolution fetched cache if
43
- // present, else the bundled snapshot so pre-validation never makes a
44
- // network call, including on a fresh install (spec §3).
41
+ // Tool-schema source. GET /v1/tools is the single live schema source, and
42
+ // `run` DERIVES its flags from it (a new API param is a usable flag with no CLI
43
+ // upgrade) via resolveRunSchemas: the cached fetch when fresh, a live keyless
44
+ // fetch on miss (public endpoint), and the bundled snapshot as the offline
45
+ // fallback so it stays current online and still works offline. (This relaxes
46
+ // the original spec §3 "no network before submit" guarantee to a cached,
47
+ // at-most-hourly, keyless fetch — the cost of never-stale flags.)
45
48
  const fs = __importStar(require("node:fs"));
46
49
  const path = __importStar(require("node:path"));
47
50
  const config_1 = require("./config");
@@ -72,6 +75,19 @@ function writeCache(cache) {
72
75
  function getLocalToolSchemas(apiUrl) {
73
76
  return readCache()[apiUrl]?.tools ?? schema_snapshot_1.TOOLS_SNAPSHOT;
74
77
  }
78
+ /** Schema source for `run`'s flag derivation + pre-validation. Live-and-cached
79
+ * (GET /v1/tools is PUBLIC — no key) so a new tool param becomes a usable flag
80
+ * without a CLI upgrade; falls back to the last cache, then the bundled
81
+ * snapshot, so it never throws (offline / fresh install still work). The server
82
+ * re-validates authoritatively on submit. */
83
+ async function resolveRunSchemas(apiUrl) {
84
+ try {
85
+ return await getToolSchemas(apiUrl);
86
+ }
87
+ catch {
88
+ return getLocalToolSchemas(apiUrl);
89
+ }
90
+ }
75
91
  /** Cached-first schema load. `fresh: true` (the `tools` command) always
76
92
  * refetches; otherwise a fresh cache entry short-circuits the network, and a
77
93
  * stale one is the fallback when the API is unreachable (the server re-checks
package/dist/validate.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FLAG_FOR = exports.OWNS_REFERENCES_CONSENT = exports.PROMPT_CAPS = void 0;
3
+ exports.OWNS_REFERENCES_CONSENT = exports.PROMPT_CAPS = void 0;
4
4
  exports.buildToolInput = buildToolInput;
5
5
  exports.preValidate = preValidate;
6
6
  // Per-model prompt caps, mirroring packages/core/models.ts promptMax (frozen).
@@ -13,77 +13,25 @@ exports.PROMPT_CAPS = {
13
13
  };
14
14
  exports.OWNS_REFERENCES_CONSENT = "By passing --owns-references you attest that you own, or have the rights to use, " +
15
15
  "the likeness of every person appearing in the referenced images.";
16
- /** CLI flag spelling for each API param for readable error messages.
17
- * test/parity.test.ts asserts every param served by /v1/tools has an entry. */
18
- exports.FLAG_FOR = {
19
- prompt: "--prompt",
20
- reference_images: "--ref",
21
- owns_references: "--owns-references",
22
- model: "--model",
23
- quality: "--quality",
24
- duration: "--duration",
25
- aspect_ratio: "--aspect-ratio",
26
- audio: "--no-audio",
27
- captions: "--captions",
28
- caption_style: "--caption-style",
29
- caption_size: "--caption-size",
30
- caption_placement: "--caption-placement",
31
- enhance_prompt: "--enhance-prompt",
32
- video_url: "--video-url",
33
- style: "--style",
34
- size: "--size",
35
- placement: "--placement",
36
- count: "--count",
37
- resolution: "--resolution",
38
- language: "--language",
39
- name: "--name",
40
- slug: "--slug",
41
- influencer: "--influencer",
42
- };
43
- /** Map parsed CLI flags onto the tool-input body the API expects. Only flags
44
- * the user actually passed are included, so server defaults stay in charge. */
45
- function buildToolInput(flags) {
16
+ /** Map parsed CLI flags onto the tool-input body the API expects, using the
17
+ * flag→param map derived from the tool schema (see flags.ts). Only flags the
18
+ * user actually passed are present in `flags`, so server defaults stay in
19
+ * charge; a global CLI flag (api-url/key/json/dry-run) has no param and is
20
+ * skipped. The one non-direct case is `--no-audio` inverting to audio:false. */
21
+ function buildToolInput(flags, paramForFlag) {
46
22
  const input = {};
47
- const direct = [
48
- ["prompt", "prompt"],
49
- ["model", "model"],
50
- ["quality", "quality"],
51
- ["duration", "duration"],
52
- ["aspect-ratio", "aspect_ratio"],
53
- ["caption-style", "caption_style"],
54
- ["caption-size", "caption_size"],
55
- ["caption-placement", "caption_placement"],
56
- ["video-url", "video_url"],
57
- ["style", "style"],
58
- ["size", "size"],
59
- ["placement", "placement"],
60
- ["count", "count"],
61
- ["resolution", "resolution"],
62
- ["language", "language"],
63
- ["name", "name"],
64
- ["slug", "slug"],
65
- ["influencer", "influencer"],
66
- ];
67
- for (const [flag, param] of direct) {
68
- if (flags[flag] !== undefined)
69
- input[param] = flags[flag];
23
+ for (const [flag, value] of Object.entries(flags)) {
24
+ const mapping = paramForFlag[flag];
25
+ if (!mapping)
26
+ continue; // a global CLI flag, not a tool param
27
+ input[mapping.param] = mapping.invert ? !value : value;
70
28
  }
71
- const refs = flags["ref"];
72
- if (refs?.length)
73
- input.reference_images = refs;
74
- if (flags["owns-references"])
75
- input.owns_references = true;
76
- if (flags["no-audio"])
77
- input.audio = false;
78
- if (flags["captions"])
79
- input.captions = true;
80
- if (flags["enhance-prompt"])
81
- input.enhance_prompt = true;
82
29
  return input;
83
30
  }
84
- const flag = (param) => exports.FLAG_FOR[param] ?? param;
85
- /** Returns human-readable problems (empty array = locally valid). */
86
- function preValidate(tool, input, schemas) {
31
+ /** Returns human-readable problems (empty array = locally valid). `flagFor`
32
+ * (derived from the schema) supplies the CLI flag spelling for messages. */
33
+ function preValidate(tool, input, schemas, flagFor) {
34
+ const flag = (param) => flagFor[param] ?? "--" + param.replace(/_/g, "-");
87
35
  const schema = schemas.find((s) => s.name === tool);
88
36
  if (!schema) {
89
37
  return [`Unknown tool "${tool}". Available tools: ${schemas.map((s) => s.name).join(", ")}`];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getagenthook",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI for the AgentHook hosted media-generation API — generate video, images, and captions from your terminal or agent.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://getagenthook.com",