@myapihq/cli 2.2.0 → 2.4.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/account.d.ts +1 -1
- package/dist/commands/account.js +1 -0
- package/dist/commands/authproduct.js +7 -3
- package/dist/commands/billing.js +7 -2
- package/dist/commands/container.js +4 -3
- package/dist/commands/database.js +4 -3
- package/dist/commands/domain.js +39 -14
- package/dist/commands/email/message.js +6 -3
- package/dist/commands/email/template.js +3 -3
- package/dist/commands/fn.js +4 -3
- package/dist/commands/funnel.js +33 -13
- package/dist/commands/git.js +108 -35
- package/dist/commands/image.js +7 -4
- package/dist/commands/keys-validation.test.js +5 -0
- package/dist/commands/keys.js +6 -2
- package/dist/commands/llm.js +41 -20
- package/dist/commands/login-validation.test.d.ts +1 -0
- package/dist/commands/login-validation.test.js +43 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.js +447 -0
- package/dist/commands/org.d.ts +1 -1
- package/dist/commands/org.js +24 -7
- package/dist/commands/queue.js +4 -3
- package/dist/commands/setup.js +6 -1
- package/dist/commands/storage.js +1 -1
- package/dist/commands/workflow.js +5 -4
- package/dist/completion.js +3 -3
- package/dist/config.js +3 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +62 -0
- package/dist/errors.test.d.ts +1 -0
- package/dist/errors.test.js +28 -0
- package/dist/exposes.test.js +1 -0
- package/dist/flags.js +5 -3
- package/dist/flags.test.js +1 -1
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +22 -0
- package/dist/index.js +10 -51
- package/dist/skills/my-api-hq/SKILL.md +2 -2
- package/dist/skills/my-domain-api/SKILL.md +5 -5
- package/dist/skills/my-email-verify-api/SKILL.md +6 -4
- package/dist/skills/my-funnel-api/SKILL.md +5 -4
- package/dist/skills/my-git-api/SKILL.md +14 -7
- package/dist/skills/my-image-api/SKILL.md +1 -1
- package/dist/skills/my-llm-api/SKILL.md +11 -11
- package/dist/skills/my-storage-api/SKILL.md +5 -5
- package/dist/skills/my-webhook-api/SKILL.md +1 -1
- package/dist/skills/my-workflow-api/SKILL.md +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -1
- package/package.json +3 -2
package/dist/errors.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Per-code friendly messages for MyApiError.code. Kept in its own module (not
|
|
2
|
+
// index.ts) so it can be unit-tested without importing the CLI entrypoint,
|
|
3
|
+
// which runs main() on import.
|
|
4
|
+
export const ERROR_MESSAGES = {
|
|
5
|
+
DOMAIN_NOT_FOUND: 'Domain not found.',
|
|
6
|
+
INVALID_DOMAIN: 'Invalid domain name.',
|
|
7
|
+
ORG_NOT_FOUND: 'Organization not found.',
|
|
8
|
+
org_not_found: 'Organization not found.',
|
|
9
|
+
FUNNEL_NOT_FOUND: 'Funnel not found.',
|
|
10
|
+
funnel_not_found: 'Funnel not found.',
|
|
11
|
+
funnel_already_exists: 'Your org already has a funnel. Use: myapi funnel list',
|
|
12
|
+
FUNNEL_ALREADY_EXISTS: 'Your org already has a funnel. Use: myapi funnel list',
|
|
13
|
+
domain_already_registered: 'This domain is already registered under your account.',
|
|
14
|
+
DOMAIN_ALREADY_REGISTERED: 'This domain is already registered under your account.',
|
|
15
|
+
domain_not_available: 'This domain is not available for registration.',
|
|
16
|
+
DOMAIN_NOT_AVAILABLE: 'This domain is not available for registration.',
|
|
17
|
+
db_error: 'Resource not found or invalid ID.',
|
|
18
|
+
NOT_FOUND: 'Resource not found.',
|
|
19
|
+
domain_not_found: 'Domain not found.',
|
|
20
|
+
invalid_domain: 'Invalid domain name.',
|
|
21
|
+
invalid_org: 'Invalid organization.',
|
|
22
|
+
FORBIDDEN: 'You do not have permission to perform this action.',
|
|
23
|
+
RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
|
|
24
|
+
INSUFFICIENT_BALANCE: 'Insufficient balance. Run: myapi billing topup <amount>',
|
|
25
|
+
INVALID_AMOUNT: 'Amount out of range. Maximum single top-up is $100. Run: myapi billing topup <amount>',
|
|
26
|
+
SERVICE_NOT_LAUNCHED: 'This service is disabled pre-launch. Track availability via: myapi status',
|
|
27
|
+
RECORD_NOT_FOUND: 'DNS record not found in this zone.',
|
|
28
|
+
INVALID_RECORD_TYPE: 'Unsupported record type. Allowed: A, AAAA, CNAME, MX, TXT.',
|
|
29
|
+
MX_PRIORITY_REQUIRED: 'MX records require --priority (typical value: 10).',
|
|
30
|
+
INVALID_TTL: 'Invalid TTL. Use the auto sentinel (1) or a value between 60 and 86400 seconds.',
|
|
31
|
+
INVALID_RECORD_CONTENT: 'Invalid record content for this type.',
|
|
32
|
+
RECORD_LIMIT_EXCEEDED: 'Cloudflare per-zone record limit reached.',
|
|
33
|
+
CF_API_ERROR: 'Cloudflare API error.',
|
|
34
|
+
// invalid_json_response intentionally absent — the SDK's MyApiError now
|
|
35
|
+
// builds a useful detailed message for that case (status + URL + body
|
|
36
|
+
// snippet), and friendlyError(err.code) would override it.
|
|
37
|
+
};
|
|
38
|
+
export function friendlyError(err) {
|
|
39
|
+
const body = err.body ?? {};
|
|
40
|
+
// CF_API_ERROR: the cf_message already includes a "Cloudflare API error"
|
|
41
|
+
// prefix, so use it verbatim (with cf_status if present) instead of doubling.
|
|
42
|
+
if (err.code === 'CF_API_ERROR' && typeof body.cf_message === 'string') {
|
|
43
|
+
return typeof body.cf_status === 'number'
|
|
44
|
+
? `${body.cf_message} (HTTP ${body.cf_status})`
|
|
45
|
+
: body.cf_message;
|
|
46
|
+
}
|
|
47
|
+
// Registrar outages are platform-side and transient. Say so plainly and,
|
|
48
|
+
// crucially, tell the caller NOT to hammer retries — a retry storm on a
|
|
49
|
+
// balance/credit failure is what got the registrar to rate-limit us.
|
|
50
|
+
if (err.code === 'REGISTRAR_UNAVAILABLE') {
|
|
51
|
+
return 'Domain registration is temporarily unavailable on our side — please try again in a little while. This is a platform issue, not your account, so retrying immediately (or in a loop) will not help.';
|
|
52
|
+
}
|
|
53
|
+
if (err.code === 'REGISTRAR_RATE_LIMITED') {
|
|
54
|
+
return 'Domain registration is busy right now — please wait a few minutes before trying again (avoid automatic retries).';
|
|
55
|
+
}
|
|
56
|
+
const base = ERROR_MESSAGES[err.code] || err.code;
|
|
57
|
+
// Generic fallback: append the backend's `message` when it adds info beyond
|
|
58
|
+
// the friendly mapping. Future per-service detail fields can be added here.
|
|
59
|
+
if (err.detail && err.detail !== base)
|
|
60
|
+
return `${base} — ${err.detail}`;
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { friendlyError, ERROR_MESSAGES } from './errors.js';
|
|
3
|
+
import { MyApiError } from '@myapihq/sdk';
|
|
4
|
+
describe('friendlyError', () => {
|
|
5
|
+
it('REGISTRAR_UNAVAILABLE → clear temporary message that discourages retries', () => {
|
|
6
|
+
const msg = friendlyError(new MyApiError('REGISTRAR_UNAVAILABLE', 500, 'domain registration is temporarily unavailable'));
|
|
7
|
+
expect(msg).toMatch(/temporarily unavailable/i);
|
|
8
|
+
expect(msg).toMatch(/not help|avoid|later|loop/i); // steers away from a retry storm
|
|
9
|
+
expect(msg).not.toMatch(/openprovider|OP310/i); // never leak the provider
|
|
10
|
+
});
|
|
11
|
+
it('REGISTRAR_RATE_LIMITED → back-off message', () => {
|
|
12
|
+
const msg = friendlyError(new MyApiError('REGISTRAR_RATE_LIMITED', 503));
|
|
13
|
+
expect(msg).toMatch(/busy|wait a few minutes/i);
|
|
14
|
+
expect(msg).toMatch(/avoid automatic retries|wait/i);
|
|
15
|
+
expect(msg).not.toMatch(/openprovider/i);
|
|
16
|
+
});
|
|
17
|
+
it('maps a known code verbatim from the table', () => {
|
|
18
|
+
expect(friendlyError(new MyApiError('DOMAIN_ALREADY_REGISTERED', 409))).toBe(ERROR_MESSAGES.DOMAIN_ALREADY_REGISTERED);
|
|
19
|
+
});
|
|
20
|
+
it('appends the backend detail for generic mapped codes', () => {
|
|
21
|
+
const msg = friendlyError(new MyApiError('INVALID_DOMAIN', 400, 'bad tld'));
|
|
22
|
+
expect(msg).toContain('Invalid domain name.');
|
|
23
|
+
expect(msg).toContain('bad tld');
|
|
24
|
+
});
|
|
25
|
+
it('falls back to the raw code when unmapped with no detail', () => {
|
|
26
|
+
expect(friendlyError(new MyApiError('SOME_NEW_CODE', 400))).toBe('SOME_NEW_CODE');
|
|
27
|
+
});
|
|
28
|
+
});
|
package/dist/exposes.test.js
CHANGED
|
@@ -44,6 +44,7 @@ const COMMAND_MODULES = [
|
|
|
44
44
|
'./commands/queue.js',
|
|
45
45
|
'./commands/task.js',
|
|
46
46
|
'./commands/doctor.js',
|
|
47
|
+
'./commands/login.js',
|
|
47
48
|
];
|
|
48
49
|
const ENDPOINT_PATTERN = /^(GET|POST|PATCH|PUT|DELETE) \/[A-Za-z0-9_\-./{}]*$/;
|
|
49
50
|
describe('every CLI command exports a typed EXPOSES array (S-101)', () => {
|
package/dist/flags.js
CHANGED
|
@@ -23,7 +23,7 @@ export function parseFlags(argv, schema = {}) {
|
|
|
23
23
|
flags.help = true;
|
|
24
24
|
continue;
|
|
25
25
|
}
|
|
26
|
-
if (arg === '-v') {
|
|
26
|
+
if (arg === '-v' || arg === '-V') {
|
|
27
27
|
flags.version = true;
|
|
28
28
|
continue;
|
|
29
29
|
}
|
|
@@ -51,7 +51,7 @@ export function parseFlags(argv, schema = {}) {
|
|
|
51
51
|
// `--on-error <email>` without breaking back-compat.
|
|
52
52
|
if (type === undefined) {
|
|
53
53
|
flags[key] = inlineValue ?? true;
|
|
54
|
-
|
|
54
|
+
let note = `--${key}`;
|
|
55
55
|
// If no inline value and the next token doesn't look like another
|
|
56
56
|
// flag, the user almost certainly intended it as the flag's value
|
|
57
57
|
// (e.g. `--on-error someone@x.com`). Consume it so it doesn't
|
|
@@ -60,9 +60,11 @@ export function parseFlags(argv, schema = {}) {
|
|
|
60
60
|
const next = argv[i + 1];
|
|
61
61
|
if (next !== undefined && !next.startsWith('--') && !next.startsWith('-h') && !next.startsWith('-v')) {
|
|
62
62
|
flags[key] = next;
|
|
63
|
+
note = `--${key} (took value "${next}")`;
|
|
63
64
|
i++;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
unknownFlags.push(note);
|
|
66
68
|
continue;
|
|
67
69
|
}
|
|
68
70
|
if (type === 'boolean') {
|
|
@@ -104,7 +106,7 @@ export function parseFlags(argv, schema = {}) {
|
|
|
104
106
|
// disappear. Don't block — the value is still in `flags` for any handler
|
|
105
107
|
// that wants it — just print one line on stderr.
|
|
106
108
|
if (unknownFlags.length > 0 && !process.env.MYAPI_QUIET_UNKNOWN_FLAGS) {
|
|
107
|
-
process.stderr.write(`› Note:
|
|
109
|
+
process.stderr.write(`› Note: unknown flag(s) not recognized by this command: ${unknownFlags.join(', ')} — check for typos.\n`);
|
|
108
110
|
}
|
|
109
111
|
return { args, flags };
|
|
110
112
|
}
|
package/dist/flags.test.js
CHANGED
|
@@ -103,7 +103,7 @@ describe('parseFlags', () => {
|
|
|
103
103
|
process.stderr.write = origWrite;
|
|
104
104
|
}
|
|
105
105
|
const out = stderrChunks.join('');
|
|
106
|
-
expect(out).toMatch(/
|
|
106
|
+
expect(out).toMatch(/unknown flag/i);
|
|
107
107
|
expect(out).toMatch(/--mystery/);
|
|
108
108
|
expect(out).toMatch(/--also-unknown/);
|
|
109
109
|
});
|
package/dist/helpers.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export type Flags = Record<string, string | boolean | number>;
|
|
|
3
3
|
export declare function requireOrg(flags: Flags, config: Config, usage: string): string;
|
|
4
4
|
export declare function requireDomain(arg: string | undefined, flags: Flags, config: Config, usage: string): string;
|
|
5
5
|
export declare function requireArg(value: string | undefined, name: string, usage: string): string;
|
|
6
|
+
export declare function confirmDestructive(flags: Flags, description: string, usage: string): Promise<void>;
|
|
6
7
|
export declare function requireFlag(flags: Flags, name: string, usage: string): string;
|
package/dist/helpers.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { error } from './output.js';
|
|
2
|
+
import { confirm, isNonInteractive } from './prompt.js';
|
|
2
3
|
// error() returns `never`, so after `if (!x) error(...)` TS narrows x to a
|
|
3
4
|
// non-falsy value and the casts disappear.
|
|
4
5
|
export function requireOrg(flags, config, usage) {
|
|
6
|
+
// A present-but-empty --org (e.g. --org "$ORG" with $ORG unset, or a bare
|
|
7
|
+
// --org that swallowed no value) must never degrade to the default org —
|
|
8
|
+
// that is exactly the silent wrong-tenant path. Fail loudly instead.
|
|
9
|
+
if ('org' in flags && (flags.org === '' || flags.org === true)) {
|
|
10
|
+
error(`--org was passed without a value. Refusing to fall back to the default org.\nUsage: ${usage}`);
|
|
11
|
+
}
|
|
5
12
|
const orgId = (typeof flags.org === 'string' ? flags.org : '') || config.default_org;
|
|
6
13
|
if (!orgId) {
|
|
7
14
|
error(`Missing required arguments.\nUsage: ${usage}\n(Or set default: myapi config set-org <id>)`);
|
|
@@ -22,6 +29,21 @@ export function requireArg(value, name, usage) {
|
|
|
22
29
|
error(`Missing required argument: ${name}.\nUsage: ${usage}`);
|
|
23
30
|
return value;
|
|
24
31
|
}
|
|
32
|
+
// Gate for irreversible writes. Interactive runs get a y/N prompt; --yes
|
|
33
|
+
// skips it; non-interactive runs (CI, agents) hard-refuse without --yes so a
|
|
34
|
+
// destructive command can never ride a silent default. `description` should
|
|
35
|
+
// name the exact target incl. the org it lives in, e.g.
|
|
36
|
+
// `delete funnel "landing" in org abc-123`.
|
|
37
|
+
export async function confirmDestructive(flags, description, usage) {
|
|
38
|
+
if (flags.yes)
|
|
39
|
+
return;
|
|
40
|
+
if (isNonInteractive()) {
|
|
41
|
+
error(`Refusing to ${description} without --yes.\nUsage: ${usage}`);
|
|
42
|
+
}
|
|
43
|
+
const ok = await confirm(`› Really ${description}? This cannot be undone. (y/N) `, false);
|
|
44
|
+
if (!ok)
|
|
45
|
+
error('Aborted.');
|
|
46
|
+
}
|
|
25
47
|
export function requireFlag(flags, name, usage) {
|
|
26
48
|
const v = flags[name];
|
|
27
49
|
if (v === undefined || v === true || v === false || v === '') {
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { error, info, success, banner } from './output.js';
|
|
3
3
|
import { loadConfig } from './config.js';
|
|
4
4
|
import { MyApiError } from '@myapihq/sdk';
|
|
5
|
+
import { friendlyError } from './errors.js';
|
|
5
6
|
import * as fs from 'fs';
|
|
6
7
|
import { parseFlags } from './flags.js';
|
|
7
8
|
const pkgPath = new URL('../package.json', import.meta.url);
|
|
@@ -37,6 +38,7 @@ import * as gitCmd from './commands/git.js';
|
|
|
37
38
|
import * as queueCmd from './commands/queue.js';
|
|
38
39
|
import * as taskCmd from './commands/task.js';
|
|
39
40
|
import * as doctorCmd from './commands/doctor.js';
|
|
41
|
+
import * as loginCmd from './commands/login.js';
|
|
40
42
|
// Each command file declares the value flags it understands. We union them
|
|
41
43
|
// into a single schema for the upfront parse, so adding a new value flag in
|
|
42
44
|
// one command means editing one file (its SCHEMA), not a global allowlist.
|
|
@@ -70,60 +72,11 @@ const COMBINED_SCHEMA = {
|
|
|
70
72
|
...queueCmd.SCHEMA,
|
|
71
73
|
...taskCmd.SCHEMA,
|
|
72
74
|
...doctorCmd.SCHEMA,
|
|
75
|
+
...loginCmd.SCHEMA,
|
|
73
76
|
// Top-level flags
|
|
74
77
|
version: 'boolean',
|
|
75
78
|
V: 'boolean',
|
|
76
79
|
};
|
|
77
|
-
const ERROR_MESSAGES = {
|
|
78
|
-
DOMAIN_NOT_FOUND: 'Domain not found.',
|
|
79
|
-
INVALID_DOMAIN: 'Invalid domain name.',
|
|
80
|
-
ORG_NOT_FOUND: 'Organization not found.',
|
|
81
|
-
org_not_found: 'Organization not found.',
|
|
82
|
-
FUNNEL_NOT_FOUND: 'Funnel not found.',
|
|
83
|
-
funnel_not_found: 'Funnel not found.',
|
|
84
|
-
funnel_already_exists: 'Your org already has a funnel. Use: myapi funnel list',
|
|
85
|
-
FUNNEL_ALREADY_EXISTS: 'Your org already has a funnel. Use: myapi funnel list',
|
|
86
|
-
domain_already_registered: 'This domain is already registered under your account.',
|
|
87
|
-
DOMAIN_ALREADY_REGISTERED: 'This domain is already registered under your account.',
|
|
88
|
-
domain_not_available: 'This domain is not available for registration.',
|
|
89
|
-
DOMAIN_NOT_AVAILABLE: 'This domain is not available for registration.',
|
|
90
|
-
db_error: 'Resource not found or invalid ID.',
|
|
91
|
-
NOT_FOUND: 'Resource not found.',
|
|
92
|
-
domain_not_found: 'Domain not found.',
|
|
93
|
-
invalid_domain: 'Invalid domain name.',
|
|
94
|
-
invalid_org: 'Invalid organization.',
|
|
95
|
-
FORBIDDEN: 'You do not have permission to perform this action.',
|
|
96
|
-
RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
|
|
97
|
-
INSUFFICIENT_BALANCE: 'Insufficient balance. Run: myapi billing topup <amount>',
|
|
98
|
-
INVALID_AMOUNT: 'Amount out of range. Maximum single top-up is $100. Run: myapi billing topup <amount>',
|
|
99
|
-
SERVICE_NOT_LAUNCHED: 'This service is disabled pre-launch. Track availability via: myapi status',
|
|
100
|
-
RECORD_NOT_FOUND: 'DNS record not found in this zone.',
|
|
101
|
-
INVALID_RECORD_TYPE: 'Unsupported record type. Allowed: A, AAAA, CNAME, MX, TXT.',
|
|
102
|
-
MX_PRIORITY_REQUIRED: 'MX records require --priority (typical value: 10).',
|
|
103
|
-
INVALID_TTL: 'Invalid TTL. Use the auto sentinel (1) or a value between 60 and 86400 seconds.',
|
|
104
|
-
INVALID_RECORD_CONTENT: 'Invalid record content for this type.',
|
|
105
|
-
RECORD_LIMIT_EXCEEDED: 'Cloudflare per-zone record limit reached.',
|
|
106
|
-
CF_API_ERROR: 'Cloudflare API error.',
|
|
107
|
-
// invalid_json_response intentionally absent — the SDK's MyApiError now
|
|
108
|
-
// builds a useful detailed message for that case (status + URL + body
|
|
109
|
-
// snippet), and friendlyError(err.code) would override it.
|
|
110
|
-
};
|
|
111
|
-
function friendlyError(err) {
|
|
112
|
-
const body = err.body ?? {};
|
|
113
|
-
// CF_API_ERROR: the cf_message already includes a "Cloudflare API error"
|
|
114
|
-
// prefix, so use it verbatim (with cf_status if present) instead of doubling.
|
|
115
|
-
if (err.code === 'CF_API_ERROR' && typeof body.cf_message === 'string') {
|
|
116
|
-
return typeof body.cf_status === 'number'
|
|
117
|
-
? `${body.cf_message} (HTTP ${body.cf_status})`
|
|
118
|
-
: body.cf_message;
|
|
119
|
-
}
|
|
120
|
-
const base = ERROR_MESSAGES[err.code] || err.code;
|
|
121
|
-
// Generic fallback: append the backend's `message` when it adds info beyond
|
|
122
|
-
// the friendly mapping. Future per-service detail fields can be added here.
|
|
123
|
-
if (err.detail && err.detail !== base)
|
|
124
|
-
return `${base} — ${err.detail}`;
|
|
125
|
-
return base;
|
|
126
|
-
}
|
|
127
80
|
async function main() {
|
|
128
81
|
// Shell tab-completion: when the shell invokes us for completion it passes
|
|
129
82
|
// a --comp* marker. Handle it via a lazily-imported module so completion
|
|
@@ -246,6 +199,9 @@ async function main() {
|
|
|
246
199
|
case 'doctor':
|
|
247
200
|
await doctorCmd.run(subcommand, restArgs, flags);
|
|
248
201
|
break;
|
|
202
|
+
case 'login':
|
|
203
|
+
await loginCmd.login(flags);
|
|
204
|
+
break;
|
|
249
205
|
// Convenience aliases
|
|
250
206
|
case 'setup':
|
|
251
207
|
await setupCmd.setup(flags);
|
|
@@ -353,6 +309,7 @@ async function dispatchAccount(subcommand, restArgs, flags) {
|
|
|
353
309
|
case 'setup': return setupCmd.setup(flags);
|
|
354
310
|
case 'import-key': return setupCmd.importKey(restArgs[0], flags);
|
|
355
311
|
case 'whoami': return accountCmd.whoami(flags);
|
|
312
|
+
case 'login': return loginCmd.login(flags);
|
|
356
313
|
case 'link': return accountCmd.link(flags, restArgs[0]);
|
|
357
314
|
case 'switch': return accountCmd.switchCmd(flags, restArgs[0]);
|
|
358
315
|
case 'install-skills':
|
|
@@ -370,7 +327,7 @@ async function dispatchAccount(subcommand, restArgs, flags) {
|
|
|
370
327
|
case 'registrant': return accountCmd.registrant(restArgs[0], flags);
|
|
371
328
|
case 'api-keys':
|
|
372
329
|
case 'keys': return keysCmd.runApiKeys(restArgs[0], restArgs.slice(1), flags);
|
|
373
|
-
default:
|
|
330
|
+
default: error('Unknown subcommand. Run: myapi account --help');
|
|
374
331
|
}
|
|
375
332
|
}
|
|
376
333
|
const HELP_TARGETS = {
|
|
@@ -400,6 +357,7 @@ const HELP_TARGETS = {
|
|
|
400
357
|
billing: f => billingCmd.run(undefined, [], f),
|
|
401
358
|
keys: f => keysCmd.run(undefined, [], f),
|
|
402
359
|
config: f => configCmd.run(undefined, [], f),
|
|
360
|
+
login: f => loginCmd.login(f),
|
|
403
361
|
};
|
|
404
362
|
async function dispatchHelp(target) {
|
|
405
363
|
if (!target) {
|
|
@@ -446,6 +404,7 @@ Commands:
|
|
|
446
404
|
image Generate AI images and manage them in storage
|
|
447
405
|
install-skills Install or update the MyAPI skills pack for AI agents
|
|
448
406
|
llm Run LLM completions and embeddings (chat + embed, with usage/cost)
|
|
407
|
+
login Sign in via your browser — Google or email code (preview: --mock)
|
|
449
408
|
org Manage organizations (tip: myapi org create "name" --yes to auto-set as default)
|
|
450
409
|
payments Take payments with Stripe Checkout (connect, charge, refund)
|
|
451
410
|
people Search the contact database (filter by industry, seniority, country, ...)
|
|
@@ -49,7 +49,7 @@ An anonymous account can upgrade at any time via `myapi account link <email>`
|
|
|
49
49
|
| `myapi org create --name "..."` | Create a new org (`--yes` auto-sets as default) |
|
|
50
50
|
| `myapi org get [id]` | Inspect one org (defaults to current default) |
|
|
51
51
|
| `myapi org update [id]` | Update fields (name, tagline, description, business-sector, logo-url) |
|
|
52
|
-
| `myapi org delete <id>` | Delete an org and cascade |
|
|
52
|
+
| `myapi org delete <id>` | Delete an org and cascade (funnels included). Verify the target with `myapi org get <id>` first; asks for confirmation — `--yes` required in non-interactive runs |
|
|
53
53
|
| `myapi org sync-brand <domain>` | Scrape a live site and auto-fill brand info |
|
|
54
54
|
| `myapi keys list / create / revoke <id>` | Manage API keys (alias `myapi account api-keys`) |
|
|
55
55
|
| `myapi billing balance` | Check balance |
|
|
@@ -87,7 +87,7 @@ myapi org sync-brand acme.com
|
|
|
87
87
|
myapi account switch 2
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
If any service returns `402 INSUFFICIENT_FUNDS`, top up (`myapi billing topup`) — or enable `myapi billing auto-recharge` so it refills itself. A `402 SPEND_CAP_EXCEEDED` is different: you hit a spend ceiling you set, so raise it with `myapi billing spend-cap` rather than topping up.
|
|
90
|
+
If any service returns `402 INSUFFICIENT_FUNDS`, top up (`myapi billing topup`) — or enable `myapi billing auto-recharge` so it refills itself. With auto-recharge enabled, billable CLI commands (llm, image, email send, domain register/renew, git commit) handle a refill-in-flight automatically: they wait the server-hinted interval and retry instead of failing, so don't add your own retry loop. A `402 SPEND_CAP_EXCEEDED` is different: you hit a spend ceiling you set, so raise it with `myapi billing spend-cap` rather than topping up.
|
|
91
91
|
|
|
92
92
|
Each org gets a free preview subdomain (`*.makeautonomous.com`) usable before registering a custom domain.
|
|
93
93
|
<!-- llm:end -->
|
|
@@ -48,9 +48,9 @@ Resolution at register time (highest wins): `--registrant-json` → per-field fl
|
|
|
48
48
|
|---|---|
|
|
49
49
|
| `myapi domain check <domain>` | Check availability and yearly price |
|
|
50
50
|
| `myapi domain register <domain> [--years N] <registrant flags>` | Register a new domain (deducts credits). Requires ICANN WHOIS contact info — store once with `myapi account registrant set`, or pass per-call via `--registrant-json` / `--registrant-*` flags |
|
|
51
|
-
| `myapi domain renew <domain
|
|
51
|
+
| `myapi domain renew <domain> [--yes]` | Renew a registered domain for another period. Charges the org balance; asks for confirmation — pass `--yes` in non-interactive runs |
|
|
52
52
|
| `myapi domain list [--filter all\|unassigned\|org]` | List domains in your account |
|
|
53
|
-
| `myapi domain assign <domain>` | Assign domain to
|
|
53
|
+
| `myapi domain assign <domain> --org <id>` | Assign domain to an org. **Also the reassign path** — re-running moves a domain already assigned elsewhere. Pre-flight with `myapi domain list --filter all` and always pass `--org` explicitly |
|
|
54
54
|
| `myapi domain unassign <domain>` | Remove domain from its org |
|
|
55
55
|
| `myapi domain import <domain>` | Bring-your-own-domain. Snapshots current DNS, returns nameservers to set at your existing registrar — no registrar credentials needed |
|
|
56
56
|
| `myapi domain status <domain> [--watch]` | Registration + DNS propagation status. `--watch` polls until terminal (10s × 30 → 30s × 60) |
|
|
@@ -74,11 +74,11 @@ myapi account registrant set
|
|
|
74
74
|
# Register and bring online
|
|
75
75
|
myapi domain check example.com
|
|
76
76
|
myapi domain register example.com
|
|
77
|
-
myapi domain assign example.com
|
|
77
|
+
myapi domain assign example.com --org <org_id> # explicit --org: assign also reassigns
|
|
78
78
|
myapi domain status example.com # poll until status = active
|
|
79
79
|
|
|
80
|
-
# Renew before expiry
|
|
81
|
-
myapi domain renew example.com
|
|
80
|
+
# Renew before expiry (charges the org balance; --yes for non-interactive runs)
|
|
81
|
+
myapi domain renew example.com --yes
|
|
82
82
|
|
|
83
83
|
# Tune the edge for AI bot traffic
|
|
84
84
|
myapi domain update-settings example.com \
|
|
@@ -9,7 +9,7 @@ checksum: sha256-pending
|
|
|
9
9
|
|
|
10
10
|
# MyEmailVerifyAPI
|
|
11
11
|
|
|
12
|
-
A cheap, fast quality gate for a single email address. Runs three layers in sequence: syntax check (instant) → DNS / MX lookup (sub-second) → Microsoft GetCredentialType probe (sub-second for non-Microsoft, slower for federated domains). Returns a definitive `deliverable` / `undeliverable` verdict for about half of inputs; the rest get `verdict: 'unknown'` with `smtp_recommended: true
|
|
12
|
+
A cheap, fast quality gate for a single email address. Runs three layers in sequence: syntax check (instant) → DNS / MX lookup (sub-second) → Microsoft GetCredentialType probe (sub-second for non-Microsoft, slower for federated domains). Returns a definitive `deliverable` / `undeliverable` verdict for about half of inputs; the rest get `verdict: 'unknown'` with `smtp_recommended: true` — feed those into `verify bulk`, which SMTP-probes the uncertain addresses asynchronously.
|
|
13
13
|
|
|
14
14
|
## Capabilities
|
|
15
15
|
<!-- llm:start -->
|
|
@@ -18,7 +18,7 @@ Use this before any campaign send — pipe the audience members through `verify`
|
|
|
18
18
|
Verdicts:
|
|
19
19
|
- **`deliverable`** — high-confidence acceptance. Safe to send.
|
|
20
20
|
- **`undeliverable`** — high-confidence reject. Skip. Most common causes: malformed syntax (`missing '@'`), no MX records, Microsoft positively returns "no such mailbox".
|
|
21
|
-
- **`unknown`** — couldn't determine cheaply. The `smtp_recommended` flag will be `true`;
|
|
21
|
+
- **`unknown`** — couldn't determine cheaply. The `smtp_recommended` flag will be `true`; run those addresses through `myapi email verify bulk`, which SMTP-probes the uncertain ones (or treat as "send but watch the bounce signal").
|
|
22
22
|
|
|
23
23
|
Response shape (full check breakdown available via `--json`):
|
|
24
24
|
```json
|
|
@@ -44,6 +44,8 @@ Latency notes: syntax + DNS are <100ms. Microsoft GetCredentialType is fast for
|
|
|
44
44
|
| Command | What it does |
|
|
45
45
|
|---|---|
|
|
46
46
|
| `myapi email verify <email> [--json]` | Run all three verification layers on one address; print verdict + confidence + key checks |
|
|
47
|
+
| `myapi email verify bulk [--quick] < emails.txt` | Async batch (newline-separated stdin, ≤500 per job): cheap verdict on every address, then SMTP-probes the uncertain ones. `--quick` skips the catch-all check. Prints a `job_id` |
|
|
48
|
+
| `myapi email verify job <job_id> [--json]` | Poll a bulk job: status + per-address verdicts |
|
|
47
49
|
<!-- generated:end -->
|
|
48
50
|
|
|
49
51
|
Pass `--json` for the full check breakdown (syntax detail, MX records, Microsoft probe result). The default human render shows the verdict + a one-line summary per check that fired.
|
|
@@ -60,7 +62,7 @@ myapi email verify not-an-email
|
|
|
60
62
|
# Generic domain — unknown verdict, MX visible, confidence 0.5
|
|
61
63
|
myapi email verify hello@gmail.com
|
|
62
64
|
# → Verdict: ? unknown (confidence 0.50)
|
|
63
|
-
# → SMTP next: yes — consider
|
|
65
|
+
# → SMTP next: yes — consider a bulk job (SMTP-probes the uncertain ones)
|
|
64
66
|
# → MX: gmail-smtp-in.l.google.com, alt1...
|
|
65
67
|
|
|
66
68
|
# Full breakdown
|
|
@@ -83,7 +85,7 @@ done < emails.txt | grep -v ' undeliverable$' > verified.txt
|
|
|
83
85
|
|
|
84
86
|
## Notes
|
|
85
87
|
|
|
86
|
-
-
|
|
88
|
+
- `verify <email>` is the **cheap layer** (syntax → DNS → Microsoft probe). `verify bulk` completes the pipeline: it re-runs the cheap verdict, then SMTP-probes the uncertain addresses and runs catch-all detection (skip catch-all with `--quick`).
|
|
87
89
|
- The `confidence` field is a hint, not a guarantee. A `deliverable` verdict at 0.95 confidence is still ≈5% bounce risk in practice.
|
|
88
90
|
- For bulk verification use the async batch endpoint: `myapi email verify bulk < emails.txt` (one address per line) returns a `job_id`, then poll `myapi email verify job <job_id>` for status + per-address results.
|
|
89
91
|
- Verification is per-org; you'll get rate-limited if you blast more than ~1 req/sec per key.
|
|
@@ -30,7 +30,8 @@ Every funnel auto-provisions a **webhook** at creation time (`org_webhook_id`).
|
|
|
30
30
|
| `myapi funnel list` | List all funnels with preview/domain URLs |
|
|
31
31
|
| `myapi funnel get <id>` | Inspect a funnel's metadata + preview URL |
|
|
32
32
|
| `myapi funnel delete <id>` | Delete the funnel and purge its edge pages |
|
|
33
|
-
| `myapi funnel push [slug]` | Push HTML from stdin to a slug (default: `/`). **Overwrites** an existing page — refused without `--force
|
|
33
|
+
| `myapi funnel push [slug]` | Push HTML from stdin to a slug (default: `/`). **Overwrites** an existing page — refused without `--force`. `--json` prints `{slug, subdomain_url, overwritten, org_id, funnel_id}` |
|
|
34
|
+
| `myapi funnel publish <dir>` | Upload a whole directory as the funnel's site (`--env dev\|prod`, default prod; `--api-fn <id>`; `--json`). Prod refuses to replace a live site without `--force` |
|
|
34
35
|
| `myapi funnel pages [funnel_id]` | List the pages currently published to a funnel |
|
|
35
36
|
| `myapi funnel form [funnel_id]` | Emit canonical form HTML (and register a binding with `--capture-to`) |
|
|
36
37
|
| `myapi funnel verify [slug]` | Verify a published page is reachable + check links/webhooks |
|
|
@@ -45,7 +46,7 @@ A write targets a `(org, funnel, slug)` address. Get all three right *before* pu
|
|
|
45
46
|
3. **A new demo = a new funnel.** Don't reuse an org's existing funnel for an unrelated demo. `myapi funnel create --name <demo> --org <id>` gives you a clean namespace and its own preview subdomain.
|
|
46
47
|
4. **Pin the funnel.** When an org has exactly one funnel, `push`/`publish` auto-pick it — convenient, but it's how a demo lands on the wrong site. Pass `--funnel <id>` (or set a default) so the target is explicit, not inferred.
|
|
47
48
|
|
|
48
|
-
The CLI backstops you: `push` refuses to overwrite an existing slug (and `publish --env prod` refuses to replace a live site) without `--force`, and both print the resolved **org + funnel** on success.
|
|
49
|
+
The CLI backstops you: `push` refuses to overwrite an existing slug (and `publish --env prod` refuses to replace a live site) without `--force`, and both print the resolved **org + funnel** on success.
|
|
49
50
|
|
|
50
51
|
## Examples
|
|
51
52
|
<!-- llm:start -->
|
|
@@ -71,7 +72,7 @@ cat new-home.html | myapi funnel push / --funnel <funnel_id> --force
|
|
|
71
72
|
myapi funnel delete <funnel_id> --org <org_id>
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Omitting `[slug]` defaults to `/`. The funnel id is resolved from `--funnel`, the saved default funnel, or — only if the org has exactly one funnel — auto-picked
|
|
75
|
+
Omitting `[slug]` defaults to `/`. The funnel id is resolved from `--funnel`, the saved default funnel, or — only if the org has exactly one funnel — auto-picked; pass `--funnel` to be sure. `push --json` returns the live URL as `subdomain_url` — no follow-up `funnel get` needed.
|
|
75
76
|
<!-- llm:end -->
|
|
76
77
|
|
|
77
78
|
## Form submissions (canonical recipe)
|
|
@@ -113,7 +114,7 @@ myapi funnel form <funnel_id> --slug survey \
|
|
|
113
114
|
## Notes
|
|
114
115
|
|
|
115
116
|
- Set defaults with `myapi config set-org <id>` and `myapi config set-funnel <id>` to skip flags on every command — but a stale default is exactly how a push lands on the wrong org/funnel. For demos and one-offs, pass `--org`/`--funnel` explicitly instead of relying on whatever default was set last.
|
|
116
|
-
-
|
|
117
|
+
- `--force` means "yes, replace the live page/site" — confirm the `(org, funnel, slug)` target first; don't use it to silence the overwrite error.
|
|
117
118
|
- Deleting a funnel purges all its edge pages immediately.
|
|
118
119
|
- `402 INSUFFICIENT_FUNDS` → top up (`myapi billing topup`) or enable `myapi billing auto-recharge`.
|
|
119
120
|
|
|
@@ -15,7 +15,7 @@ Per-org hosted git repositories, served by a go-git engine (objects in GCS packf
|
|
|
15
15
|
<!-- llm:start -->
|
|
16
16
|
Git is the source-control layer. Each repo lives under your org and is reachable two ways:
|
|
17
17
|
|
|
18
|
-
1. **CLI / SDK** — no local clone. `commit`
|
|
18
|
+
1. **CLI / SDK** — no local clone. `commit` writes atomically from three sources: `--dir <path>` (every file under a local directory — the preferred path for committing a generated project), `--file <path> [--as <repo-path>]` (one local file), or an explicit `--changes` JSON array. Reads (`log`, `show`, `tree`, `blob`, `diff`, `refs`) round-trip the API.
|
|
19
19
|
2. **Real git over HTTPS** — `git clone`/`fetch`/`push` against `https://git.mygitapi.com/<org-slug>/<repo>.git` with your MyAPI API key as the password. This is how a normal git workflow connects (see *Clone & push with real git*).
|
|
20
20
|
|
|
21
21
|
Both surfaces share one engine: a `git push` and a `myapi git commit` converge on the same refs.
|
|
@@ -32,14 +32,14 @@ Both surfaces share one engine: a `git push` and a `myapi git commit` converge o
|
|
|
32
32
|
| `myapi git create <name>` | Create a repository (`--default-branch <b>`) |
|
|
33
33
|
| `myapi git list` | List repositories |
|
|
34
34
|
| `myapi git get <repo>` | Show branch/tag counts + default branch |
|
|
35
|
-
| `myapi git delete <repo>` | Delete a repository |
|
|
35
|
+
| `myapi git delete <repo>` | Delete a repository. Look before you delete: `myapi git list` first, pass `--org` explicitly; asks for confirmation — `--yes` required in non-interactive runs |
|
|
36
36
|
| `myapi git refs <repo>` | List HEAD, branches, and tags |
|
|
37
37
|
| `myapi git log <repo>` | List commits (`--ref`, `--limit`) |
|
|
38
38
|
| `myapi git show <repo> <sha>` | Show a single commit |
|
|
39
39
|
| `myapi git tree <repo> <ref>` | List a ref's file tree (`--path`) |
|
|
40
40
|
| `myapi git blob <repo> <ref> <path>` | Print a file's content (decoded) |
|
|
41
41
|
| `myapi git diff <repo>` | Unified diff of two refs (`--base`, `--head`) |
|
|
42
|
-
| `myapi git commit <repo>` | Atomic commit (`--branch`, `--message`, `--changes
|
|
42
|
+
| `myapi git commit <repo>` | Atomic commit (`--branch`, `--message`, plus `--dir <path>` \| `--file <path> [--as <repo-path>]` \| `--changes <json>`) |
|
|
43
43
|
| `myapi git create-branch <repo> <name>` | Create a branch (`--from <ref>`) |
|
|
44
44
|
| `myapi git delete-branch <repo> <branch>` | Delete a branch |
|
|
45
45
|
| `myapi git tag <repo> <name>` | Create a tag (`--ref <ref>`) |
|
|
@@ -55,9 +55,16 @@ myapi git create my-app --default-branch main
|
|
|
55
55
|
myapi git list
|
|
56
56
|
myapi git refs my-app
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
myapi git commit my-app --branch main --message "init"
|
|
60
|
-
|
|
58
|
+
# Commit a generated project — point --dir at the directory (preferred)
|
|
59
|
+
myapi git commit my-app --branch main --message "init" --dir ./dist
|
|
60
|
+
|
|
61
|
+
# Commit one local file (--as places/renames it in the repo)
|
|
62
|
+
myapi git commit my-app --branch main --message "add page" \
|
|
63
|
+
--file ./out.html --as index.html
|
|
64
|
+
|
|
65
|
+
# Or hand-author edits as JSON (deletes, modes, base64 content)
|
|
66
|
+
myapi git commit my-app --branch main --message "rm" \
|
|
67
|
+
--changes '[{"path":"old.txt","delete":true}]'
|
|
61
68
|
# Each entry: {path, content | content_base64 | delete:true, mode?}
|
|
62
69
|
# mode: "100644" (default), "100755" (exec), "120000" (symlink)
|
|
63
70
|
|
|
@@ -105,7 +112,7 @@ surface never leaks which repos exist).
|
|
|
105
112
|
|
|
106
113
|
## Notes
|
|
107
114
|
|
|
108
|
-
- **Clone-free is the default mode.** The CLI never needs a working copy — `commit` sends file edits
|
|
115
|
+
- **Clone-free is the default mode.** The CLI never needs a working copy — `commit` sends local files (`--dir`/`--file`) or JSON edits (`--changes`), reads come back over the API. Use real git only when you want a local checkout.
|
|
109
116
|
- **`merge` is fast-forward only.** No merge commits; the target must be an ancestor of the source. Rebase/merge locally and push instead for non-FF cases.
|
|
110
117
|
- **`commit --base`** is the concurrency guard: pass the expected tip SHA to reject a stale write, or `--base ""` to require the branch be newly created. Omit it to create-or-update.
|
|
111
118
|
- **Metering.** A `git push` (and `myapi git commit`) is metered like a commit. Clone/fetch (`upload-pack`) is free.
|
|
@@ -73,6 +73,6 @@ Tips for agents:
|
|
|
73
73
|
|
|
74
74
|
- Cost is per-image at the selected model's catalog rate (`myapi image models`). Failed jobs aren't charged.
|
|
75
75
|
- The asset URL is public — don't generate sensitive content.
|
|
76
|
-
- `delete` is permanent for the asset; the job record (prompt + metadata) stays for history.
|
|
76
|
+
- `delete` is permanent for the asset; the job record (prompt + metadata) stays for history. Look before you delete: `myapi image list` first, pass `--org` explicitly; delete verbs require `--yes` in non-interactive runs.
|
|
77
77
|
|
|
78
78
|
Run `myapi image --help` or `myapi image <subcommand> --help` for full flag reference.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-llm-api
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.2.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
|
|
@@ -14,7 +14,7 @@ checksum: sha256-pending
|
|
|
14
14
|
|
|
15
15
|
A two-surface LLM gateway:
|
|
16
16
|
|
|
17
|
-
- **Raw** — `complete` / `embed` / `models`. You pick a self-hosted catalog model (
|
|
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
18
|
- **Verbs** — `classify` / `extract` / `summarize` / `draft`. You ask for a task done; the model is implementation detail and is never named in the response.
|
|
19
19
|
|
|
20
20
|
Pricing is in cents per 1M tokens at the actual upstream rate; cost is debited from your MyAPI balance.
|
|
@@ -33,7 +33,7 @@ Use this for workflow tasks — summarize a doc, classify an inbound email, extr
|
|
|
33
33
|
### Raw `complete` request
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
|
-
"model": "
|
|
36
|
+
"model": "<model-id>",
|
|
37
37
|
"messages": [
|
|
38
38
|
{ "role": "system", "content": "You are a terse editor." },
|
|
39
39
|
{ "role": "user", "content": "Tighten this paragraph: ..." }
|
|
@@ -49,7 +49,7 @@ Roles: `system | user | assistant`. Multiple system messages collapse to one ins
|
|
|
49
49
|
### Raw `complete` response
|
|
50
50
|
```json
|
|
51
51
|
{
|
|
52
|
-
"model": "
|
|
52
|
+
"model": "<model-id>",
|
|
53
53
|
"content": "...assistant reply...",
|
|
54
54
|
"finish_reason": "stop",
|
|
55
55
|
"usage": { "input_tokens": 42, "output_tokens": 87, "cost_cents": 0.029 }
|
|
@@ -62,7 +62,7 @@ If `model` isn't in the self-hosted catalog the server returns `MODEL_NOT_IN_RAW
|
|
|
62
62
|
|
|
63
63
|
### Raw `embed`
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
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.
|
|
66
66
|
|
|
67
67
|
### Model catalog
|
|
68
68
|
- Chat models: `id`, `kind: 'chat'`, `context_window`, `input_cost_per_1m_cents`, `output_cost_per_1m_cents`
|
|
@@ -99,11 +99,11 @@ client = OpenAI(
|
|
|
99
99
|
api_key="hq_live_…",
|
|
100
100
|
base_url="https://api.myapihq.com/llm/orgs/<org_id>/v1",
|
|
101
101
|
)
|
|
102
|
-
r = client.chat.completions.create(model="
|
|
102
|
+
r = client.chat.completions.create(model="<model-id>",
|
|
103
103
|
messages=[{"role":"user","content":"Hi"}])
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Use raw `complete`
|
|
106
|
+
Use raw `complete` for first-party code; the compat path for existing OpenAI/LangChain tooling.
|
|
107
107
|
|
|
108
108
|
<!-- llm:end -->
|
|
109
109
|
|
|
@@ -113,7 +113,7 @@ Use raw `complete` (MyAPI shape) for first-party integrations; use the OpenAI-co
|
|
|
113
113
|
|---|---|
|
|
114
114
|
| `myapi llm models [--kind chat\|embed] [--json]` | List the live model catalog with pricing (cents/1M) |
|
|
115
115
|
| `myapi llm complete "<prompt>" [--model <id>] [--system "<s>"] [--max-tokens N] [--temperature 0..1] [--stop <csv>] [--file <path>] [--json]` | Raw chat completion; reply to stdout, usage to stderr. Defaults to the first chat model in the catalog |
|
|
116
|
-
| `myapi llm embed "<text>" --model <id> [--json]` | Embed a string
|
|
116
|
+
| `myapi llm embed "<text>" [--model <id>] [--json]` | Embed a string into a vector; `--model` optional when the catalog has one embed model |
|
|
117
117
|
| `myapi llm classify "<input>" --labels <csv> [--multi] [--tier <t>] [--json]` | Pick a label from a set |
|
|
118
118
|
| `myapi llm extract "<input>" --schema <path\|json> [--tier <t>] [--json]` | Pull structured data conforming to a JSON Schema |
|
|
119
119
|
| `myapi llm summarize "<input>" [--style brief\|exec\|bullet] [--tier <t>] [--json]` | Summarize text |
|
|
@@ -132,9 +132,9 @@ myapi llm models --kind chat --json | jq '.models[].id'
|
|
|
132
132
|
# Raw completion — picks the first chat model from the catalog
|
|
133
133
|
myapi llm complete "Summarize in 12 words: $(cat README.md)"
|
|
134
134
|
|
|
135
|
-
# Pin a specific model
|
|
135
|
+
# Pin a specific model (ids come from `myapi llm models`)
|
|
136
136
|
myapi llm complete "Refactor this function: ..." \
|
|
137
|
-
--model
|
|
137
|
+
--model <model-id> \
|
|
138
138
|
--system "You are a careful Go reviewer." \
|
|
139
139
|
--max-tokens 600
|
|
140
140
|
|
|
@@ -163,5 +163,5 @@ INTENT=$(printf '%s' "$BODY" | myapi llm classify - \
|
|
|
163
163
|
|
|
164
164
|
- **`draft` context safety.** `context` fields are quoted into the prompt verbatim; sensitive-named keys (`secret`, `api_key`, `password`, …) are NOT redacted. Two guards on top: (a) injection-defense strips `instructions`/`system`/`prompt`/`override` keys and surfaces them in `meta.warnings`; (b) output guardrail substring-scans fact values (length ≥ 4) in the response and lists matches in `meta.guardrails.facts_in_output` (signal, not redaction). Rule of thumb: never put credentials, PII, or internal metadata in `context` — pass identifiers, reference them indirectly.
|
|
165
165
|
- **Self-hosted raw, server-picked verbs.** Raw runs on MyAPI's TPU; verbs route wherever the server picks.
|
|
166
|
-
- **Cost + latency.** `usage.cost_cents` is authoritative — no markup.
|
|
166
|
+
- **Cost + latency.** `usage.cost_cents` is authoritative — no markup. Varies by tier: 200–600 ms to first token, 1–3 s end-to-end.
|
|
167
167
|
- **Live catalog, no streaming, no BYOK.** Don't hard-code ids — `models` is truth (CLI auto-picks if `--model` omitted). Full reply only.
|