getagenthook 0.1.0 → 0.2.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/dist/cli.js CHANGED
@@ -1,48 +1,69 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCli = runCli;
4
+ const auth_login_1 = require("./commands/auth-login");
4
5
  const balance_1 = require("./commands/balance");
5
6
  const history_1 = require("./commands/history");
7
+ const influencers_1 = require("./commands/influencers");
8
+ const jobs_1 = require("./commands/jobs");
6
9
  const list_1 = require("./commands/list");
7
10
  const login_1 = require("./commands/login");
8
11
  const run_1 = require("./commands/run");
9
12
  const tools_1 = require("./commands/tools");
13
+ const exit_1 = require("./exit");
10
14
  const http_1 = require("./http");
11
15
  const USAGE = `agenthook — hosted media generation for agents
12
16
 
13
17
  Usage: agenthook <command> [flags]
14
18
 
15
19
  Commands:
16
- login [--key <key>] store your API key (chmod 600)
20
+ auth:login keyless device flow approve in the console
21
+ login [--key <key>] keyless device flow, or store a pasted key
17
22
  tools list tools + parameters (from GET /v1/tools)
18
23
  run <tool> [flags] submit a run, poll every 5s, print output URL(s)
19
24
  list [--tool t] [--status s] [--search q] your past generations
25
+ influencers list your saved influencers (slug / name / portrait)
26
+ influencers:delete <slug> delete a saved influencer
20
27
  balance credit balance
21
28
  history credit ledger
29
+ jobs local run ledger (~/.agenthook/jobs.jsonl)
22
30
 
23
31
  Run flags:
24
32
  --prompt <text> --ref <url> (repeatable) --owns-references --model <m>
25
33
  --quality <standard|pro> --duration <s> --aspect-ratio <r> --no-audio
26
34
  --captions --caption-style <movie|tiktok> --enhance-prompt
27
35
  --video-url <url> --style <movie|tiktok> --count <n> --resolution <1k|2k|4k>
36
+ --dry-run price + validate against the server without spending credits
28
37
 
38
+ Auth: pass --key, set AGENTHOOK_API_KEY, or run auth:login (precedence: flag > env > file).
39
+ --json on run/list/balance/history/tools/jobs emits machine JSON on stdout, progress on stderr.
29
40
  Every command accepts --api-url <url> (or AGENTHOOK_API_URL; default https://getagenthook.com).`;
30
41
  async function runCli(argv) {
31
42
  const [cmd, ...rest] = argv;
32
43
  try {
33
44
  switch (cmd) {
45
+ case "auth:login":
46
+ return await (0, auth_login_1.authLogin)(rest);
34
47
  case "login":
35
- return await (0, login_1.login)(rest);
48
+ // Keyless `login` falls through to the device flow; `login --key <k>`
49
+ // keeps the paste path (still supported for CI/pre-provisioned keys).
50
+ return rest.includes("--key") ? await (0, login_1.login)(rest) : await (0, auth_login_1.authLogin)(rest);
36
51
  case "tools":
37
52
  return await (0, tools_1.tools)(rest);
38
53
  case "run":
39
54
  return await (0, run_1.run)(rest);
40
55
  case "list":
41
56
  return await (0, list_1.list)(rest);
57
+ case "influencers":
58
+ return await (0, influencers_1.influencers)(rest);
59
+ case "influencers:delete":
60
+ return await (0, influencers_1.influencersDelete)(rest);
42
61
  case "balance":
43
62
  return await (0, balance_1.balance)(rest);
44
63
  case "history":
45
64
  return await (0, history_1.history)(rest);
65
+ case "jobs":
66
+ return await (0, jobs_1.jobs)(rest);
46
67
  case undefined:
47
68
  case "help":
48
69
  case "--help":
@@ -57,7 +78,7 @@ async function runCli(argv) {
57
78
  catch (e) {
58
79
  if (e instanceof http_1.ApiError) {
59
80
  console.error((0, http_1.describeApiError)(e));
60
- return 1;
81
+ return (0, exit_1.exitCodeForApiError)(e);
61
82
  }
62
83
  console.error(e instanceof Error ? e.message : String(e));
63
84
  return 1;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.authLogin = authLogin;
37
+ const os = __importStar(require("node:os"));
38
+ const args_1 = require("../args");
39
+ const config_1 = require("../config");
40
+ const exit_1 = require("../exit");
41
+ const http_1 = require("../http");
42
+ // Poll cadence: 5s (spec); AGENTHOOK_POLL_MS override mirrors run.ts:30.
43
+ const pollIntervalMs = () => Number(process.env.AGENTHOOK_POLL_MS || 5000);
44
+ // Keyless device flow: create a session, print the approved shape verbatim,
45
+ // poll until a human approves in the console, then save the minted key.
46
+ // `requesting_host = os.hostname()` so the approve screen can name the agent.
47
+ async function authLogin(argv) {
48
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string" });
49
+ if (errors.length) {
50
+ errors.forEach((e) => console.error(e));
51
+ return exit_1.EXIT.GENERIC;
52
+ }
53
+ const apiUrlFlag = flags["api-url"];
54
+ const apiUrl = (0, config_1.resolveApiUrl)(apiUrlFlag);
55
+ let session;
56
+ try {
57
+ session = await (0, http_1.api)(apiUrl, "/device", {
58
+ method: "POST",
59
+ body: { requesting_host: os.hostname() },
60
+ });
61
+ }
62
+ catch (e) {
63
+ if (e instanceof http_1.ApiError) {
64
+ console.error(`Could not start authorization against ${apiUrl}: ${e.message}`);
65
+ return e.status === 401 || e.status === 403 ? exit_1.EXIT.AUTH : exit_1.EXIT.GENERIC;
66
+ }
67
+ throw e;
68
+ }
69
+ // Approved output shape (verbatim — AC1). Human-facing, so it goes to stdout.
70
+ console.log("To authorize this agent, ask your human to visit:\n\n" +
71
+ ` ${session.device_url}\n` +
72
+ ` and enter code: ${session.user_code}\n\n` +
73
+ "Waiting for approval… (expires in 15m)");
74
+ // Poll GET /device/:pollToken until terminal. A network blip is retried
75
+ // silently until the code expires server-side (poll returns `expired`).
76
+ for (;;) {
77
+ await sleep(pollIntervalMs());
78
+ let poll;
79
+ try {
80
+ poll = await (0, http_1.api)(apiUrl, `/device/${encodeURIComponent(session.poll_token)}`);
81
+ }
82
+ catch (e) {
83
+ if (e instanceof http_1.ApiError && e.status === 404) {
84
+ console.error("Device session not found — it may have expired. Run `agenthook auth:login` again.");
85
+ return exit_1.EXIT.AUTH;
86
+ }
87
+ continue; // transient — keep polling until the server reports terminal state
88
+ }
89
+ switch (poll.status) {
90
+ case "pending":
91
+ continue;
92
+ case "approved": {
93
+ const creds = {
94
+ ...(0, config_1.loadCredentials)(),
95
+ api_key: poll.api_key,
96
+ ...(apiUrlFlag ? { api_url: apiUrl } : {}),
97
+ };
98
+ const p = (0, config_1.saveCredentials)(creds);
99
+ console.log(`✓ Authorized as ${poll.email}\n Key saved to ${p}`);
100
+ return exit_1.EXIT.OK;
101
+ }
102
+ case "expired":
103
+ console.error("The authorization code expired before it was approved. Run `agenthook auth:login` again.");
104
+ return exit_1.EXIT.AUTH;
105
+ case "claimed":
106
+ console.error("This authorization code was already used. Run `agenthook auth:login` again.");
107
+ return exit_1.EXIT.AUTH;
108
+ }
109
+ }
110
+ }
111
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -3,22 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.balance = balance;
4
4
  const args_1 = require("../args");
5
5
  const config_1 = require("../config");
6
+ const exit_1 = require("../exit");
6
7
  const http_1 = require("../http");
8
+ const json_error_1 = require("../json-error");
7
9
  async function balance(argv) {
8
- const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string" });
10
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string", key: "string", json: "boolean" });
9
11
  if (errors.length) {
10
12
  errors.forEach((e) => console.error(e));
11
- return 1;
13
+ return exit_1.EXIT.GENERIC;
12
14
  }
13
- const key = (0, config_1.storedApiKey)();
15
+ const asJson = flags["json"] === true;
16
+ const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
17
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
14
18
  if (!key) {
15
- console.error("Not logged in — run `agenthook login` first.");
16
- return 1;
19
+ console.error("Not logged in — run `agenthook auth:login` first.");
20
+ return exit_1.EXIT.AUTH;
17
21
  }
18
- const me = await (0, http_1.api)((0, config_1.resolveApiUrl)(flags["api-url"]), "/me", { key });
19
- console.log(`User: ${me.user_id}`);
20
- console.log(`Balance: ${me.balance} credits`);
21
- if (me.suspended)
22
- console.error("Account suspended — new runs are refused.");
23
- return 0;
22
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
23
+ const me = await (0, http_1.api)(apiUrl, "/me", { key });
24
+ if (asJson) {
25
+ console.log(JSON.stringify(me));
26
+ }
27
+ else {
28
+ console.log(`User: ${me.user_id}`);
29
+ console.log(`Balance: ${me.balance} credits`);
30
+ if (me.suspended)
31
+ console.error("Account suspended — new runs are refused.");
32
+ }
33
+ return exit_1.EXIT.OK;
34
+ });
24
35
  }
@@ -3,33 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.history = history;
4
4
  const args_1 = require("../args");
5
5
  const config_1 = require("../config");
6
+ const exit_1 = require("../exit");
6
7
  const http_1 = require("../http");
8
+ const json_error_1 = require("../json-error");
7
9
  const render_1 = require("../render");
8
10
  async function history(argv) {
9
- const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string" });
11
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string", key: "string", json: "boolean" });
10
12
  if (errors.length) {
11
13
  errors.forEach((e) => console.error(e));
12
- return 1;
13
- }
14
- const key = (0, config_1.storedApiKey)();
15
- if (!key) {
16
- console.error("Not logged in — run `agenthook login` first.");
17
- return 1;
14
+ return exit_1.EXIT.GENERIC;
18
15
  }
16
+ const asJson = flags["json"] === true;
19
17
  const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
20
- const res = await (0, http_1.api)(apiUrl, "/credits/history", { key });
21
- if (res.entries.length === 0) {
22
- console.log("No credit activity yet.");
23
- }
24
- else {
25
- const rows = res.entries.map((e) => [
26
- e.created_at,
27
- e.reason,
28
- e.delta > 0 ? `+${e.delta}` : String(e.delta),
29
- e.run_id ?? "-",
30
- ]);
31
- console.log((0, render_1.table)(["CREATED", "REASON", "DELTA", "RUN"], rows));
18
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
19
+ if (!key) {
20
+ console.error("Not logged in — run `agenthook auth:login` first.");
21
+ return exit_1.EXIT.AUTH;
32
22
  }
33
- console.log(`Balance: ${res.balance} credits`);
34
- return 0;
23
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
24
+ const res = await (0, http_1.api)(apiUrl, "/credits/history", { key });
25
+ if (asJson) {
26
+ console.log(JSON.stringify(res));
27
+ return exit_1.EXIT.OK;
28
+ }
29
+ if (res.entries.length === 0) {
30
+ console.log("No credit activity yet.");
31
+ }
32
+ else {
33
+ const rows = res.entries.map((e) => [
34
+ e.created_at,
35
+ e.reason,
36
+ e.delta > 0 ? `+${e.delta}` : String(e.delta),
37
+ e.run_id ?? "-",
38
+ ]);
39
+ console.log((0, render_1.table)(["CREATED", "REASON", "DELTA", "RUN"], rows));
40
+ }
41
+ console.log(`Balance: ${res.balance} credits`);
42
+ return exit_1.EXIT.OK;
43
+ });
35
44
  }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.influencers = influencers;
4
+ exports.influencersDelete = influencersDelete;
5
+ const args_1 = require("../args");
6
+ const config_1 = require("../config");
7
+ const exit_1 = require("../exit");
8
+ const http_1 = require("../http");
9
+ const json_error_1 = require("../json-error");
10
+ const render_1 = require("../render");
11
+ const AUTH_FLAGS = { "api-url": "string", key: "string", json: "boolean" };
12
+ /** `agenthook influencers` — GET /v1/influencers, the account's roster. */
13
+ async function influencers(argv) {
14
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { ...AUTH_FLAGS });
15
+ if (errors.length) {
16
+ errors.forEach((e) => console.error(e));
17
+ return exit_1.EXIT.GENERIC;
18
+ }
19
+ const asJson = flags["json"] === true;
20
+ const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
21
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
22
+ if (!key) {
23
+ console.error("Not logged in — run `agenthook auth:login` first.");
24
+ return exit_1.EXIT.AUTH;
25
+ }
26
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
27
+ const res = await (0, http_1.api)(apiUrl, "/influencers", { key });
28
+ if (asJson) {
29
+ console.log(JSON.stringify(res));
30
+ return exit_1.EXIT.OK;
31
+ }
32
+ if (res.influencers.length === 0) {
33
+ console.log("No influencers yet — create one with `agenthook run create_influencer`.");
34
+ return exit_1.EXIT.OK;
35
+ }
36
+ const rows = res.influencers.map((i) => [i.slug, i.name, i.portrait_url]);
37
+ console.log((0, render_1.table)(["SLUG", "NAME", "PORTRAIT"], rows));
38
+ return exit_1.EXIT.OK;
39
+ });
40
+ }
41
+ /** `agenthook influencers:delete <slug>` — DELETE /v1/influencers/:slug. */
42
+ async function influencersDelete(argv) {
43
+ const { positionals, flags, errors } = (0, args_1.parseArgs)(argv, { ...AUTH_FLAGS });
44
+ if (errors.length) {
45
+ errors.forEach((e) => console.error(e));
46
+ return exit_1.EXIT.GENERIC;
47
+ }
48
+ const asJson = flags["json"] === true;
49
+ const slug = positionals[0];
50
+ if (!slug) {
51
+ console.error("Usage: agenthook influencers:delete <slug>");
52
+ return exit_1.EXIT.GENERIC;
53
+ }
54
+ const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
55
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
56
+ if (!key) {
57
+ console.error("Not logged in — run `agenthook auth:login` first.");
58
+ return exit_1.EXIT.AUTH;
59
+ }
60
+ try {
61
+ await (0, http_1.api)(apiUrl, `/influencers/${encodeURIComponent(slug)}`, {
62
+ method: "DELETE",
63
+ key,
64
+ });
65
+ }
66
+ catch (e) {
67
+ // A 404 gets a clear, slug-named message; every other ApiError maps through
68
+ // the frozen taxonomy (401/403 → AUTH exit 2) via guardJson's re-emission.
69
+ if (e instanceof http_1.ApiError && e.status === 404) {
70
+ if (asJson)
71
+ console.log(JSON.stringify({ error: `No influencer with slug "${slug}".`, exit_code: exit_1.EXIT.GENERIC }));
72
+ else
73
+ console.error(`No influencer with slug "${slug}".`);
74
+ return exit_1.EXIT.GENERIC;
75
+ }
76
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
77
+ throw e;
78
+ });
79
+ }
80
+ if (asJson)
81
+ console.log(JSON.stringify({ deleted: true }));
82
+ else
83
+ console.log(`Deleted ${slug}.`);
84
+ return exit_1.EXIT.OK;
85
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jobs = jobs;
4
+ const args_1 = require("../args");
5
+ const exit_1 = require("../exit");
6
+ const jobs_1 = require("../jobs");
7
+ const render_1 = require("../render");
8
+ // Reads the local jobs ledger (<config-dir>/jobs.jsonl) — the CLI's own record
9
+ // of every submit + terminal event. Offline, no network, no key needed.
10
+ async function jobs(argv) {
11
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { json: "boolean" });
12
+ if (errors.length) {
13
+ errors.forEach((e) => console.error(e));
14
+ return exit_1.EXIT.GENERIC;
15
+ }
16
+ const entries = (0, jobs_1.readJobs)();
17
+ if (flags["json"] === true) {
18
+ for (const e of entries)
19
+ console.log(JSON.stringify(e));
20
+ return exit_1.EXIT.OK;
21
+ }
22
+ if (entries.length === 0) {
23
+ console.log("No jobs recorded yet.");
24
+ return exit_1.EXIT.OK;
25
+ }
26
+ const rows = entries.map((e) => [
27
+ e.ts,
28
+ e.run_id,
29
+ e.tool,
30
+ e.status,
31
+ (0, render_1.truncate)(e.output[0] ?? "", 48),
32
+ ]);
33
+ console.log((0, render_1.table)(["TS", "RUN", "TOOL", "STATUS", "OUTPUT"], rows));
34
+ return exit_1.EXIT.OK;
35
+ }
@@ -3,47 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.list = list;
4
4
  const args_1 = require("../args");
5
5
  const config_1 = require("../config");
6
+ const exit_1 = require("../exit");
6
7
  const http_1 = require("../http");
8
+ const json_error_1 = require("../json-error");
7
9
  const render_1 = require("../render");
8
10
  async function list(argv) {
9
11
  const { flags, errors } = (0, args_1.parseArgs)(argv, {
10
12
  "api-url": "string",
13
+ key: "string",
14
+ json: "boolean",
11
15
  tool: "string",
12
16
  status: "string",
13
17
  search: "string",
14
18
  });
15
19
  if (errors.length) {
16
20
  errors.forEach((e) => console.error(e));
17
- return 1;
21
+ return exit_1.EXIT.GENERIC;
18
22
  }
23
+ const asJson = flags["json"] === true;
19
24
  const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
20
- const key = (0, config_1.storedApiKey)();
25
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
21
26
  if (!key) {
22
- console.error("Not logged in — run `agenthook login` first.");
23
- return 1;
27
+ console.error("Not logged in — run `agenthook auth:login` first.");
28
+ return exit_1.EXIT.AUTH;
24
29
  }
25
- const res = await (0, http_1.api)(apiUrl, "/generations", {
26
- key,
27
- query: {
28
- tool: flags["tool"],
29
- status: flags["status"],
30
- q: flags["search"],
31
- },
30
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
31
+ const res = await (0, http_1.api)(apiUrl, "/generations", {
32
+ key,
33
+ query: {
34
+ tool: flags["tool"],
35
+ status: flags["status"],
36
+ q: flags["search"],
37
+ },
38
+ });
39
+ if (asJson) {
40
+ console.log(JSON.stringify(res));
41
+ return exit_1.EXIT.OK;
42
+ }
43
+ if (res.runs.length === 0) {
44
+ console.log("No generations found.");
45
+ return exit_1.EXIT.OK;
46
+ }
47
+ const rows = res.runs.map((r) => [
48
+ r.id,
49
+ r.tool,
50
+ r.status,
51
+ r.created_at,
52
+ String(r.credits_charged),
53
+ r.output[0] ?? (r.error ? (0, render_1.truncate)(r.error, 40) : (0, render_1.truncate)(r.prompt ?? "", 40)),
54
+ ]);
55
+ console.log((0, render_1.table)(["ID", "TOOL", "STATUS", "CREATED", "CREDITS", "OUTPUT"], rows));
56
+ if (res.next_cursor)
57
+ console.error("(more results available — narrow with --tool/--status/--search)");
58
+ return exit_1.EXIT.OK;
32
59
  });
33
- if (res.runs.length === 0) {
34
- console.log("No generations found.");
35
- return 0;
36
- }
37
- const rows = res.runs.map((r) => [
38
- r.id,
39
- r.tool,
40
- r.status,
41
- r.created_at,
42
- String(r.credits_charged),
43
- r.output[0] ?? (r.error ? (0, render_1.truncate)(r.error, 40) : (0, render_1.truncate)(r.prompt ?? "", 40)),
44
- ]);
45
- console.log((0, render_1.table)(["ID", "TOOL", "STATUS", "CREATED", "CREDITS", "OUTPUT"], rows));
46
- if (res.next_cursor)
47
- console.error("(more results available — narrow with --tool/--status/--search)");
48
- return 0;
49
60
  }
@@ -1,13 +1,53 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.run = run;
37
+ const crypto = __importStar(require("node:crypto"));
4
38
  const args_1 = require("../args");
5
39
  const config_1 = require("../config");
40
+ const exit_1 = require("../exit");
6
41
  const http_1 = require("../http");
42
+ const jobs_1 = require("../jobs");
7
43
  const schemas_1 = require("../schemas");
44
+ const types_1 = require("../types");
8
45
  const validate_1 = require("../validate");
9
46
  const RUN_FLAGS = {
10
47
  "api-url": "string",
48
+ key: "string",
49
+ json: "boolean",
50
+ "dry-run": "boolean",
11
51
  prompt: "string",
12
52
  ref: "array",
13
53
  "owns-references": "boolean",
@@ -24,28 +64,35 @@ const RUN_FLAGS = {
24
64
  count: "number",
25
65
  resolution: "string",
26
66
  language: "string",
67
+ name: "string",
68
+ slug: "string",
69
+ influencer: "string",
27
70
  };
28
71
  // Poll cadence: 5s (spec); overridable for tests. Every watcher gets a
29
72
  // give-up (donor rule): a wall-clock deadline + bounded consecutive misses.
30
73
  const pollIntervalMs = () => Number(process.env.AGENTHOOK_POLL_MS || 5000);
31
74
  const POLL_DEADLINE_MS = 60 * 60 * 1000; // the server sweep fails stuck runs at 45min; we outlast it
32
75
  const MAX_POLL_MISSES = 5;
76
+ // One transient re-submit with the SAME idempotency key (spec AC7 — a retried
77
+ // submit must never double-charge; the server dedupes on the key).
78
+ const SUBMIT_RETRIES = 1;
33
79
  async function run(argv) {
34
80
  const { positionals, flags, errors } = (0, args_1.parseArgs)(argv, RUN_FLAGS);
35
81
  if (errors.length) {
36
82
  errors.forEach((e) => console.error(e));
37
- return 1;
83
+ return exit_1.EXIT.GENERIC;
38
84
  }
85
+ const asJson = flags["json"] === true;
39
86
  const tool = positionals[0];
40
87
  if (!tool) {
41
88
  console.error("Usage: agenthook run <tool> [flags] — see `agenthook tools`");
42
- return 1;
89
+ return exit_1.EXIT.GENERIC;
43
90
  }
44
91
  const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
45
- const key = (0, config_1.storedApiKey)();
92
+ const key = (0, config_1.resolveApiKey)(flags["key"]);
46
93
  if (!key) {
47
- console.error("Not logged in — run `agenthook login` first.");
48
- return 1;
94
+ emitError(asJson, "Not logged in — run `agenthook auth:login` first.", exit_1.EXIT.AUTH);
95
+ return exit_1.EXIT.AUTH;
49
96
  }
50
97
  // Deterministic pre-validation BEFORE any network call (spec §3) — schemas
51
98
  // resolve locally in every case: cached /v1/tools fetch if one exists,
@@ -55,28 +102,66 @@ async function run(argv) {
55
102
  const input = (0, validate_1.buildToolInput)(flags);
56
103
  const problems = (0, validate_1.preValidate)(tool, input, schemas);
57
104
  if (problems.length) {
58
- problems.forEach((p) => console.error(p));
59
- return 1;
105
+ if (asJson)
106
+ emitError(true, problems.join("; "), exit_1.EXIT.VALIDATION);
107
+ else
108
+ problems.forEach((p) => console.error(p));
109
+ return exit_1.EXIT.VALIDATION;
110
+ }
111
+ // Free pre-flight: POST with `dry_run` so the server prices + validates
112
+ // authoritatively without creating a run or debiting. No idempotency key
113
+ // (nothing is charged) and no poll — the priced response is terminal.
114
+ if (flags["dry-run"] === true) {
115
+ let dry;
116
+ try {
117
+ dry = await (0, http_1.api)(apiUrl, `/tools/${encodeURIComponent(tool)}/run`, {
118
+ method: "POST",
119
+ key,
120
+ body: { ...input, dry_run: true },
121
+ });
122
+ }
123
+ catch (e) {
124
+ if (e instanceof http_1.ApiError) {
125
+ const code = (0, exit_1.exitCodeForApiError)(e);
126
+ if (asJson)
127
+ emitError(true, e.message, code, e.status === 402 ? topUpUrl(apiUrl) : undefined);
128
+ else
129
+ console.error((0, http_1.describeApiError)(e));
130
+ return code;
131
+ }
132
+ throw e;
133
+ }
134
+ if (asJson)
135
+ emit(dry);
136
+ else
137
+ console.log(`Would cost ${dry.credits_required} credits${dry.model ? ` (model ${dry.model})` : ""}. No credits charged.`);
138
+ return exit_1.EXIT.OK;
60
139
  }
61
- // Submit.
140
+ // Idempotency key generated once and reused across transient re-submits, so
141
+ // a network hiccup mid-submit can retry without a second debit (spec AC5/AC7).
142
+ const idempotencyKey = crypto.randomUUID();
62
143
  let created;
63
144
  try {
64
- created = await (0, http_1.api)(apiUrl, `/tools/${encodeURIComponent(tool)}/run`, {
65
- method: "POST",
66
- key,
67
- body: input,
68
- });
145
+ created = await submitWithRetry(apiUrl, tool, input, key, idempotencyKey);
69
146
  }
70
147
  catch (e) {
71
148
  if (e instanceof http_1.ApiError) {
72
- console.error((0, http_1.describeApiError)(e));
73
- return 1;
149
+ const code = (0, exit_1.exitCodeForApiError)(e);
150
+ if (asJson)
151
+ emitError(true, e.message, code, e.status === 402 ? topUpUrl(apiUrl) : undefined);
152
+ else
153
+ console.error((0, http_1.describeApiError)(e));
154
+ return code;
74
155
  }
75
156
  throw e;
76
157
  }
158
+ // Submit ledger row + submit JSON object (first ndjson line).
159
+ (0, jobs_1.appendJob)({ ts: new Date().toISOString(), run_id: created.run_id, tool, status: "submit", output: [] });
160
+ if (asJson)
161
+ emit(created);
77
162
  console.error(`Run ${created.run_id} submitted (${created.credits_charged} credits). Polling…`);
78
- // Poll until terminal. Progress goes to stderr; output URLs alone go to
79
- // stdout so agents/scripts can capture them cleanly.
163
+ // Poll until terminal. In --json mode the terminal RunResponse is the second
164
+ // ndjson line on stdout; otherwise output URLs alone go to stdout.
80
165
  const deadline = Date.now() + POLL_DEADLINE_MS;
81
166
  let lastStatus = created.status;
82
167
  let misses = 0;
@@ -89,14 +174,14 @@ async function run(argv) {
89
174
  }
90
175
  catch (e) {
91
176
  if (e instanceof http_1.ApiError && e.status === 404) {
92
- console.error(`Run ${created.run_id} no longer exists (404).`);
93
- return 1;
177
+ emitError(asJson, `Run ${created.run_id} no longer exists (404).`, exit_1.EXIT.GENERIC);
178
+ return exit_1.EXIT.GENERIC;
94
179
  }
95
180
  misses++;
96
181
  if (misses >= MAX_POLL_MISSES) {
97
- console.error(`Lost contact with the API (${MAX_POLL_MISSES} consecutive failures: ${e.message}).\n` +
98
- `The run may still finish — check later with: agenthook list`);
99
- return 1;
182
+ emitError(asJson, `Lost contact with the API (${MAX_POLL_MISSES} consecutive failures: ${e.message}). ` +
183
+ `The run may still finish — check later with: agenthook list`, exit_1.EXIT.GENERIC);
184
+ return exit_1.EXIT.GENERIC;
100
185
  }
101
186
  continue;
102
187
  }
@@ -105,17 +190,57 @@ async function run(argv) {
105
190
  lastStatus = runRow.status;
106
191
  }
107
192
  if (runRow.status === "completed") {
108
- for (const url of runRow.output)
109
- console.log(url);
110
- return 0;
193
+ (0, jobs_1.appendJob)({ ts: new Date().toISOString(), run_id: runRow.id, tool, status: "completed", output: runRow.output });
194
+ if (asJson)
195
+ emit(runRow);
196
+ else
197
+ for (const url of runRow.output)
198
+ console.log(url);
199
+ return exit_1.EXIT.OK;
111
200
  }
112
201
  if (runRow.status === "failed") {
113
- console.error(`Run failed: ${runRow.error ?? "unknown error"} (credits are refunded automatically)`);
114
- return 1;
202
+ (0, jobs_1.appendJob)({ ts: new Date().toISOString(), run_id: runRow.id, tool, status: "failed", output: [] });
203
+ if (asJson)
204
+ emit(runRow);
205
+ else
206
+ console.error(`Run failed: ${runRow.error ?? "unknown error"} (credits are refunded automatically)`);
207
+ return exit_1.EXIT.GENERIC;
115
208
  }
116
209
  }
117
- console.error(`Run ${created.run_id} did not finish within ${POLL_DEADLINE_MS / 60_000} minutes — ` +
118
- `giving up on polling. Check later with: agenthook list`);
119
- return 1;
210
+ emitError(asJson, `Run ${created.run_id} did not finish within ${POLL_DEADLINE_MS / 60_000} minutes — ` +
211
+ `giving up on polling. Check later with: agenthook list`, exit_1.EXIT.GENERIC);
212
+ return exit_1.EXIT.GENERIC;
213
+ }
214
+ /** Submit, retrying transient network failures with the SAME idempotency key. */
215
+ async function submitWithRetry(apiUrl, tool, input, key, idempotencyKey) {
216
+ for (let attempt = 0;; attempt++) {
217
+ try {
218
+ return await (0, http_1.api)(apiUrl, `/tools/${encodeURIComponent(tool)}/run`, {
219
+ method: "POST",
220
+ key,
221
+ body: input,
222
+ headers: { [types_1.IDEMPOTENCY_KEY_HEADER]: idempotencyKey },
223
+ });
224
+ }
225
+ catch (e) {
226
+ // Only retry pure network/timeout failures (status 0); an HTTP error is
227
+ // deterministic and re-sending won't change it.
228
+ if (e instanceof http_1.ApiError && e.status === 0 && attempt < SUBMIT_RETRIES)
229
+ continue;
230
+ throw e;
231
+ }
232
+ }
233
+ }
234
+ const topUpUrl = (apiUrl) => `${apiUrl}/credits`;
235
+ /** Machine JSON to stdout (one object per line, ndjson). */
236
+ function emit(obj) {
237
+ console.log(JSON.stringify(obj));
238
+ }
239
+ /** Human message to stderr, or a JSON error object to stdout under --json. */
240
+ function emitError(asJson, message, code, top_up_url) {
241
+ if (asJson)
242
+ console.log(JSON.stringify({ error: message, exit_code: code, ...(top_up_url ? { top_up_url } : {}) }));
243
+ else
244
+ console.error(message);
120
245
  }
121
246
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -3,26 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.tools = tools;
4
4
  const args_1 = require("../args");
5
5
  const config_1 = require("../config");
6
+ const exit_1 = require("../exit");
7
+ const json_error_1 = require("../json-error");
6
8
  const schemas_1 = require("../schemas");
7
9
  const validate_1 = require("../validate");
8
10
  async function tools(argv) {
9
- const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string" });
11
+ const { flags, errors } = (0, args_1.parseArgs)(argv, { "api-url": "string", key: "string", json: "boolean" });
10
12
  if (errors.length) {
11
13
  errors.forEach((e) => console.error(e));
12
- return 1;
14
+ return exit_1.EXIT.GENERIC;
13
15
  }
16
+ const asJson = flags["json"] === true;
14
17
  const apiUrl = (0, config_1.resolveApiUrl)(flags["api-url"]);
15
- // Always fetch fresh (and warm the cache `run` pre-validates from).
16
- const schemas = await (0, schemas_1.getToolSchemas)(apiUrl, (0, config_1.storedApiKey)(), { fresh: true });
17
- for (const tool of schemas) {
18
- console.log(`${tool.name} — ${tool.description}`);
19
- for (const [name, spec] of Object.entries(tool.params)) {
20
- console.log(` ${renderParam(name, spec)}`);
18
+ return (0, json_error_1.guardJson)(asJson, apiUrl, async () => {
19
+ // Always fetch fresh (and warm the cache `run` pre-validates from).
20
+ const schemas = await (0, schemas_1.getToolSchemas)(apiUrl, (0, config_1.resolveApiKey)(flags["key"]), { fresh: true });
21
+ if (asJson) {
22
+ console.log(JSON.stringify({ tools: schemas }));
23
+ return exit_1.EXIT.OK;
21
24
  }
22
- console.log("");
23
- }
24
- console.log(`Run one with: agenthook run <tool> --prompt "..." [flags]`);
25
- return 0;
25
+ for (const tool of schemas) {
26
+ console.log(`${tool.name} — ${tool.description}`);
27
+ for (const [name, spec] of Object.entries(tool.params)) {
28
+ console.log(` ${renderParam(name, spec)}`);
29
+ }
30
+ console.log("");
31
+ }
32
+ console.log(`Run one with: agenthook run <tool> --prompt "..." [flags]`);
33
+ return exit_1.EXIT.OK;
34
+ });
26
35
  }
27
36
  function renderParam(name, spec) {
28
37
  const flagName = validate_1.FLAG_FOR[name] ?? `--${name.replace(/_/g, "-")}`;
package/dist/config.js CHANGED
@@ -36,10 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DEFAULT_API_URL = exports.BASE_PATH = void 0;
37
37
  exports.configDir = configDir;
38
38
  exports.credentialsPath = credentialsPath;
39
+ exports.jobsPath = jobsPath;
39
40
  exports.loadCredentials = loadCredentials;
40
41
  exports.saveCredentials = saveCredentials;
41
42
  exports.resolveApiUrl = resolveApiUrl;
42
43
  exports.storedApiKey = storedApiKey;
44
+ exports.resolveApiKey = resolveApiKey;
43
45
  const fs = __importStar(require("node:fs"));
44
46
  const os = __importStar(require("node:os"));
45
47
  const path = __importStar(require("node:path"));
@@ -52,6 +54,10 @@ function configDir() {
52
54
  function credentialsPath() {
53
55
  return path.join(configDir(), "credentials.json");
54
56
  }
57
+ /** Local jobs ledger — one JSON line per run submit + terminal event. */
58
+ function jobsPath() {
59
+ return path.join(configDir(), "jobs.jsonl");
60
+ }
55
61
  function loadCredentials() {
56
62
  try {
57
63
  return JSON.parse(fs.readFileSync(credentialsPath(), "utf8"));
@@ -73,6 +79,11 @@ function resolveApiUrl(flagValue) {
73
79
  const url = flagValue || process.env.AGENTHOOK_API_URL || loadCredentials().api_url || exports.DEFAULT_API_URL;
74
80
  return url.replace(/\/+$/, "");
75
81
  }
82
+ /** File-only key (used by `login` to display "already stored"; commands use resolveApiKey). */
76
83
  function storedApiKey() {
77
84
  return loadCredentials().api_key;
78
85
  }
86
+ /** Resolution order: --key flag > AGENTHOOK_API_KEY env > credentials file. */
87
+ function resolveApiKey(flagValue) {
88
+ return flagValue || process.env.AGENTHOOK_API_KEY || loadCredentials().api_key;
89
+ }
package/dist/exit.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXIT = void 0;
4
+ exports.exitCodeForApiError = exitCodeForApiError;
5
+ exports.EXIT = {
6
+ OK: 0,
7
+ GENERIC: 1, // any uncategorized failure (network, bad args, run failed)
8
+ AUTH: 2, // 401/403, and a device session that expired/was already claimed
9
+ VALIDATION: 3, // 400 — malformed request the server rejected
10
+ INSUFFICIENT_CREDITS: 4, // 402 — out of credits (carries top_up_url in --json)
11
+ };
12
+ /** Maps an ApiError's HTTP status onto the frozen taxonomy. Status 0
13
+ * (network/timeout) and everything unmapped fall through to GENERIC. */
14
+ function exitCodeForApiError(e) {
15
+ switch (e.status) {
16
+ case 401:
17
+ case 403:
18
+ return exports.EXIT.AUTH;
19
+ case 400:
20
+ return exports.EXIT.VALIDATION;
21
+ case 402:
22
+ return exports.EXIT.INSUFFICIENT_CREDITS;
23
+ default:
24
+ return exports.EXIT.GENERIC;
25
+ }
26
+ }
package/dist/http.js CHANGED
@@ -36,7 +36,7 @@ async function api(apiUrl, pathname, opts = {}) {
36
36
  if (v !== undefined && v !== "")
37
37
  url.searchParams.set(k, v);
38
38
  }
39
- const headers = {};
39
+ const headers = { ...opts.headers };
40
40
  if (opts.key)
41
41
  headers.authorization = `Bearer ${opts.key}`;
42
42
  if (opts.body !== undefined)
package/dist/jobs.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.appendJob = appendJob;
37
+ exports.readJobs = readJobs;
38
+ // Local jobs ledger — append-only NDJSON at <config-dir>/jobs.jsonl. `run`
39
+ // appends one line on submit and one on the terminal result; `jobs` reads it
40
+ // back. Best-effort: a ledger write must never crash a run (spec — the ledger
41
+ // is a convenience, not the source of truth; the server owns run state).
42
+ const fs = __importStar(require("node:fs"));
43
+ const config_1 = require("./config");
44
+ /** Append one ledger line. Swallows IO errors — never fails the caller. */
45
+ function appendJob(entry) {
46
+ try {
47
+ fs.mkdirSync((0, config_1.configDir)(), { recursive: true, mode: 0o700 });
48
+ fs.appendFileSync((0, config_1.jobsPath)(), JSON.stringify(entry) + "\n");
49
+ }
50
+ catch {
51
+ // ledger is best-effort; the run already succeeded/failed on its own terms
52
+ }
53
+ }
54
+ /** Read every ledger line, newest last. Skips malformed lines defensively. */
55
+ function readJobs() {
56
+ let raw;
57
+ try {
58
+ raw = fs.readFileSync((0, config_1.jobsPath)(), "utf8");
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ const out = [];
64
+ for (const line of raw.split("\n")) {
65
+ if (!line.trim())
66
+ continue;
67
+ try {
68
+ out.push(JSON.parse(line));
69
+ }
70
+ catch {
71
+ // tolerate a partially-written trailing line
72
+ }
73
+ }
74
+ return out;
75
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.topUpUrl = topUpUrl;
4
+ exports.guardJson = guardJson;
5
+ // Shared --json error emission for the read commands (list/balance/history/
6
+ // tools). Under --json a failing request must put a machine-readable error on
7
+ // stdout (not stderr) and exit with the frozen code; 402 carries top_up_url.
8
+ const exit_1 = require("./exit");
9
+ const http_1 = require("./http");
10
+ function topUpUrl(apiUrl) {
11
+ return `${apiUrl}/credits`;
12
+ }
13
+ /** Runs `body`; on ApiError under --json, emits the JSON error payload to
14
+ * stdout and returns the mapped exit code. Non-json failures re-throw so the
15
+ * cli.ts dispatcher renders them to stderr with the same code. */
16
+ async function guardJson(asJson, apiUrl, body) {
17
+ try {
18
+ return await body();
19
+ }
20
+ catch (e) {
21
+ if (asJson && e instanceof http_1.ApiError) {
22
+ const code = (0, exit_1.exitCodeForApiError)(e);
23
+ const payload = { error: e.message, exit_code: code };
24
+ if (e.code)
25
+ payload.code = e.code;
26
+ if (code === exit_1.EXIT.INSUFFICIENT_CREDITS)
27
+ payload.top_up_url = topUpUrl(apiUrl);
28
+ console.log(JSON.stringify(payload));
29
+ return code;
30
+ }
31
+ throw e;
32
+ }
33
+ }
@@ -24,6 +24,10 @@ exports.TOOLS_SNAPSHOT = [
24
24
  captions: { type: "boolean", default: false },
25
25
  caption_style: { type: "string", enum: ["movie", "tiktok"], default: "tiktok" },
26
26
  enhance_prompt: { type: "boolean", default: false },
27
+ influencer: {
28
+ type: "string",
29
+ description: "Slug of a saved influencer — the server attaches its portrait + character sheet as references and prepends its appearance description to the prompt.",
30
+ },
27
31
  },
28
32
  },
29
33
  {
@@ -45,6 +49,10 @@ exports.TOOLS_SNAPSHOT = [
45
49
  resolution: { type: "string", enum: ["1k", "2k", "4k"], default: "1k" },
46
50
  count: { type: "number", default: 1, min: 1, max: 4 },
47
51
  enhance_prompt: { type: "boolean", default: false },
52
+ influencer: {
53
+ type: "string",
54
+ description: "Slug of a saved influencer — the server attaches its portrait + character sheet as references and prepends its appearance description to the prompt.",
55
+ },
48
56
  },
49
57
  },
50
58
  {
@@ -56,4 +64,13 @@ exports.TOOLS_SNAPSHOT = [
56
64
  language: { type: "string", default: "auto" },
57
65
  },
58
66
  },
67
+ {
68
+ name: "create_influencer",
69
+ description: "Create a reusable, account-bound influencer: a hero portrait + a multi-view character sheet. Flat 20 credits. The prompt is auto-rewritten server-side (always-on).",
70
+ params: {
71
+ prompt: { type: "string", required: true },
72
+ name: { type: "string", required: true, min: 1, maxLength: 60 },
73
+ slug: { type: "string", maxLength: 40 },
74
+ },
75
+ },
59
76
  ];
package/dist/types.js CHANGED
@@ -6,3 +6,7 @@
6
6
  // test/parity.test.ts imports core (test-only, relative path) and pins the
7
7
  // values that could drift.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.IDEMPOTENCY_KEY_HEADER = void 0;
10
+ /** Header carrying the client-generated idempotency key on run creation
11
+ * (mirrors contract.ts IDEMPOTENCY_KEY_HEADER; parity.test.ts pins it). */
12
+ exports.IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
package/dist/validate.js CHANGED
@@ -32,6 +32,9 @@ exports.FLAG_FOR = {
32
32
  count: "--count",
33
33
  resolution: "--resolution",
34
34
  language: "--language",
35
+ name: "--name",
36
+ slug: "--slug",
37
+ influencer: "--influencer",
35
38
  };
36
39
  /** Map parsed CLI flags onto the tool-input body the API expects. Only flags
37
40
  * the user actually passed are included, so server defaults stay in charge. */
@@ -49,6 +52,9 @@ function buildToolInput(flags) {
49
52
  ["count", "count"],
50
53
  ["resolution", "resolution"],
51
54
  ["language", "language"],
55
+ ["name", "name"],
56
+ ["slug", "slug"],
57
+ ["influencer", "influencer"],
52
58
  ];
53
59
  for (const [flag, param] of direct) {
54
60
  if (flags[flag] !== undefined)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getagenthook",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",