getagenthook 0.1.0 → 0.2.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/dist/cli.js +33 -3
- package/dist/commands/auth-login.js +111 -0
- package/dist/commands/balance.js +22 -11
- package/dist/commands/history.js +30 -21
- package/dist/commands/influencers.js +85 -0
- package/dist/commands/jobs.js +35 -0
- package/dist/commands/list.js +38 -27
- package/dist/commands/run.js +162 -30
- package/dist/commands/tools.js +21 -12
- package/dist/config.js +11 -0
- package/dist/exit.js +26 -0
- package/dist/http.js +1 -1
- package/dist/jobs.js +75 -0
- package/dist/json-error.js +33 -0
- package/dist/schema-snapshot.js +23 -1
- package/dist/types.js +4 -0
- package/dist/validate.js +6 -0
- package/dist/version.js +8 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,48 +1,78 @@
|
|
|
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");
|
|
15
|
+
const version_1 = require("./version");
|
|
11
16
|
const USAGE = `agenthook — hosted media generation for agents
|
|
12
17
|
|
|
13
18
|
Usage: agenthook <command> [flags]
|
|
14
19
|
|
|
15
20
|
Commands:
|
|
16
|
-
login
|
|
21
|
+
auth:login keyless device flow — approve in the console
|
|
22
|
+
login [--key <key>] keyless device flow, or store a pasted key
|
|
17
23
|
tools list tools + parameters (from GET /v1/tools)
|
|
18
24
|
run <tool> [flags] submit a run, poll every 5s, print output URL(s)
|
|
19
25
|
list [--tool t] [--status s] [--search q] your past generations
|
|
26
|
+
influencers list your saved influencers (slug / name / portrait)
|
|
27
|
+
influencers:delete <slug> delete a saved influencer
|
|
20
28
|
balance credit balance
|
|
21
29
|
history credit ledger
|
|
30
|
+
jobs local run ledger (~/.agenthook/jobs.jsonl)
|
|
31
|
+
version print the CLI version (also --version / -v)
|
|
22
32
|
|
|
23
33
|
Run flags:
|
|
24
34
|
--prompt <text> --ref <url> (repeatable) --owns-references --model <m>
|
|
25
35
|
--quality <standard|pro> --duration <s> --aspect-ratio <r> --no-audio
|
|
26
36
|
--captions --caption-style <movie|tiktok> --enhance-prompt
|
|
27
37
|
--video-url <url> --style <movie|tiktok> --count <n> --resolution <1k|2k|4k>
|
|
38
|
+
--dry-run price + validate against the server without spending credits
|
|
28
39
|
|
|
40
|
+
Auth: pass --key, set AGENTHOOK_API_KEY, or run auth:login (precedence: flag > env > file).
|
|
41
|
+
--json on run/list/balance/history/tools/jobs emits machine JSON on stdout, progress on stderr.
|
|
29
42
|
Every command accepts --api-url <url> (or AGENTHOOK_API_URL; default https://getagenthook.com).`;
|
|
30
43
|
async function runCli(argv) {
|
|
31
44
|
const [cmd, ...rest] = argv;
|
|
32
45
|
try {
|
|
33
46
|
switch (cmd) {
|
|
47
|
+
case "auth:login":
|
|
48
|
+
return await (0, auth_login_1.authLogin)(rest);
|
|
34
49
|
case "login":
|
|
35
|
-
|
|
50
|
+
// Keyless `login` falls through to the device flow; `login --key <k>`
|
|
51
|
+
// keeps the paste path (still supported for CI/pre-provisioned keys).
|
|
52
|
+
return rest.includes("--key") ? await (0, login_1.login)(rest) : await (0, auth_login_1.authLogin)(rest);
|
|
36
53
|
case "tools":
|
|
37
54
|
return await (0, tools_1.tools)(rest);
|
|
38
55
|
case "run":
|
|
39
56
|
return await (0, run_1.run)(rest);
|
|
40
57
|
case "list":
|
|
41
58
|
return await (0, list_1.list)(rest);
|
|
59
|
+
case "influencers":
|
|
60
|
+
return await (0, influencers_1.influencers)(rest);
|
|
61
|
+
case "influencers:delete":
|
|
62
|
+
return await (0, influencers_1.influencersDelete)(rest);
|
|
42
63
|
case "balance":
|
|
43
64
|
return await (0, balance_1.balance)(rest);
|
|
44
65
|
case "history":
|
|
45
66
|
return await (0, history_1.history)(rest);
|
|
67
|
+
case "jobs":
|
|
68
|
+
return await (0, jobs_1.jobs)(rest);
|
|
69
|
+
case "version":
|
|
70
|
+
case "--version":
|
|
71
|
+
case "-v":
|
|
72
|
+
// Agents probe this to detect a stale install — must exit 0 with the
|
|
73
|
+
// package version, never fall through to "Unknown command" (exit 1).
|
|
74
|
+
console.log(version_1.VERSION);
|
|
75
|
+
return 0;
|
|
46
76
|
case undefined:
|
|
47
77
|
case "help":
|
|
48
78
|
case "--help":
|
|
@@ -57,7 +87,7 @@ async function runCli(argv) {
|
|
|
57
87
|
catch (e) {
|
|
58
88
|
if (e instanceof http_1.ApiError) {
|
|
59
89
|
console.error((0, http_1.describeApiError)(e));
|
|
60
|
-
return
|
|
90
|
+
return (0, exit_1.exitCodeForApiError)(e);
|
|
61
91
|
}
|
|
62
92
|
console.error(e instanceof Error ? e.message : String(e));
|
|
63
93
|
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));
|
package/dist/commands/balance.js
CHANGED
|
@@ -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
|
|
13
|
+
return exit_1.EXIT.GENERIC;
|
|
12
14
|
}
|
|
13
|
-
const
|
|
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
|
|
19
|
+
console.error("Not logged in — run `agenthook auth:login` first.");
|
|
20
|
+
return exit_1.EXIT.AUTH;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
}
|
package/dist/commands/history.js
CHANGED
|
@@ -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
|
|
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
|
|
21
|
-
if (
|
|
22
|
-
console.
|
|
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
|
-
|
|
34
|
-
|
|
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
|
+
}
|
package/dist/commands/list.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
27
|
+
console.error("Not logged in — run `agenthook auth:login` first.");
|
|
28
|
+
return exit_1.EXIT.AUTH;
|
|
24
29
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
92
|
+
const key = (0, config_1.resolveApiKey)(flags["key"]);
|
|
46
93
|
if (!key) {
|
|
47
|
-
|
|
48
|
-
return
|
|
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
|
-
|
|
59
|
-
|
|
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, e.details);
|
|
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
|
-
//
|
|
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 (
|
|
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
|
-
|
|
73
|
-
|
|
149
|
+
const code = (0, exit_1.exitCodeForApiError)(e);
|
|
150
|
+
if (asJson)
|
|
151
|
+
emitError(true, e.message, code, e.status === 402 ? topUpUrl(apiUrl) : undefined, e.details);
|
|
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.
|
|
79
|
-
// stdout
|
|
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
|
-
|
|
93
|
-
return
|
|
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
|
-
|
|
98
|
-
`The run may still finish — check later with: agenthook list
|
|
99
|
-
return
|
|
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,64 @@ async function run(argv) {
|
|
|
105
190
|
lastStatus = runRow.status;
|
|
106
191
|
}
|
|
107
192
|
if (runRow.status === "completed") {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
`giving up on polling. Check later with: agenthook list
|
|
119
|
-
return
|
|
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
|
+
* A server 400 carries a `details: [{path,message}]` array naming the offending
|
|
241
|
+
* params — surfaced in the JSON so an agent needn't re-run without --json. */
|
|
242
|
+
function emitError(asJson, message, code, top_up_url, details) {
|
|
243
|
+
if (asJson)
|
|
244
|
+
console.log(JSON.stringify({
|
|
245
|
+
error: message,
|
|
246
|
+
exit_code: code,
|
|
247
|
+
...(details && details.length ? { details } : {}),
|
|
248
|
+
...(top_up_url ? { top_up_url } : {}),
|
|
249
|
+
}));
|
|
250
|
+
else
|
|
251
|
+
console.error(message);
|
|
120
252
|
}
|
|
121
253
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
package/dist/commands/tools.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|
package/dist/schema-snapshot.js
CHANGED
|
@@ -14,7 +14,12 @@ exports.TOOLS_SNAPSHOT = [
|
|
|
14
14
|
},
|
|
15
15
|
model: { type: "string", enum: ["seedance-2", "kling-3"], default: "seedance-2" },
|
|
16
16
|
quality: { type: "string", enum: ["standard", "pro"], default: "standard" },
|
|
17
|
-
duration: {
|
|
17
|
+
duration: {
|
|
18
|
+
type: "number",
|
|
19
|
+
default: 5,
|
|
20
|
+
min: 1,
|
|
21
|
+
description: "Video length in seconds. Allowed values depend on model — seedance-2: 4, 5, 6, 8, 10, 12, 15; kling-3: 3, 5, 8, 10, 15.",
|
|
22
|
+
},
|
|
18
23
|
aspect_ratio: {
|
|
19
24
|
type: "string",
|
|
20
25
|
enum: ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"],
|
|
@@ -24,6 +29,10 @@ exports.TOOLS_SNAPSHOT = [
|
|
|
24
29
|
captions: { type: "boolean", default: false },
|
|
25
30
|
caption_style: { type: "string", enum: ["movie", "tiktok"], default: "tiktok" },
|
|
26
31
|
enhance_prompt: { type: "boolean", default: false },
|
|
32
|
+
influencer: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Slug of a saved influencer — the server attaches its portrait + character sheet as references and prepends its appearance description to the prompt.",
|
|
35
|
+
},
|
|
27
36
|
},
|
|
28
37
|
},
|
|
29
38
|
{
|
|
@@ -45,6 +54,10 @@ exports.TOOLS_SNAPSHOT = [
|
|
|
45
54
|
resolution: { type: "string", enum: ["1k", "2k", "4k"], default: "1k" },
|
|
46
55
|
count: { type: "number", default: 1, min: 1, max: 4 },
|
|
47
56
|
enhance_prompt: { type: "boolean", default: false },
|
|
57
|
+
influencer: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Slug of a saved influencer — the server attaches its portrait + character sheet as references and prepends its appearance description to the prompt.",
|
|
60
|
+
},
|
|
48
61
|
},
|
|
49
62
|
},
|
|
50
63
|
{
|
|
@@ -56,4 +69,13 @@ exports.TOOLS_SNAPSHOT = [
|
|
|
56
69
|
language: { type: "string", default: "auto" },
|
|
57
70
|
},
|
|
58
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: "create_influencer",
|
|
74
|
+
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).",
|
|
75
|
+
params: {
|
|
76
|
+
prompt: { type: "string", required: true },
|
|
77
|
+
name: { type: "string", required: true, min: 1, maxLength: 60 },
|
|
78
|
+
slug: { type: "string", maxLength: 40 },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
59
81
|
];
|
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/dist/version.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERSION = void 0;
|
|
4
|
+
// Package version, read from package.json at runtime. dist/*.js sits one level
|
|
5
|
+
// under the package root (rootDir src → outDir dist), so ../package.json
|
|
6
|
+
// resolves both from the installed npm package and from the repo's built dist.
|
|
7
|
+
// require() (CommonJS build) reads it synchronously with no bundler step.
|
|
8
|
+
exports.VERSION = require("../package.json").version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getagenthook",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.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",
|