@myapihq/cli 2.20.0 → 2.21.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/commands/llm.d.ts +1 -0
- package/dist/commands/llm.js +123 -4
- package/dist/completion.js +1 -1
- package/dist/flags.d.ts +1 -1
- package/dist/flags.js +6 -2
- package/dist/index.js +17 -1
- package/dist/sdk-request-all.test.d.ts +1 -0
- package/dist/sdk-request-all.test.js +94 -0
- package/dist/skills/my-api-hq/SKILL.md +4 -3
- package/dist/skills/my-auth-api/SKILL.md +2 -1
- package/dist/skills/my-container-api/SKILL.md +4 -5
- package/dist/skills/my-crm-api/SKILL.md +5 -4
- package/dist/skills/my-database-api/SKILL.md +2 -1
- package/dist/skills/my-feedback-api/SKILL.md +6 -5
- package/dist/skills/my-function-api/SKILL.md +2 -1
- package/dist/skills/my-llm-api/SKILL.md +30 -38
- package/dist/skills/my-queue-api/SKILL.md +2 -1
- package/dist/skills/my-storage-api/SKILL.md +2 -1
- package/dist/skills/my-webhook-api/SKILL.md +2 -1
- package/dist/skills/my-workflow-api/SKILL.md +2 -1
- package/package.json +2 -2
package/dist/commands/llm.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export declare const EXPOSES: Exposes;
|
|
|
5
5
|
export declare const SCHEMA: FlagSchema;
|
|
6
6
|
export declare function _parseJsonObjectFlag(raw: unknown, flagName: string): Record<string, unknown> | undefined;
|
|
7
7
|
export declare const SUBCOMMAND_USAGE: Record<string, string>;
|
|
8
|
+
export declare const NESTED_SUBCOMMAND_USAGE: Record<string, Record<string, string>>;
|
|
8
9
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
package/dist/commands/llm.js
CHANGED
|
@@ -2,13 +2,16 @@ import * as fs from 'fs';
|
|
|
2
2
|
import { llm as sdkLlm } from '@myapihq/sdk';
|
|
3
3
|
import { requireConfig } from '../config.js';
|
|
4
4
|
import { error, info, printTable, printJson } from '../output.js';
|
|
5
|
-
import { requireOrg } from '../helpers.js';
|
|
5
|
+
import { requireOrg, requireArg } from '../helpers.js';
|
|
6
6
|
import { retryFunds } from '../utils.js';
|
|
7
7
|
export const EXPOSES = [
|
|
8
8
|
'POST /llm/orgs/{org_id}/complete',
|
|
9
9
|
'POST /llm/orgs/{org_id}/embed',
|
|
10
10
|
'GET /llm/orgs/{org_id}/models',
|
|
11
11
|
'POST /llm/orgs/{org_id}/tasks/{verb}',
|
|
12
|
+
'POST /llm/orgs/{org_id}/caches',
|
|
13
|
+
'GET /llm/orgs/{org_id}/caches',
|
|
14
|
+
'DELETE /llm/orgs/{org_id}/caches/{id}',
|
|
12
15
|
];
|
|
13
16
|
export const SCHEMA = {
|
|
14
17
|
org: 'string',
|
|
@@ -30,6 +33,10 @@ export const SCHEMA = {
|
|
|
30
33
|
context: 'string',
|
|
31
34
|
prompt: 'string',
|
|
32
35
|
tier: 'string',
|
|
36
|
+
// context caches
|
|
37
|
+
cache: 'string',
|
|
38
|
+
ttl: 'number',
|
|
39
|
+
yes: 'boolean',
|
|
33
40
|
};
|
|
34
41
|
// Cache the catalog once per CLI invocation so verbs (which don't take a
|
|
35
42
|
// --model flag) can fall back to "the first chat model" without an extra
|
|
@@ -161,6 +168,7 @@ async function complete(promptArg, flags) {
|
|
|
161
168
|
const res = await retryFunds(() => sdkLlm.complete(config.api_key, orgId, {
|
|
162
169
|
model,
|
|
163
170
|
messages,
|
|
171
|
+
cache_id: typeof flags.cache === 'string' && flags.cache ? flags.cache : undefined,
|
|
164
172
|
max_tokens: typeof flags['max-tokens'] === 'number' ? flags['max-tokens'] : undefined,
|
|
165
173
|
temperature: typeof flags.temperature === 'number' ? flags.temperature : undefined,
|
|
166
174
|
stop,
|
|
@@ -171,7 +179,12 @@ async function complete(promptArg, flags) {
|
|
|
171
179
|
}
|
|
172
180
|
info(res.content);
|
|
173
181
|
// Usage footer on stderr so it doesn't pollute piped output.
|
|
174
|
-
|
|
182
|
+
// Cached tokens are shown as "N of M cached", never added to the input
|
|
183
|
+
// count: `cached_input_tokens` is a SUBSET of `input_tokens`, and a footer
|
|
184
|
+
// that summed them would report more tokens than were sent.
|
|
185
|
+
const cached = res.usage.cached_input_tokens ?? 0;
|
|
186
|
+
const cachedNote = cached > 0 ? ` (${cached} cached)` : '';
|
|
187
|
+
process.stderr.write(`\n— ${res.model} · ${res.usage.input_tokens}${cachedNote}+${res.usage.output_tokens ?? 0} tokens · ${fmtCostCents(res.usage.cost_cents)} · ${res.finish_reason}\n`);
|
|
175
188
|
}
|
|
176
189
|
async function embed(inputArg, flags) {
|
|
177
190
|
const config = requireConfig();
|
|
@@ -209,6 +222,81 @@ async function listModels(flags) {
|
|
|
209
222
|
'out ¢/1M': m.output_cost_per_1m_cents != null ? m.output_cost_per_1m_cents : '',
|
|
210
223
|
})), { flags, empty: 'No models available.' });
|
|
211
224
|
}
|
|
225
|
+
// ── context caches ────────────────────────────────────────────────────────
|
|
226
|
+
// A cache bills for EXISTING, not for being read: storage accrues per hour
|
|
227
|
+
// from the moment it is created. That is why this surface is explicit and why
|
|
228
|
+
// `delete` is documented as the way to stop paying — the alternative, waiting
|
|
229
|
+
// out the TTL, means paying the TTL in full.
|
|
230
|
+
const CACHE_HELP = `Usage: myapi llm cache <create|list|delete>
|
|
231
|
+
|
|
232
|
+
create Store a prompt prefix (--file <path>, --model <id>, [--ttl <secs>])
|
|
233
|
+
list Live caches with size, expiry, and what they cost to hold
|
|
234
|
+
delete Delete a cache and stop paying for it
|
|
235
|
+
|
|
236
|
+
A cache costs a TENTH to re-read: pass it to complete with --cache <id>.
|
|
237
|
+
It is bound to ONE model — a cache made for one model cannot be read by
|
|
238
|
+
another. Storage is billed per hour whether or not anybody reads it, so
|
|
239
|
+
delete one you are done with rather than waiting out its TTL.`;
|
|
240
|
+
async function cacheCreate(flags) {
|
|
241
|
+
const config = requireConfig();
|
|
242
|
+
const orgId = requireOrg(flags, config, 'myapi llm cache create --file <path> [--model <id>] [--ttl <seconds>] [--org <id>]');
|
|
243
|
+
const content = readPromptArg(undefined, flags);
|
|
244
|
+
if (!content.trim()) {
|
|
245
|
+
error('Empty content. Pass --file <path> (the content is long by definition — that is the reason to cache it).');
|
|
246
|
+
}
|
|
247
|
+
const model = typeof flags.model === 'string' && flags.model
|
|
248
|
+
? flags.model
|
|
249
|
+
: await defaultChatModel(config.api_key, orgId);
|
|
250
|
+
const ttl = typeof flags.ttl === 'number' ? flags.ttl : undefined;
|
|
251
|
+
const res = await retryFunds(() => sdkLlm.createCache(config.api_key, orgId, {
|
|
252
|
+
model,
|
|
253
|
+
content,
|
|
254
|
+
ttl_seconds: ttl,
|
|
255
|
+
}));
|
|
256
|
+
if (flags.json) {
|
|
257
|
+
printJson(res);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
info(`Cached ${res.tokens} tokens for ${res.model} → ${res.id}`);
|
|
261
|
+
info(`Expires ${res.expires_at}. Read it with: myapi llm complete --model ${res.model} --cache ${res.id} "..."`);
|
|
262
|
+
// Said on create because this is the moment the meter starts, and the
|
|
263
|
+
// request's own --ttl is not what was necessarily granted.
|
|
264
|
+
info(`Billed per hour until then whether or not you read it — "myapi llm cache delete ${res.id}" stops the charge.`);
|
|
265
|
+
}
|
|
266
|
+
async function cacheList(flags) {
|
|
267
|
+
const config = requireConfig();
|
|
268
|
+
const orgId = requireOrg(flags, config, 'myapi llm cache list [--org <id>]');
|
|
269
|
+
const res = await sdkLlm.listCaches(config.api_key, orgId);
|
|
270
|
+
if (flags.json) {
|
|
271
|
+
printJson(res);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
printTable(res.caches.map(c => ({
|
|
275
|
+
id: c.id,
|
|
276
|
+
model: c.model,
|
|
277
|
+
tokens: c.tokens,
|
|
278
|
+
expires_at: c.expires_at,
|
|
279
|
+
})), { flags, empty: 'No live caches. (Expired ones are not listed — and are no longer billed.)' });
|
|
280
|
+
}
|
|
281
|
+
async function cacheDelete(args, flags) {
|
|
282
|
+
const config = requireConfig();
|
|
283
|
+
const orgId = requireOrg(flags, config, 'myapi llm cache delete <id> [--org <id>]');
|
|
284
|
+
const id = requireArg(args[0], '<id>', 'myapi llm cache delete <id>');
|
|
285
|
+
await sdkLlm.deleteCache(config.api_key, orgId, id);
|
|
286
|
+
info(`Deleted ${id}. Storage for it is no longer billed.`);
|
|
287
|
+
}
|
|
288
|
+
async function cacheRun(sub, args, flags) {
|
|
289
|
+
if (!sub || flags.help) {
|
|
290
|
+
info(CACHE_HELP);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
switch (sub) {
|
|
294
|
+
case 'create': return cacheCreate(flags);
|
|
295
|
+
case 'list': return cacheList(flags);
|
|
296
|
+
case 'delete': return cacheDelete(args, flags);
|
|
297
|
+
default: error(`Unknown subcommand: llm cache ${sub}. Run "myapi llm cache --help" for the list.`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
212
300
|
// ── verbs ────────────────────────────────────────────────────────────────
|
|
213
301
|
function printVerbFooter(usage) {
|
|
214
302
|
process.stderr.write(`\n— tier=${usage.tier_used} · ${usage.tokens_in}+${usage.tokens_out} tokens · ${fmtCostCents(usage.cost_cents)}\n`);
|
|
@@ -315,7 +403,7 @@ async function draft(inputArg, flags) {
|
|
|
315
403
|
}
|
|
316
404
|
// ── help ─────────────────────────────────────────────────────────────────
|
|
317
405
|
export const SUBCOMMAND_USAGE = {
|
|
318
|
-
complete: `myapi llm complete "<prompt>" [--model <id>]
|
|
406
|
+
complete: `myapi llm complete "<prompt>" [--model <id>] [--cache <id>]
|
|
319
407
|
[--system "<system msg>"] [--max-tokens N] [--temperature 0..1]
|
|
320
408
|
[--stop <csv>] [--file <path>] [--org <id>] [--json]
|
|
321
409
|
|
|
@@ -326,9 +414,31 @@ export const SUBCOMMAND_USAGE = {
|
|
|
326
414
|
one-line usage footer (tokens + cost in cents) goes to stderr so it
|
|
327
415
|
doesn't pollute piped output.
|
|
328
416
|
|
|
417
|
+
--cache <id> re-reads a stored prefix at a TENTH the input price; make one
|
|
418
|
+
with "myapi llm cache create". The cache must belong to the same model.
|
|
419
|
+
|
|
329
420
|
Examples:
|
|
330
421
|
myapi llm complete "summarize: $(cat README.md)"
|
|
331
|
-
cat draft.md | myapi llm complete - --system "You are an editor" --max-tokens 200
|
|
422
|
+
cat draft.md | myapi llm complete - --system "You are an editor" --max-tokens 200
|
|
423
|
+
myapi llm complete "what changed?" --model myapi-fast --cache cch_...`,
|
|
424
|
+
cache: `myapi llm cache <create|list|delete>
|
|
425
|
+
|
|
426
|
+
create myapi llm cache create --file <path> [--model <id>] [--ttl <secs>]
|
|
427
|
+
list myapi llm cache list
|
|
428
|
+
delete myapi llm cache delete <id>
|
|
429
|
+
|
|
430
|
+
Store a prompt prefix once and re-read it at a TENTH the input price. Worth
|
|
431
|
+
it for anything long you send on every call — a system prompt, a reference
|
|
432
|
+
document, a schema.
|
|
433
|
+
|
|
434
|
+
Two things that cost money if you skip them:
|
|
435
|
+
- A cache bills for EXISTING, not for being read. Storage accrues per hour
|
|
436
|
+
from creation whether or not anybody reads it. "cache delete" is how you
|
|
437
|
+
stop paying; waiting out the TTL means paying the whole TTL.
|
|
438
|
+
- --ttl is capped at 24h. A longer request is silently shortened, not
|
|
439
|
+
refused, so read expires_at from the reply rather than trusting your input.
|
|
440
|
+
|
|
441
|
+
A cache belongs to ONE model and cannot be read by another.`,
|
|
332
442
|
embed: `myapi llm embed "<text>" [--model <id>] [--file <path>] [--org <id>] [--json]
|
|
333
443
|
|
|
334
444
|
Embed text into a dense vector. --model is optional when the catalog serves
|
|
@@ -381,6 +491,13 @@ export const SUBCOMMAND_USAGE = {
|
|
|
381
491
|
--context '{"recipient":"a new signup","product":"MyAPI"}'
|
|
382
492
|
cat inbound.eml | myapi llm draft - --kind reply --prompt "Acknowledge and ask for the order ID"`,
|
|
383
493
|
};
|
|
494
|
+
export const NESTED_SUBCOMMAND_USAGE = {
|
|
495
|
+
cache: {
|
|
496
|
+
'create': 'myapi llm cache create --file <path> [--model <id>] [--ttl <seconds>] [--org <id>] [--json]',
|
|
497
|
+
'list': 'myapi llm cache list [--org <id>] [--json]',
|
|
498
|
+
'delete': 'myapi llm cache delete <id> [--org <id>]',
|
|
499
|
+
},
|
|
500
|
+
};
|
|
384
501
|
export async function run(subcommand, args, flags) {
|
|
385
502
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
386
503
|
info(`Usage: myapi llm <subcommand>
|
|
@@ -392,6 +509,7 @@ Two surfaces:
|
|
|
392
509
|
is never named in the response.
|
|
393
510
|
|
|
394
511
|
Subcommands:
|
|
512
|
+
cache Store a prompt prefix; re-read it at a tenth the input price
|
|
395
513
|
classify Pick a label from a set
|
|
396
514
|
complete Raw chat completion against a catalog model
|
|
397
515
|
draft Write something (email | reply | message | …)
|
|
@@ -421,6 +539,7 @@ for your own reasoning. The agent has its own model already.`);
|
|
|
421
539
|
case 'extract': return extract(args[0], flags);
|
|
422
540
|
case 'summarize': return summarize(args[0], flags);
|
|
423
541
|
case 'draft': return draft(args[0], flags);
|
|
542
|
+
case 'cache': return cacheRun(args[0], args.slice(1), flags);
|
|
424
543
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi llm --help" for valid subcommands.`);
|
|
425
544
|
}
|
|
426
545
|
}
|
package/dist/completion.js
CHANGED
|
@@ -51,7 +51,7 @@ export const SUBCOMMANDS = {
|
|
|
51
51
|
people: ['search', 'get'],
|
|
52
52
|
company: ['search', 'get'],
|
|
53
53
|
audience: ['create', 'list', 'get', 'update', 'delete', 'members', 'refresh'],
|
|
54
|
-
llm: ['complete', 'embed', 'models', 'classify', 'extract', 'summarize', 'draft'],
|
|
54
|
+
llm: ['complete', 'embed', 'models', 'classify', 'extract', 'summarize', 'draft', 'cache'],
|
|
55
55
|
database: ['namespaces', 'create', 'delete-namespace', 'keys', 'get', 'set', 'del'],
|
|
56
56
|
crm: ['contacts', 'companies'],
|
|
57
57
|
url: ['shorten'],
|
package/dist/flags.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export interface ParsedArgs {
|
|
|
5
5
|
args: string[];
|
|
6
6
|
flags: Record<string, string | boolean | number>;
|
|
7
7
|
}
|
|
8
|
-
export declare function parseFlags(argv: string[], schema?: FlagSchema): ParsedArgs;
|
|
8
|
+
export declare function parseFlags(argv: string[], schema?: FlagSchema, quiet?: boolean): ParsedArgs;
|
package/dist/flags.js
CHANGED
|
@@ -12,7 +12,11 @@ export const GLOBAL_FLAGS = {
|
|
|
12
12
|
yes: 'boolean',
|
|
13
13
|
y: 'boolean',
|
|
14
14
|
};
|
|
15
|
-
|
|
15
|
+
// `quiet` suppresses the unknown-flag note. The dispatcher parses twice — once
|
|
16
|
+
// to learn the command, once under that command's own schema — and only the
|
|
17
|
+
// second pass knows which flags are genuinely foreign, so the first must stay
|
|
18
|
+
// silent or every typo would be reported twice.
|
|
19
|
+
export function parseFlags(argv, schema = {}, quiet = false) {
|
|
16
20
|
const merged = { ...GLOBAL_FLAGS, ...schema };
|
|
17
21
|
const args = [];
|
|
18
22
|
const flags = {};
|
|
@@ -126,7 +130,7 @@ export function parseFlags(argv, schema = {}) {
|
|
|
126
130
|
// Surface unknown flags so typos and hallucinated flags don't silently
|
|
127
131
|
// disappear. Don't block — the value is still in `flags` for any handler
|
|
128
132
|
// that wants it — just print one line on stderr.
|
|
129
|
-
if (unknownFlags.length > 0 && !process.env.MYAPI_QUIET_UNKNOWN_FLAGS) {
|
|
133
|
+
if (unknownFlags.length > 0 && !quiet && !process.env.MYAPI_QUIET_UNKNOWN_FLAGS) {
|
|
130
134
|
process.stderr.write(`› Note: unknown flag(s) not recognized by this command: ${unknownFlags.join(', ')} — check for typos.\n`);
|
|
131
135
|
}
|
|
132
136
|
return { args, flags };
|
package/dist/index.js
CHANGED
|
@@ -158,7 +158,23 @@ async function main() {
|
|
|
158
158
|
handleCompletionRequest(); // computes candidates / emits the script, then exits
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
// Two passes. The first only has to find the command, because the schema
|
|
162
|
+
// that types the flags depends on it: COMBINED_SCHEMA is a merge, and a
|
|
163
|
+
// merge silently resolves collisions by declaration order. `ttl` is declared
|
|
164
|
+
// `number` by domain and llm and `string` by storage, and because storage
|
|
165
|
+
// merges last EVERY --ttl in the CLI was parsed as a string — so
|
|
166
|
+
// `domain records create --ttl 3600` sent "3600" and `llm cache create
|
|
167
|
+
// --ttl 600` dropped the value entirely on a `typeof === 'number'` check.
|
|
168
|
+
//
|
|
169
|
+
// Re-parsing under the command's own schema makes each command's declared
|
|
170
|
+
// types authoritative, which is what every command already assumes, and what
|
|
171
|
+
// warnForeignFlags already assumes when it decides a flag is foreign.
|
|
172
|
+
const first = parseFlags(process.argv.slice(2), COMBINED_SCHEMA, /* quiet */ true);
|
|
173
|
+
const commandName = first.args[0];
|
|
174
|
+
const ownSchema = commandName ? COMMAND_SCHEMAS[commandName] : undefined;
|
|
175
|
+
// The second pass is the one that speaks: it knows the command, so it is the
|
|
176
|
+
// only one that can tell a foreign flag from a flag another command declares.
|
|
177
|
+
const { args, flags } = parseFlags(process.argv.slice(2), ownSchema ?? COMBINED_SCHEMA);
|
|
162
178
|
warnForeignFlags(args[0], flags);
|
|
163
179
|
if (flags.version || flags.v || flags.V) {
|
|
164
180
|
// Read the last-known published version from cache — no network call, so
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// requestAll follows a keyset cursor to the end.
|
|
2
|
+
//
|
|
3
|
+
// The backend started paging ten list endpoints on 2026-08-20. Callers using
|
|
4
|
+
// `request` kept working and quietly began receiving only the first page — 50
|
|
5
|
+
// rows where they used to get the collection, with nothing in the response
|
|
6
|
+
// saying so. A list that silently stops is worse than a slow one: the caller
|
|
7
|
+
// acts on a partial answer believing it is complete.
|
|
8
|
+
//
|
|
9
|
+
// So these pin the two ways this helper could reintroduce that: stopping early,
|
|
10
|
+
// and looping forever on a server that never says it is done.
|
|
11
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
12
|
+
import { requestAll } from '@myapihq/sdk';
|
|
13
|
+
/** A stub that pages `rows` and records every URL it was asked for. */
|
|
14
|
+
function pagingServer(rows, pageSize) {
|
|
15
|
+
const seen = [];
|
|
16
|
+
const fetchMock = vi.fn(async (url) => {
|
|
17
|
+
seen.push(url);
|
|
18
|
+
const u = new URL(url);
|
|
19
|
+
const cursor = u.searchParams.get('cursor');
|
|
20
|
+
const start = cursor ? rows.findIndex(r => r.id === cursor) + 1 : 0;
|
|
21
|
+
const slice = rows.slice(start, start + pageSize);
|
|
22
|
+
const last = start + pageSize >= rows.length;
|
|
23
|
+
return new Response(JSON.stringify({
|
|
24
|
+
success: true,
|
|
25
|
+
data: slice,
|
|
26
|
+
error: null,
|
|
27
|
+
meta: last ? { has_more: false } : { has_more: true, next_cursor: slice[slice.length - 1]?.id },
|
|
28
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
29
|
+
});
|
|
30
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
31
|
+
return { seen };
|
|
32
|
+
}
|
|
33
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
34
|
+
describe('requestAll', () => {
|
|
35
|
+
it('returns every row across pages, in order, exactly once', async () => {
|
|
36
|
+
const rows = Array.from({ length: 23 }, (_, i) => ({ id: `r${i}` }));
|
|
37
|
+
pagingServer(rows, 5);
|
|
38
|
+
const got = await requestAll('https://api.test/things', 'k', { pageSize: 5 });
|
|
39
|
+
expect(got.map(r => r.id)).toEqual(rows.map(r => r.id));
|
|
40
|
+
expect(new Set(got.map(r => r.id)).size).toBe(23);
|
|
41
|
+
});
|
|
42
|
+
it('passes the cursor on, so it is not just asking page one repeatedly', async () => {
|
|
43
|
+
const rows = Array.from({ length: 7 }, (_, i) => ({ id: `r${i}` }));
|
|
44
|
+
const { seen } = pagingServer(rows, 3);
|
|
45
|
+
await requestAll('https://api.test/things', 'k', { pageSize: 3 });
|
|
46
|
+
expect(seen).toHaveLength(3);
|
|
47
|
+
expect(seen[0]).toContain('limit=3');
|
|
48
|
+
expect(seen[0]).not.toContain('cursor=');
|
|
49
|
+
expect(seen[1]).toContain('cursor=r2');
|
|
50
|
+
expect(seen[2]).toContain('cursor=r5');
|
|
51
|
+
});
|
|
52
|
+
it('stops on the page that says has_more is false', async () => {
|
|
53
|
+
const rows = Array.from({ length: 4 }, (_, i) => ({ id: `r${i}` }));
|
|
54
|
+
const { seen } = pagingServer(rows, 10);
|
|
55
|
+
const got = await requestAll('https://api.test/things', 'k', { pageSize: 10 });
|
|
56
|
+
expect(got).toHaveLength(4);
|
|
57
|
+
expect(seen).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
it('throws rather than returning a partial list when the server never finishes', async () => {
|
|
60
|
+
// A server that always says has_more with a fresh cursor. Returning what
|
|
61
|
+
// was collected would hand back a truncated list that looks complete —
|
|
62
|
+
// exactly the failure this helper exists to prevent, so it must fail loudly.
|
|
63
|
+
let n = 0;
|
|
64
|
+
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({
|
|
65
|
+
success: true, data: [{ id: `x${n}` }], error: null,
|
|
66
|
+
meta: { has_more: true, next_cursor: `c${n++}` },
|
|
67
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } })));
|
|
68
|
+
await expect(requestAll('https://api.test/things', 'k', { maxPages: 5 }))
|
|
69
|
+
.rejects.toThrow(/pagination_runaway|still reported more rows/);
|
|
70
|
+
});
|
|
71
|
+
it('throws when the cursor does not advance', async () => {
|
|
72
|
+
// A stuck cursor is the ascending/descending mix-up seen from the client
|
|
73
|
+
// side: the server keeps answering with the same next_cursor, so a naive
|
|
74
|
+
// loop spins forever on the same rows.
|
|
75
|
+
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({
|
|
76
|
+
success: true, data: [{ id: 'same' }], error: null,
|
|
77
|
+
meta: { has_more: true, next_cursor: 'stuck' },
|
|
78
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } })));
|
|
79
|
+
await expect(requestAll('https://api.test/things', 'k', { maxPages: 50 }))
|
|
80
|
+
.rejects.toThrow(/pagination_stalled|same cursor twice/);
|
|
81
|
+
});
|
|
82
|
+
it('unwraps a wrapped payload when told how', async () => {
|
|
83
|
+
// {queues: […]}, {clients: […]} — the wrappers kept so paging did not break
|
|
84
|
+
// existing callers.
|
|
85
|
+
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({
|
|
86
|
+
success: true, data: { queues: [{ id: 'q1' }, { id: 'q2' }] }, error: null,
|
|
87
|
+
meta: { has_more: false },
|
|
88
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } })));
|
|
89
|
+
const got = await requestAll('https://api.test/queues', 'k', {
|
|
90
|
+
select: d => (d?.queues ?? []),
|
|
91
|
+
});
|
|
92
|
+
expect(got.map(r => r.id)).toEqual(['q1', 'q2']);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-api-hq
|
|
3
|
-
version: 1.2.
|
|
3
|
+
version: 1.2.2
|
|
4
4
|
description: >
|
|
5
5
|
Auth, organizations, and billing hub. Start here to get an api_key and org_id — every other service depends on both.
|
|
6
6
|
triggers: [api key, account, organization, org, billing, balance, topup, credits, setup, defaults, brand, sync brand, doctor, health check, is my org healthy]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-e7f0a16a130603ca5664cf5c268658bffd85230fe89dc82a4fda715ff81135b8
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyApiHQ
|
|
@@ -13,7 +13,7 @@ The root service. It manages accounts, API keys, organizations, and billing. No
|
|
|
13
13
|
|
|
14
14
|
## Capabilities
|
|
15
15
|
<!-- llm:start -->
|
|
16
|
-
MyApiHQ is the platform's foundation. Every other service
|
|
16
|
+
MyApiHQ is the platform's foundation. Every other service requires both an `api_key` and (for org-scoped resources) an `org_id` minted here. Setup is one command — `myapi account setup` — which provisions an account, generates an api_key, creates a default org, and stores everything in `~/.myapi/config.json`. Subsequent commands pick up those defaults automatically.
|
|
17
17
|
|
|
18
18
|
### Anonymous vs registered accounts
|
|
19
19
|
|
|
@@ -153,6 +153,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
153
153
|
|
|
154
154
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
155
155
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
156
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
156
157
|
<!-- http:end -->
|
|
157
158
|
|
|
158
159
|
Run `myapi --help` or `myapi <command> --help` for full flag reference.
|
|
@@ -4,7 +4,7 @@ version: 1.1.1
|
|
|
4
4
|
description: >
|
|
5
5
|
Add authentication to apps you build on MyAPI — a managed OIDC identity provider for your app's END USERS (à la Kinde/Auth0). One auth tenant per org; register OIDC clients; sign users in with managed Google or the hosted login page; verify RS256 tokens against the tenant JWKS.
|
|
6
6
|
triggers: [auth, authentication, login, sign-in, oidc, oauth, jwt, jwks, sso, google sign-in, user accounts, identity provider, kinde, auth0, clerk]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-92ed5cff8e8cd5bed3f59564b3ebb6a66eb572f0914de28322fbda1b8b6ae943
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyAuthAPI
|
|
@@ -142,4 +142,5 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
142
142
|
|
|
143
143
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
144
144
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
145
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
145
146
|
<!-- http:end -->
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-container-api
|
|
3
|
-
version: 1.1.
|
|
3
|
+
version: 1.1.1
|
|
4
4
|
description: >
|
|
5
5
|
Run containers on demand — long-running services, background workers, and scheduled jobs. The heavier-duty sibling of edge functions, for native deps and long execution.
|
|
6
6
|
triggers: [container, cloud run, dynamic app, custom domain app, service, worker, scheduled job, deploy container, docker image]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-80d64f6086e3d16549e530464b8188d9b9db2db5b54696e85c9ddde60d5b40a0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyContainerAPI
|
|
11
11
|
|
|
12
|
-
A container runs a pre-built image on managed cloud infrastructure. Three types: a **service** (HTTP server, scales to zero), a **worker** (always-on
|
|
12
|
+
A container runs a pre-built image on managed cloud infrastructure. Three types: a **service** (HTTP server, scales to zero), a **worker** (always-on process), or a **job** (runs to completion — the only type that takes a cron schedule).
|
|
13
13
|
|
|
14
14
|
## Capabilities
|
|
15
15
|
<!-- llm:start -->
|
|
@@ -55,8 +55,6 @@ it, so the probe would never reach your container.
|
|
|
55
55
|
|
|
56
56
|
`myapi container domain <id> <domain>` binds a custom domain to a **deployed** container, over HTTPS automatically. The path for a dynamic backend on a real domain — unlike `my-funnel-api`, which serves static sites.
|
|
57
57
|
|
|
58
|
-
Get it right:
|
|
59
|
-
|
|
60
58
|
- **Deploy first.** Binding a domain to a container that has never deployed fails (422) — there is nothing running to route to.
|
|
61
59
|
- **Register the parent domain first** via `my-domain-api`. Binding `app.synthesisdaily.com` requires `synthesisdaily.com` registered in MyAPI; otherwise 422.
|
|
62
60
|
- **One domain per container.** Re-binding, or binding a hostname already taken, fails (409).
|
|
@@ -168,6 +166,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
168
166
|
|
|
169
167
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
170
168
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
169
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
171
170
|
<!-- http:end -->
|
|
172
171
|
|
|
173
172
|
Run `myapi container --help` for the full flag reference.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-crm-api
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.2
|
|
4
4
|
description: >
|
|
5
5
|
The canonical store of engaged contacts + companies for an org. Auto-ingests from inbound webhooks via a configurable dot-path. Fixed lifecycle_stage enum (cold | warm | qualified | customer | churned). Append-only event timeline with reserved kinds. Soft delete + restore. Promote-from-Goldfox closes the discovery → engagement loop.
|
|
6
6
|
triggers: [crm, contact, company, lead, engagement, pipeline, lifecycle, qualified, customer, webhook ingest, promote]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-ac33bd091d64089bf1d43b20431eff5361e018406bb5046f3c27fd628f7082fb
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyCRMAPI
|
|
@@ -63,7 +63,7 @@ Coming next (backend wiring in progress):
|
|
|
63
63
|
|
|
64
64
|
If a contact doesn't exist for the matched email, it's auto-created with `source=` matching the originating service. The contact's company is auto-linked by email domain (creates the company on first sight).
|
|
65
65
|
|
|
66
|
-
**Missing lead? Check `myapi webhook deliveries` before concluding it never arrived.**
|
|
66
|
+
**Missing lead? Check `myapi webhook deliveries` before concluding it never arrived.** Raw payloads are always stored, so the delivery is there even when the contact isn't.
|
|
67
67
|
|
|
68
68
|
### Soft delete + restore
|
|
69
69
|
|
|
@@ -71,7 +71,7 @@ If a contact doesn't exist for the matched email, it's auto-created with `source
|
|
|
71
71
|
|
|
72
72
|
### Goldfox enrichment (deferred)
|
|
73
73
|
|
|
74
|
-
A contact promoted from Goldfox carries a `goldfox_person_id
|
|
74
|
+
A contact promoted from Goldfox carries a `goldfox_person_id`; the embedded `goldfox_person` is null today, and Goldfox-only fields are not searchable.
|
|
75
75
|
|
|
76
76
|
### Search filter — re-engagement semantics
|
|
77
77
|
|
|
@@ -180,6 +180,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
180
180
|
|
|
181
181
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
182
182
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
183
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
183
184
|
<!-- http:end -->
|
|
184
185
|
|
|
185
186
|
Run `myapi crm --help` or `myapi crm <namespace> --help` for inline reference.
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Per-org KV store with named namespaces, JSON values up to 256 KB, prefix-scan listing, and compare-and-swap via etag. The substrate for any stateful agent-built app on MyAPI — user tables, session stores, idempotency keys, per-user lookup maps.
|
|
6
6
|
triggers: [database, kv, key value, namespace, store, state, etag, cas, session, idempotency]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-91f0ba9f2dba98bcb322951455b153584b667c4570e5536d39dcf10c6cec44c6
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyDatabaseAPI
|
|
@@ -119,6 +119,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
119
119
|
|
|
120
120
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
121
121
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
122
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
122
123
|
<!-- http:end -->
|
|
123
124
|
|
|
124
125
|
Run `myapi database --help` for inline reference.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-feedback-api
|
|
3
|
-
version: 1.7.
|
|
3
|
+
version: 1.7.1
|
|
4
4
|
description: >
|
|
5
5
|
Collect feedback from the people using what you built. A public widget key lets a page submit without a credential; you list, filter and resolve the results. Kind is chosen by the person reporting, not inferred from their wording.
|
|
6
6
|
triggers: [feedback, bug report, user feedback, feature request, widget, support, complaints, praise]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-144cac696fd3eecf228682929be063a9c26ad3d0f921c41d9ccb433255609c7e
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFeedbackAPI
|
|
@@ -75,8 +75,8 @@ probed), `ORIGIN_NOT_ALLOWED`, `RATE_LIMITED`, `INVALID_KIND`, `BODY_REQUIRED`,
|
|
|
75
75
|
says it is**. A `bug` means they believe the product is broken — more urgent
|
|
76
76
|
than a wish for something new. Do not re-classify from the wording.
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
Nothing classifies or de-duplicates it: `kind` is the person's label, not a
|
|
79
|
+
processed signal.
|
|
80
80
|
|
|
81
81
|
### Controlling what a report contains
|
|
82
82
|
|
|
@@ -148,7 +148,7 @@ one, so success is not proof it existed — so ids cannot be probed.
|
|
|
148
148
|
| Command | What it does |
|
|
149
149
|
|---|---|
|
|
150
150
|
| `myapi feedback create "<text>" --kind <k>` | Record one item (`--page-url`, `--route` for context) |
|
|
151
|
-
| `myapi feedback list [--kind bug\|issue\|suggestion] [--status open\|resolved] [--limit N] [--offset N] [--trace]` | List feedback, newest first. Each report summarises what was pointed at
|
|
151
|
+
| `myapi feedback list [--kind bug\|issue\|suggestion] [--status open\|resolved] [--limit N] [--offset N] [--trace]` | List feedback, newest first. Each report summarises what was pointed at and whether a screenshot exists; `--trace` expands the events |
|
|
152
152
|
| `myapi feedback resolve <id>` | Close an item, keeping it |
|
|
153
153
|
| `myapi feedback test <id> [--base <url>] [--out <p>]` | Render the report as a Playwright spec (stdout, or `--out`) |
|
|
154
154
|
| `myapi feedback delete <id>` | Erase an item — for spam, or personal details typed into the box |
|
|
@@ -199,6 +199,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
199
199
|
|
|
200
200
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
201
201
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
202
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
202
203
|
<!-- http:end -->
|
|
203
204
|
|
|
204
205
|
Run `myapi feedback --help` for the full flag reference.
|
|
@@ -4,7 +4,7 @@ version: 1.1.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Deploy JavaScript functions to the MyAPI edge runtime. Register a function, upload a single-file JS bundle, get a live HTTP invocation URL or run it on a cron schedule. Each function gets a scoped capability key for cross-slot calls.
|
|
6
6
|
triggers: [function, deploy function, edge function, serverless, cloudflare worker, cron, scoped api key, capability key, invocation url, bundle]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-1e3179c62d2eb1978c6e857e11c7a86c39f3cf784b7de7755ebcb05666b90a17
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFunctionAPI
|
|
@@ -159,6 +159,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
159
159
|
|
|
160
160
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
161
161
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
162
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
162
163
|
<!-- http:end -->
|
|
163
164
|
|
|
164
165
|
Run `myapi fn --help` or `myapi fn <subcommand> --help` for full flag reference.
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-llm-api
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.3.0
|
|
4
4
|
description: >
|
|
5
5
|
Two-surface LLM primitive. Raw chat completion against self-hosted
|
|
6
6
|
open-source models (you pick the model), and objective verbs
|
|
7
7
|
(classify / extract / summarize / draft) that hide the model behind a
|
|
8
8
|
task. Pricing in cents per 1M tokens; charged from your MyAPI balance.
|
|
9
9
|
triggers: [llm, completion, chat, embed, embedding, inference, classify, extract, summarize, draft, qwen]
|
|
10
|
-
checksum: sha256-
|
|
10
|
+
checksum: sha256-d63ee7e22984a21313b70af961cee46525c60e7c727372b6c0e2b369c81989d4
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# MyLLMAPI
|
|
14
14
|
|
|
15
|
-
A two-surface LLM gateway:
|
|
16
|
-
|
|
17
|
-
- **Raw** — `complete` / `embed` / `models`. You pick a self-hosted catalog model (run `myapi llm models` for the live list). Proprietary models are not callable here.
|
|
18
|
-
- **Verbs** — `classify` / `extract` / `summarize` / `draft`. You ask for a task done; the model is implementation detail.
|
|
15
|
+
A two-surface LLM gateway: **raw** (`complete`/`embed`/`models`) against a self-hosted catalog you pick from, and **verbs** (`classify`/`extract`/`summarize`/`draft`) where the model is implementation detail. Proprietary models are not callable here.
|
|
19
16
|
|
|
20
17
|
## Capabilities
|
|
21
18
|
<!-- llm:start -->
|
|
22
|
-
For workflow tasks — summarize, classify, extract, draft. The reply goes to stdout and the usage footer (tokens, cost, finish reason) to stderr, so `myapi llm complete ... | jq` works.
|
|
23
|
-
|
|
24
19
|
**Don't use this as your own model.** If you are an agent reading this, you already have a more capable model. Reach for the LLM verbs when scripting a recurring step where a small, cheap model is the right tool — not for one-shot reasoning you can do yourself.
|
|
25
20
|
|
|
26
21
|
Reach for raw `complete` when shape matters (you build the `messages` array and set `max_tokens`/`temperature`/`stop`); reach for a verb when you want a *result* and don't care which model produced it.
|
|
@@ -55,6 +50,26 @@ Roles: `system | user | assistant`. Multiple system messages collapse to one ins
|
|
|
55
50
|
|
|
56
51
|
If `model` isn't in the self-hosted catalog the server returns `MODEL_NOT_IN_RAW_CATALOG` — that's the signal to use a verb instead, not to retry with a different `--model`.
|
|
57
52
|
|
|
53
|
+
### Context caches — a tenth to re-read
|
|
54
|
+
|
|
55
|
+
Sending the same long prefix on every call (system prompt, reference doc,
|
|
56
|
+
schema)? Store it once and re-read it at a **tenth** the input price. Measured
|
|
57
|
+
on the same 6.9k-token prefix: **0.0073¢ cached vs 0.0629¢ inline.**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
myapi llm cache create --file ./system-prompt.md --model <id> --ttl 3600
|
|
61
|
+
myapi llm complete "..." --model <id> --cache <cache-id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **Bound to ONE model** — another model cannot read it.
|
|
65
|
+
- **It bills for EXISTING, not for being read.** Storage accrues per hour from
|
|
66
|
+
creation whether anyone reads it or not, so `cache delete` is how you stop
|
|
67
|
+
paying; letting the TTL lapse means paying the TTL in full.
|
|
68
|
+
- **`ttl_seconds` is capped at 24h** — silently shortened, not refused. Read
|
|
69
|
+
`expires_at` from the reply rather than trusting your own input.
|
|
70
|
+
- **`usage.cached_input_tokens` is a SUBSET of `input_tokens`**, never an
|
|
71
|
+
addition — summing them double-counts every cached read.
|
|
72
|
+
|
|
58
73
|
### Raw `embed`
|
|
59
74
|
|
|
60
75
|
Embed text into a dense vector. `--model` is optional when the catalog serves exactly one embed model; else pass one from `myapi llm models --kind embed`. Returns `EMBED_NOT_AVAILABLE` when none is served.
|
|
@@ -63,7 +78,7 @@ Embed text into a dense vector. `--model` is optional when the catalog serves ex
|
|
|
63
78
|
- Chat models: `id`, `kind: 'chat'`, `context_window`, `input_cost_per_1m_cents`, `output_cost_per_1m_cents`
|
|
64
79
|
- Embed models: `id`, `kind: 'embed'`, `dimensions`, `input_cost_per_1m_cents`
|
|
65
80
|
|
|
66
|
-
The catalog is **live
|
|
81
|
+
The catalog is **live**, refreshed every 15 minutes.
|
|
67
82
|
|
|
68
83
|
### Verb requests + responses
|
|
69
84
|
|
|
@@ -89,13 +104,8 @@ The model/provider is **never** named in the verb response — the verb is the c
|
|
|
89
104
|
`POST /llm/orgs/{org_id}/chat/completions` (alias `/v1/chat/completions`) takes and returns the OpenAI shape — **no envelope**. Same catalog and pricing as `complete`. Use it when an existing OpenAI SDK or LangChain integration should point at MyAPI unchanged.
|
|
90
105
|
|
|
91
106
|
```python
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
api_key="hq_live_…",
|
|
95
|
-
base_url="https://api.myapihq.com/llm/orgs/<org_id>/v1",
|
|
96
|
-
)
|
|
97
|
-
r = client.chat.completions.create(model="<model-id>",
|
|
98
|
-
messages=[{"role":"user","content":"Hi"}])
|
|
107
|
+
client = OpenAI(api_key="hq_live_…",
|
|
108
|
+
base_url="https://api.myapihq.com/llm/orgs/<org_id>/v1")
|
|
99
109
|
```
|
|
100
110
|
|
|
101
111
|
<!-- llm:end -->
|
|
@@ -111,6 +121,7 @@ r = client.chat.completions.create(model="<model-id>",
|
|
|
111
121
|
| `myapi llm extract "<input>" --schema <path\|json> [--tier <t>] [--json]` | Pull structured data conforming to a JSON Schema |
|
|
112
122
|
| `myapi llm summarize "<input>" [--style brief\|exec\|bullet] [--tier <t>] [--json]` | Summarize text |
|
|
113
123
|
| `myapi llm draft --kind <what> [--prompt "<s>"] [--facts <json>] [--directives <json>] ["<src>"] [--tier <t>] [--json]` | Draft an email / reply / message / … |
|
|
124
|
+
| `myapi llm cache <create --file <p> [--model <id>] [--ttl N] \| list \| delete <id>>` | Store a prompt prefix; re-read it at a tenth the input price |
|
|
114
125
|
<!-- generated:end -->
|
|
115
126
|
|
|
116
127
|
Pass `-` as the prompt/input to read from stdin. Pass `--file <path>` to read longer content from disk.
|
|
@@ -120,24 +131,17 @@ Pass `-` as the prompt/input to read from stdin. Pass `--file <path>` to read lo
|
|
|
120
131
|
```bash
|
|
121
132
|
# List the live catalog
|
|
122
133
|
myapi llm models
|
|
123
|
-
myapi llm models --kind chat --json | jq '.models[].id'
|
|
124
134
|
|
|
125
135
|
# Raw completion — picks the first chat model from the catalog
|
|
126
136
|
myapi llm complete "Summarize in 12 words: $(cat README.md)"
|
|
127
137
|
|
|
128
|
-
# Pin a specific model (ids come from `myapi llm models`)
|
|
129
|
-
myapi llm complete "Refactor this function: ..." \
|
|
130
|
-
--model <model-id> \
|
|
131
|
-
--system "You are a careful Go reviewer." \
|
|
132
|
-
--max-tokens 600
|
|
133
|
-
|
|
134
138
|
# ── Verbs (recommended for workflow steps) ──────────────────────────────
|
|
135
139
|
|
|
136
140
|
myapi llm classify "I was charged twice — please refund." \
|
|
137
141
|
--labels billing,technical,sales,spam
|
|
138
142
|
|
|
139
143
|
myapi llm extract "Acme Corp employs 250 people in Berlin." \
|
|
140
|
-
--schema '{"type":"object","properties":{"company":{"type":"string"}
|
|
144
|
+
--schema '{"type":"object","properties":{"company":{"type":"string"}}}'
|
|
141
145
|
|
|
142
146
|
myapi llm summarize --file long-thread.txt --style bullet
|
|
143
147
|
|
|
@@ -154,25 +158,13 @@ INTENT=$(printf '%s' "$BODY" | myapi llm classify - \
|
|
|
154
158
|
|
|
155
159
|
## Notes
|
|
156
160
|
|
|
157
|
-
- **`draft --facts` safety.** Fact values are quoted
|
|
161
|
+
- **`draft --facts` safety.** Fact values are quoted verbatim and sensitive-named keys are NOT redacted. Instruction-like keys are stripped into `meta.warnings`, and fact values echoed in the output are listed in `meta.guardrails.facts_in_output` — signal, not redaction. Never put credentials or PII in `--facts`; pass identifiers and reference them indirectly.
|
|
162
|
+
- **`--facts` vs `--directives`.** Facts are referent data (recipient, dates, amounts), quoted as reference and never as instructions; directives are writer controls only (tone, max_words, format). They are trusted differently. `--context` is the old name for `--facts`.
|
|
158
163
|
- **`402`** — `INSUFFICIENT_FUNDS`: top up or enable `myapi billing auto-recharge`. `SPEND_CAP_EXCEEDED`: raise your own ceiling with `myapi billing spend-cap`.
|
|
159
164
|
- **Self-hosted raw, server-picked verbs.** Raw runs on MyAPI's TPU; verbs route wherever the server picks.
|
|
160
165
|
- **Cost + latency.** `usage.cost_cents` is authoritative — no markup. Varies by tier: 200–600 ms to first token, 1–3 s end-to-end.
|
|
161
166
|
- **Live catalog, no streaming, no BYOK.** Don't hard-code ids — `models` is truth (CLI auto-picks if `--model` omitted). Full reply only.
|
|
162
167
|
|
|
163
|
-
## `--facts` vs `--directives` on draft
|
|
164
|
-
|
|
165
|
-
`--facts '<json>'` is referent data, quoted as reference and never as
|
|
166
|
-
instructions (recipient, dates, amounts). `--directives '<json>'` is writer
|
|
167
|
-
controls only: tone, max_words, format, style. They are trusted differently.
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
myapi llm draft --kind email --prompt "the invoice is due" \
|
|
171
|
-
--facts '{"to":"Ada"}' --directives '{"tone":"warm"}'
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
`--context` is the old name for `--facts`; accepted, deprecated upstream.
|
|
175
|
-
|
|
176
168
|
## HTTP (from deployed code)
|
|
177
169
|
|
|
178
170
|
<!-- http:start -->
|
|
@@ -4,7 +4,7 @@ version: 1.0.1
|
|
|
4
4
|
description: >
|
|
5
5
|
Durable job queue — enqueue work and have it retried against your HTTP consumer, with concurrency caps and a dependency DAG.
|
|
6
6
|
triggers: [queue, job queue, background job, enqueue, retry, async work, dead letter, delayed job, concurrency]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-762f5f68386044a4313935f14643c8cbd784e7c6a4133308d0f14658062c9a0b
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyQueueAPI
|
|
@@ -89,6 +89,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
89
89
|
|
|
90
90
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
91
91
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
92
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
92
93
|
<!-- http:end -->
|
|
93
94
|
|
|
94
95
|
## workflow vs queue vs task
|
|
@@ -4,7 +4,7 @@ version: 1.1.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Edge-hosted asset storage. Upload a local file of any content type directly, or have the server fetch from a public URL. Each asset gets a stable public CDN URL.
|
|
6
6
|
triggers: [storage, upload, ingest, asset, cdn, image hosting, file upload, get-url, download, public url]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-ef0154ed196082be84192c7ddfb4cab47641549ed9703087c9eb4643daea678a
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyStorageAPI
|
|
@@ -134,6 +134,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
134
134
|
|
|
135
135
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
136
136
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
137
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
137
138
|
<!-- http:end -->
|
|
138
139
|
|
|
139
140
|
Run `myapi storage --help` for full flag reference.
|
|
@@ -4,7 +4,7 @@ version: 1.0.1
|
|
|
4
4
|
description: >
|
|
5
5
|
Inbound webhook endpoints for non-funnel sources — Stripe, GitHub, custom services. Funnel forms use the my-funnel-api proxy.
|
|
6
6
|
triggers: [webhook, inbound, receiver, stripe events, github webhook, slack notification, delivery, payload, event ingest]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-aff831f651b965cc48fd26bbd20c04bd44443a2073917a4660aefaffc5091dcd
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyWebhookAPI
|
|
@@ -127,6 +127,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
127
127
|
|
|
128
128
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
129
129
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
130
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
130
131
|
<!-- http:end -->
|
|
131
132
|
|
|
132
133
|
Run `myapi webhook --help` for full flag reference.
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Run actions when a webhook fires. Trigger emails, Slack notifications, or HTTP calls in response to inbound webhook deliveries — without writing a backend.
|
|
6
6
|
triggers: [workflow, automation, on webhook, send email on, slack notification, payload templating, drip, trigger, run]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-0a1607bd1a89cd13a052b426fa2262cc1cef71c52acf8c63fc7e8026ba6ba52c
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyWorkflowAPI
|
|
@@ -128,6 +128,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
128
128
|
|
|
129
129
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
130
130
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
131
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
131
132
|
<!-- http:end -->
|
|
132
133
|
|
|
133
134
|
Run `myapi workflow --help` for full flag reference.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.21.0",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@myapihq/sdk": "^2.
|
|
49
|
+
"@myapihq/sdk": "^2.21.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|