@myapihq/cli 2.4.2 → 2.5.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/authproduct.js +1 -1
- package/dist/commands/billing.js +8 -2
- package/dist/commands/config.js +16 -0
- package/dist/commands/container.d.ts +2 -1
- package/dist/commands/container.js +30 -5
- package/dist/commands/fn.d.ts +1 -1
- package/dist/commands/fn.js +51 -9
- package/dist/commands/keys.js +5 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +62 -2
- package/dist/commands/payments.js +6 -2
- package/dist/commands/pixel.d.ts +2 -1
- package/dist/commands/pixel.js +28 -1
- package/dist/commands/queue.d.ts +1 -1
- package/dist/commands/queue.js +15 -2
- package/dist/commands/setup.js +6 -2
- package/dist/commands/storage.js +13 -2
- package/dist/commands/webhook.d.ts +1 -0
- package/dist/commands/webhook.js +74 -2
- package/dist/errors.js +6 -0
- package/dist/flags.d.ts +1 -1
- package/dist/flags.js +21 -0
- package/dist/flags.test.js +35 -0
- package/dist/index.js +60 -0
- package/dist/output.js +8 -0
- package/dist/sdk-container.test.js +5 -1
- package/dist/skills/my-audience-api/README.md +1 -1
- package/dist/skills/my-auth-api/SKILL.md +3 -1
- package/dist/skills/my-company-api/README.md +1 -1
- package/dist/skills/my-container-api/README.md +1 -1
- package/dist/skills/my-container-api/SKILL.md +3 -1
- package/dist/skills/my-crm-api/README.md +1 -1
- package/dist/skills/my-crm-api/SKILL.md +6 -4
- package/dist/skills/my-database-api/README.md +1 -1
- package/dist/skills/my-database-api/SKILL.md +3 -1
- package/dist/skills/my-email-api/README.md +1 -1
- package/dist/skills/my-email-verify-api/README.md +1 -1
- package/dist/skills/my-git-api/README.md +1 -1
- package/dist/skills/my-image-api/README.md +1 -1
- package/dist/skills/my-llm-api/README.md +1 -1
- package/dist/skills/my-people-api/README.md +1 -1
- package/dist/skills/my-pixel-api/README.md +1 -1
- package/dist/skills/my-queue-api/README.md +1 -1
- package/dist/skills/my-storage-api/README.md +1 -1
- package/dist/skills/my-task-api/README.md +1 -1
- package/dist/skills/my-webhook-api/README.md +1 -1
- package/dist/skills/my-webhook-api/SKILL.md +7 -3
- package/dist/skills/my-workflow-api/README.md +1 -1
- package/package.json +3 -2
package/dist/commands/webhook.js
CHANGED
|
@@ -2,12 +2,14 @@ import { webhook as sdkWebhook } from '@myapihq/sdk';
|
|
|
2
2
|
import { requireConfig } from '../config.js';
|
|
3
3
|
import { success, error, printTable, info, printJson } from '../output.js';
|
|
4
4
|
import { requireOrg } from '../helpers.js';
|
|
5
|
+
import { formatDate } from '../utils.js';
|
|
5
6
|
export const EXPOSES = [
|
|
6
7
|
'POST /webhook/orgs/{org_id}/endpoints',
|
|
7
8
|
'GET /webhook/orgs/{org_id}/endpoints',
|
|
8
9
|
'PATCH /webhook/orgs/{org_id}/endpoints/{endpoint_id}',
|
|
9
10
|
'DELETE /webhook/orgs/{org_id}/endpoints/{endpoint_id}',
|
|
10
11
|
'GET /webhook/orgs/{org_id}/deliveries/{delivery_id}',
|
|
12
|
+
'GET /webhook/orgs/{org_id}/deliveries',
|
|
11
13
|
];
|
|
12
14
|
export const SCHEMA = {
|
|
13
15
|
org: 'string',
|
|
@@ -16,6 +18,9 @@ export const SCHEMA = {
|
|
|
16
18
|
'crm-email-path': 'string',
|
|
17
19
|
// 2026-05-15: backend's POST/PATCH endpoints accept forward_url.
|
|
18
20
|
'forward-url': 'string',
|
|
21
|
+
'endpoint-id': 'string',
|
|
22
|
+
limit: 'number',
|
|
23
|
+
cursor: 'string',
|
|
19
24
|
};
|
|
20
25
|
export async function list(flags) {
|
|
21
26
|
const config = requireConfig();
|
|
@@ -130,6 +135,62 @@ export async function delivery(id, flags) {
|
|
|
130
135
|
const res = await sdkWebhook.getDelivery(config.api_key, orgId, id);
|
|
131
136
|
printJson(res);
|
|
132
137
|
}
|
|
138
|
+
// deliveries — what an endpoint actually received.
|
|
139
|
+
//
|
|
140
|
+
// Before this existed, `delivery <id>` was the only read and the id is only
|
|
141
|
+
// ever handed to whoever POSTed. For a funnel form that is the visitor's
|
|
142
|
+
// browser, so the agent that built the form could not see its own
|
|
143
|
+
// submissions; CRM ingest (email only) was the sole alternative and every
|
|
144
|
+
// other field was unreachable. This is that gap closed.
|
|
145
|
+
export async function deliveries(endpointArg, flags) {
|
|
146
|
+
const config = requireConfig();
|
|
147
|
+
const orgId = requireOrg(flags, config, 'myapi webhook deliveries [endpoint_id] [--limit N] [--cursor <c>] [--org <id>]');
|
|
148
|
+
const endpointId = endpointArg ?? flags['endpoint-id'];
|
|
149
|
+
const page = await sdkWebhook.listDeliveries(config.api_key, orgId, {
|
|
150
|
+
endpointId,
|
|
151
|
+
limit: typeof flags.limit === 'number' ? flags.limit : undefined,
|
|
152
|
+
cursor: typeof flags.cursor === 'string' ? flags.cursor : undefined,
|
|
153
|
+
});
|
|
154
|
+
if (flags.json) {
|
|
155
|
+
printJson(page);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (page.deliveries.length === 0) {
|
|
159
|
+
info(endpointId
|
|
160
|
+
? `No deliveries yet for endpoint ${endpointId}.`
|
|
161
|
+
: 'No deliveries yet in this org. POST to an endpoint\'s inbound URL to create one.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
printTable(page.deliveries.map(d => ({
|
|
165
|
+
id: d.id,
|
|
166
|
+
endpoint_id: d.endpoint_id,
|
|
167
|
+
received_at: formatDate(d.received_at),
|
|
168
|
+
forward: d.forward_status !== undefined ? String(d.forward_status) : '',
|
|
169
|
+
payload: summarizePayload(d.payload),
|
|
170
|
+
})), { flags });
|
|
171
|
+
// The full payload is the point of this command, so say how to get it
|
|
172
|
+
// rather than leaving the summary column as the only view.
|
|
173
|
+
info('');
|
|
174
|
+
info('Full payload for one delivery: myapi webhook delivery <id>');
|
|
175
|
+
if (page.next_cursor) {
|
|
176
|
+
info(`More available — next page: myapi webhook deliveries --cursor ${page.next_cursor}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// One-line preview of an arbitrary JSON payload. The table is for scanning;
|
|
180
|
+
// `webhook delivery <id>` is for reading.
|
|
181
|
+
function summarizePayload(payload) {
|
|
182
|
+
if (payload === null || payload === undefined)
|
|
183
|
+
return '';
|
|
184
|
+
if (typeof payload !== 'object')
|
|
185
|
+
return String(payload).slice(0, 60);
|
|
186
|
+
const entries = Object.entries(payload);
|
|
187
|
+
if (entries.length === 0)
|
|
188
|
+
return '{}';
|
|
189
|
+
const shown = entries.slice(0, 3)
|
|
190
|
+
.map(([k, v]) => `${k}=${typeof v === 'object' ? '…' : String(v).slice(0, 20)}`)
|
|
191
|
+
.join(' ');
|
|
192
|
+
return entries.length > 3 ? `${shown} +${entries.length - 3}` : shown;
|
|
193
|
+
}
|
|
133
194
|
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
|
134
195
|
const SUBCOMMAND_USAGE = {
|
|
135
196
|
'list': 'myapi webhook list [--org <id>] [--json]',
|
|
@@ -153,6 +214,11 @@ endpoint).
|
|
|
153
214
|
Slug is immutable; create a new endpoint if you need a different slug.`,
|
|
154
215
|
'delete': 'myapi webhook delete <id> [--org <id>]',
|
|
155
216
|
'delivery': 'myapi webhook delivery <delivery_id> [--org <id>]',
|
|
217
|
+
'deliveries': `myapi webhook deliveries [endpoint_id] [--limit N] [--cursor <c>] [--org <id>] [--json]
|
|
218
|
+
|
|
219
|
+
Lists what your endpoints actually received, newest first. Use this rather
|
|
220
|
+
than trying to capture delivery ids at POST time — for a funnel form the id
|
|
221
|
+
goes to the visitor's browser, not to you.`,
|
|
156
222
|
};
|
|
157
223
|
export async function run(subcommand, args, flags) {
|
|
158
224
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
@@ -161,6 +227,7 @@ export async function run(subcommand, args, flags) {
|
|
|
161
227
|
Subcommands:
|
|
162
228
|
create Create a new endpoint to receive data (returns an inbound URL)
|
|
163
229
|
delete Delete a webhook endpoint
|
|
230
|
+
deliveries List what your endpoints received (newest first, paginated)
|
|
164
231
|
delivery Inspect a specific webhook delivery (payload, received_at)
|
|
165
232
|
list List all inbound webhook endpoints
|
|
166
233
|
update Patch endpoint name / description / crm-email-path / forward-url
|
|
@@ -173,8 +240,12 @@ Payload contract:
|
|
|
173
240
|
Workflows reference fields with {{ payload.field }} templating.
|
|
174
241
|
|
|
175
242
|
Discovering delivery IDs:
|
|
176
|
-
|
|
177
|
-
|
|
243
|
+
myapi webhook deliveries — list what came in, newest first
|
|
244
|
+
myapi webhook delivery <id> — full payload for one
|
|
245
|
+
|
|
246
|
+
A POST to an inbound URL does return {"delivery_id":"<uuid>"}, but for a
|
|
247
|
+
funnel form that response goes to the visitor's browser, not to you — so
|
|
248
|
+
list them rather than trying to capture ids at submit time.
|
|
178
249
|
|
|
179
250
|
Example — wire a contact form to email + Slack:
|
|
180
251
|
WID=$(myapi webhook create "contact-form" --json | jq -r .id)
|
|
@@ -199,6 +270,7 @@ See the my-webhook-api skill for the full HTML + JS recipe.`);
|
|
|
199
270
|
case 'update': return update(args[0], flags);
|
|
200
271
|
case 'delete': return del(args[0], flags);
|
|
201
272
|
case 'delivery': return delivery(args[0], flags);
|
|
273
|
+
case 'deliveries': return deliveries(args[0], flags);
|
|
202
274
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi webhook --help" for a list of valid subcommands.`);
|
|
203
275
|
}
|
|
204
276
|
}
|
package/dist/errors.js
CHANGED
|
@@ -28,6 +28,12 @@ export const ERROR_MESSAGES = {
|
|
|
28
28
|
INSUFFICIENT_BALANCE: 'Insufficient balance. Run: myapi billing topup <amount>',
|
|
29
29
|
INVALID_AMOUNT: 'Amount out of range. Maximum single top-up is $100. Run: myapi billing topup <amount>',
|
|
30
30
|
SERVICE_NOT_LAUNCHED: 'This service is disabled pre-launch. Track availability via: myapi status',
|
|
31
|
+
// Every other blocker in the CLI names the command that unblocks it
|
|
32
|
+
// (DOMAIN_NOT_OWNED → `domain register`, MAILBOX_NOT_OWNED → …). This one
|
|
33
|
+
// stated the fact and stopped, so an agent had to go read `database --help`
|
|
34
|
+
// to discover namespaces are created explicitly. Observed during the
|
|
35
|
+
// 2026-07-27 from-scratch run.
|
|
36
|
+
NAMESPACE_NOT_FOUND: 'Namespace not found. Namespaces are created explicitly — run: myapi database create <name> (list them with: myapi database namespaces)',
|
|
31
37
|
RECORD_NOT_FOUND: 'DNS record not found in this zone.',
|
|
32
38
|
INVALID_RECORD_TYPE: 'Unsupported record type. Allowed: A, AAAA, CNAME, MX, TXT.',
|
|
33
39
|
MX_PRIORITY_REQUIRED: 'MX records require --priority (typical value: 10).',
|
package/dist/flags.d.ts
CHANGED
package/dist/flags.js
CHANGED
|
@@ -17,6 +17,7 @@ export function parseFlags(argv, schema = {}) {
|
|
|
17
17
|
const args = [];
|
|
18
18
|
const flags = {};
|
|
19
19
|
const unknownFlags = [];
|
|
20
|
+
const seenValueFlags = new Set();
|
|
20
21
|
for (let i = 0; i < argv.length; i++) {
|
|
21
22
|
const arg = argv[i];
|
|
22
23
|
if (arg === '-h') {
|
|
@@ -91,6 +92,26 @@ export function parseFlags(argv, schema = {}) {
|
|
|
91
92
|
flags[key] = true;
|
|
92
93
|
continue;
|
|
93
94
|
}
|
|
95
|
+
// Repeatable flag: accumulate rather than overwrite.
|
|
96
|
+
if (type === 'list') {
|
|
97
|
+
const prev = flags[key];
|
|
98
|
+
flags[key] = typeof prev === 'string' && prev.length > 0 ? `${prev},${raw}` : raw;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// A value flag given twice used to keep the LAST occurrence, silently.
|
|
102
|
+
// Reported from the field (Skout, 2026-07-27): three `--redirect` flags
|
|
103
|
+
// on `auth client create` registered one URI, reported success, and the
|
|
104
|
+
// production redirect was the one dropped — sign-in would have worked in
|
|
105
|
+
// dev and failed only for real users. Repeating a flag is a natural
|
|
106
|
+
// mistake when the docs say "comma-separated"; silently honouring the
|
|
107
|
+
// last is the one behaviour that can't be recovered from. Refuse, and
|
|
108
|
+
// say what to do instead.
|
|
109
|
+
if (seenValueFlags.has(key)) {
|
|
110
|
+
throw new Error(`--${key} was given more than once. Only the last value would be used, ` +
|
|
111
|
+
`which silently discards the others.\n` +
|
|
112
|
+
`→ If you meant to pass several values, comma-separate them: --${key} a,b,c`);
|
|
113
|
+
}
|
|
114
|
+
seenValueFlags.add(key);
|
|
94
115
|
if (type === 'number') {
|
|
95
116
|
const n = Number(raw);
|
|
96
117
|
flags[key] = isNaN(n) ? raw : n;
|
package/dist/flags.test.js
CHANGED
|
@@ -155,3 +155,38 @@ describe('parseFlags', () => {
|
|
|
155
155
|
expect(flags['no-enable']).toBe(true);
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
|
+
// Repeated flags. Reported from the field (Skout, 2026-07-27): three
|
|
159
|
+
// `--redirect` flags on `auth client create` registered ONE URI and reported
|
|
160
|
+
// success. The dropped one was production, so sign-in worked in dev and would
|
|
161
|
+
// have failed only for real users. The reporter caught it solely by reading
|
|
162
|
+
// the confirmation line.
|
|
163
|
+
describe('parseFlags — repeated flags are never silently collapsed', () => {
|
|
164
|
+
it('accumulates a repeatable (list) flag instead of keeping the last', () => {
|
|
165
|
+
const { flags } = parseFlags(['--redirect', 'https://prod/cb', '--redirect', 'http://localhost:5173/cb'], { redirect: 'list' });
|
|
166
|
+
expect(flags.redirect).toBe('https://prod/cb,http://localhost:5173/cb');
|
|
167
|
+
});
|
|
168
|
+
it('still accepts the documented comma-separated form for a list flag', () => {
|
|
169
|
+
const { flags } = parseFlags(['--redirect', 'a,b,c'], { redirect: 'list' });
|
|
170
|
+
expect(flags.redirect).toBe('a,b,c');
|
|
171
|
+
});
|
|
172
|
+
it('mixes repeated and comma forms', () => {
|
|
173
|
+
const { flags } = parseFlags(['--redirect', 'a,b', '--redirect', 'c'], { redirect: 'list' });
|
|
174
|
+
expect(flags.redirect).toBe('a,b,c');
|
|
175
|
+
});
|
|
176
|
+
it('REFUSES a non-repeatable value flag given twice rather than keeping the last', () => {
|
|
177
|
+
expect(() => parseFlags(['--name', 'prod', '--name', 'dev'], { name: 'string' }))
|
|
178
|
+
.toThrow(/given more than once/);
|
|
179
|
+
});
|
|
180
|
+
it('names the comma-separated escape hatch in the refusal', () => {
|
|
181
|
+
expect(() => parseFlags(['--name', 'a', '--name', 'b'], { name: 'string' }))
|
|
182
|
+
.toThrow(/comma-separate them/);
|
|
183
|
+
});
|
|
184
|
+
it('does not refuse repeated boolean flags — harmless and idempotent', () => {
|
|
185
|
+
const { flags } = parseFlags(['--yes', '--yes'], {});
|
|
186
|
+
expect(flags.yes).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
it('a single occurrence of a value flag is unaffected', () => {
|
|
189
|
+
const { flags } = parseFlags(['--name', 'prod'], { name: 'string' });
|
|
190
|
+
expect(flags.name).toBe('prod');
|
|
191
|
+
});
|
|
192
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,65 @@ import * as loginCmd from './commands/login.js';
|
|
|
47
47
|
// Each command file declares the value flags it understands. We union them
|
|
48
48
|
// into a single schema for the upfront parse, so adding a new value flag in
|
|
49
49
|
// one command means editing one file (its SCHEMA), not a global allowlist.
|
|
50
|
+
// Which command owns which flag schema. parseFlags necessarily runs against
|
|
51
|
+
// the UNION of every schema (it has to know whether `--foo` eats the next
|
|
52
|
+
// token before it knows the command), and its unknown-flag warning therefore
|
|
53
|
+
// only fires for a flag no command declares. The consequence, found by
|
|
54
|
+
// building a company from scratch on 2026-07-27: `myapi task create "x"
|
|
55
|
+
// --priority 9` was accepted in silence and the value discarded, because
|
|
56
|
+
// `priority` is a real flag — on `domain`, for MX records. The agent gets a
|
|
57
|
+
// success and a task with default importance.
|
|
58
|
+
//
|
|
59
|
+
// This map lets us re-check the flags against the schema of the command that
|
|
60
|
+
// actually ran, so a flag borrowed from a sibling is called out the same way a
|
|
61
|
+
// typo already was. Commands absent from the map (setup, update) are skipped
|
|
62
|
+
// rather than guessed at.
|
|
63
|
+
const COMMAND_SCHEMAS = {
|
|
64
|
+
account: accountCmd.SCHEMA,
|
|
65
|
+
audience: audienceCmd.SCHEMA,
|
|
66
|
+
auth: authProductCmd.SCHEMA,
|
|
67
|
+
billing: billingCmd.SCHEMA,
|
|
68
|
+
company: companyCmd.SCHEMA,
|
|
69
|
+
config: configCmd.SCHEMA,
|
|
70
|
+
container: containerCmd.SCHEMA,
|
|
71
|
+
crm: crmCmd.SCHEMA,
|
|
72
|
+
database: databaseCmd.SCHEMA,
|
|
73
|
+
doctor: doctorCmd.SCHEMA,
|
|
74
|
+
domain: domainCmd.SCHEMA,
|
|
75
|
+
email: emailCmd.SCHEMA,
|
|
76
|
+
fn: fnCmd.SCHEMA,
|
|
77
|
+
funnel: funnelCmd.SCHEMA,
|
|
78
|
+
git: gitCmd.SCHEMA,
|
|
79
|
+
image: imageCmd.SCHEMA,
|
|
80
|
+
keys: keysCmd.SCHEMA,
|
|
81
|
+
llm: llmCmd.SCHEMA,
|
|
82
|
+
login: loginCmd.SCHEMA,
|
|
83
|
+
org: orgCmd.SCHEMA,
|
|
84
|
+
payments: paymentsCmd.SCHEMA,
|
|
85
|
+
people: peopleCmd.SCHEMA,
|
|
86
|
+
pixel: pixelCmd.SCHEMA,
|
|
87
|
+
queue: queueCmd.SCHEMA,
|
|
88
|
+
status: statusCmd.SCHEMA,
|
|
89
|
+
storage: storageCmd.SCHEMA,
|
|
90
|
+
task: taskCmd.SCHEMA,
|
|
91
|
+
url: urlCmd.SCHEMA,
|
|
92
|
+
webhook: webhookCmd.SCHEMA,
|
|
93
|
+
workflow: workflowCmd.SCHEMA,
|
|
94
|
+
};
|
|
95
|
+
// Flags the dispatcher itself consumes, valid on any command.
|
|
96
|
+
const DISPATCH_FLAGS = new Set(['help', 'h', 'json', 'verbose', 'yes', 'y', 'version', 'v', 'V', 'org']);
|
|
97
|
+
function warnForeignFlags(command, flags) {
|
|
98
|
+
if (!command || process.env.MYAPI_QUIET_UNKNOWN_FLAGS)
|
|
99
|
+
return;
|
|
100
|
+
const schema = COMMAND_SCHEMAS[command];
|
|
101
|
+
if (!schema)
|
|
102
|
+
return; // command has no declared schema — nothing to compare against
|
|
103
|
+
const foreign = Object.keys(flags).filter(k => !DISPATCH_FLAGS.has(k) && !(k in schema));
|
|
104
|
+
if (foreign.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
banner(`› Note: ${foreign.map(f => `--${f}`).join(', ')} ${foreign.length === 1 ? 'is' : 'are'} not a flag of \`myapi ${command}\` — ` +
|
|
107
|
+
`the value was ignored. Run \`myapi ${command} --help\`.`);
|
|
108
|
+
}
|
|
50
109
|
const COMBINED_SCHEMA = {
|
|
51
110
|
...accountCmd.SCHEMA,
|
|
52
111
|
...authProductCmd.SCHEMA,
|
|
@@ -92,6 +151,7 @@ async function main() {
|
|
|
92
151
|
return;
|
|
93
152
|
}
|
|
94
153
|
const { args, flags } = parseFlags(process.argv.slice(2), COMBINED_SCHEMA);
|
|
154
|
+
warnForeignFlags(args[0], flags);
|
|
95
155
|
if (flags.version || flags.v || flags.V) {
|
|
96
156
|
// Read the last-known published version from cache — no network call, so
|
|
97
157
|
// `myapi --version` stays instant. The cache is refreshed by the
|
package/dist/output.js
CHANGED
|
@@ -26,6 +26,14 @@ export function spinnerFrame(i) {
|
|
|
26
26
|
return SPINNER_FRAMES[i % SPINNER_FRAMES.length];
|
|
27
27
|
}
|
|
28
28
|
export function spinnerWrite(s) {
|
|
29
|
+
// Spinner frames rely on \r overwriting the line, which only happens on a
|
|
30
|
+
// terminal. Piped or captured — which is how every agent runs this — each
|
|
31
|
+
// frame just accumulates, so a single image generation produced
|
|
32
|
+
// "Generating image ⠋Generating image ⠙Generating image ⠹…" in the output an
|
|
33
|
+
// agent then has to read. Progress animation is for humans; suppress it
|
|
34
|
+
// when nobody is watching.
|
|
35
|
+
if (!process.stderr.isTTY)
|
|
36
|
+
return;
|
|
29
37
|
process.stderr.write(s);
|
|
30
38
|
}
|
|
31
39
|
export function clearLine() {
|
|
@@ -126,7 +126,7 @@ describe('container.getContainerLogs', () => {
|
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
describe('container.EXPOSES', () => {
|
|
129
|
-
it('covers the
|
|
129
|
+
it('covers the 9 container endpoints', () => {
|
|
130
130
|
expect(container.EXPOSES).toEqual([
|
|
131
131
|
'POST /container/orgs/{org_id}/containers',
|
|
132
132
|
'GET /container/orgs/{org_id}/containers',
|
|
@@ -136,6 +136,10 @@ describe('container.EXPOSES', () => {
|
|
|
136
136
|
'GET /container/orgs/{org_id}/containers/{id}/logs',
|
|
137
137
|
'POST /container/orgs/{org_id}/containers/{id}/domain',
|
|
138
138
|
'DELETE /container/orgs/{org_id}/containers/{id}/domain',
|
|
139
|
+
// Added 2026-07-27: build-logs answers WHY a --source build failed,
|
|
140
|
+
// separately from the runtime `logs` stream (which interleaves platform
|
|
141
|
+
// audit records with application output).
|
|
142
|
+
'GET /container/orgs/{org_id}/containers/{id}/build-logs',
|
|
139
143
|
]);
|
|
140
144
|
});
|
|
141
145
|
});
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
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-b5eca82bb59647677fdb5796778329160572598ac6914887768fd33529cdfe84
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyAuthAPI
|
|
@@ -113,3 +113,5 @@ myapi auth client list
|
|
|
113
113
|
- Redirect URIs are matched exactly: absolute `https://…` (or
|
|
114
114
|
`http://localhost…` for local dev).
|
|
115
115
|
- `402 INSUFFICIENT_FUNDS` = empty wallet → `myapi billing topup <amount>` (or keep it funded automatically: `myapi billing auto-recharge set`). `402 SPEND_CAP_EXCEEDED` = you hit your account spend ceiling → raise it with `myapi billing spend-cap`.
|
|
116
|
+
|
|
117
|
+
**End-to-end example:** `examples/authenticated-app/` walks the full seam — hosted login → token verification → per-user KV record → deployed container — including the parts that cost real users hours (verify the id_token for identity; the access token is a bearer credential for `<issuer>/userinfo`).
|
|
@@ -22,7 +22,7 @@ myapi container domain <id> app.yourbrand.com
|
|
|
22
22
|
## Authentication
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
export MYAPI_KEY=
|
|
25
|
+
export MYAPI_KEY=hq_live_...
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Requires `api_key` + `org_id` from **myapihq**. Custom domains require the parent domain registered via **mydomainapi**.
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
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-0bd2fdee306f944702faf18b0cbb5d976a2bdc39c1f940c037600b0c0f6a4691
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyContainerAPI
|
|
@@ -75,3 +75,5 @@ myapi container domain <id> --remove
|
|
|
75
75
|
- Containers are for dynamic apps and native deps. For static sites use `my-funnel-api`; for edge functions use `my-function-api`.
|
|
76
76
|
|
|
77
77
|
Run `myapi container --help` for the full flag reference.
|
|
78
|
+
|
|
79
|
+
**End-to-end example:** `examples/authenticated-app/` walks the full seam — hosted login → token verification → per-user KV record → deployed container — including the parts that cost real users hours (env vars are create-only; a service is multi-instance; health-check `/livez` not `/healthz`).
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
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-1e1e7a8c4674f7a109f5bb0b5e9f053e938d2ee36d56c6ce8a4276826f7d9609
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyCRMAPI
|
|
@@ -38,7 +38,7 @@ Move stage with `myapi crm contacts update <id> --stage qualified`. Every stage
|
|
|
38
38
|
goldfox | email | pixel | webhook | manual
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Set automatically
|
|
41
|
+
Set automatically from how the contact entered. Filter with `--source manual` (added by hand) vs `--source goldfox` (from outreach).
|
|
42
42
|
|
|
43
43
|
### Event timeline — reserved kinds
|
|
44
44
|
|
|
@@ -48,9 +48,9 @@ email_sent | email_opened | email_clicked | email_replied
|
|
|
48
48
|
pixel_visit | webhook_received | payment
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
Agents cannot write events directly — the closed enum is intentional.
|
|
51
|
+
Agents cannot write events directly — the closed enum is intentional. For custom state use **mydatabaseapi** (KV) keyed on the contact id; the curated timeline stays authoritative.
|
|
52
52
|
|
|
53
|
-
**Engagement
|
|
53
|
+
**Engagement kinds bump `last_engagement_at`**: email_*, pixel_visit, webhook_received. Admin kinds (created, promoted, stage_changed) don't — promoting a lead isn't engagement.
|
|
54
54
|
|
|
55
55
|
### Auto-ingest
|
|
56
56
|
|
|
@@ -63,6 +63,8 @@ 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.** A deadlock under concurrent ingest could drop the contact *after* the form returned success to the visitor (fixed 2026-07-27). Raw payloads were always stored, so the delivery is there even when the contact isn't.
|
|
67
|
+
|
|
66
68
|
### Soft delete + restore
|
|
67
69
|
|
|
68
70
|
`myapi crm contacts delete <id>` sets `deleted_at` but **retains the event timeline**. By default soft-deleted contacts are excluded from search — pass `--include-deleted` to see them. Restore with `myapi crm contacts restore <id>`.
|
|
@@ -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-b66f7ebdff5bce05eaab90c6812ffc0f9ec729f6df1dc4a8bca90368406b4c21
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyDatabaseAPI
|
|
@@ -105,3 +105,5 @@ myapi database get "by-email:$EMAIL" --ns users --json | jq -r .value
|
|
|
105
105
|
- **Free in v1.** Metered later if usage shows a need. Cost discipline still applies — store data, not blobs.
|
|
106
106
|
|
|
107
107
|
Run `myapi database --help` for inline reference.
|
|
108
|
+
|
|
109
|
+
**End-to-end example:** `examples/authenticated-app/` walks the full seam — hosted login → token verification → per-user KV record → deployed container — including the parts that cost real users hours (KV writes must be wrapped as `{"value": …}`).
|
|
@@ -29,7 +29,7 @@ myapi llm draft --kind email --prompt "Friendly welcome, under 60 words"
|
|
|
29
29
|
## Authentication
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
export MYAPI_KEY=
|
|
32
|
+
export MYAPI_KEY=hq_live_...
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Requires `api_key` and `org_id` from **myapihq**. Inference cost is debited from your MyAPI balance — top up via `myapi billing topup`, or keep it funded automatically with `myapi billing auto-recharge`.
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
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-82666c883230a775b07c8e1ec6243e812e58f246dd9a7cf1659aee77b877ee73
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyWebhookAPI
|
|
@@ -32,6 +32,7 @@ Treat the webhook slug as a secret — anyone with the URL can POST.
|
|
|
32
32
|
| `myapi webhook create "<name>" [--description "..."]` | Create an endpoint, returns id + inbound URL |
|
|
33
33
|
| `myapi webhook list` | List all endpoints in your org |
|
|
34
34
|
| `myapi webhook delete <id>` | Delete an endpoint and all its deliveries |
|
|
35
|
+
| `myapi webhook deliveries [endpoint_id]` | List what came in, newest first (`--limit`, `--cursor`) |
|
|
35
36
|
| `myapi webhook delivery <delivery_id>` | Get the full payload + headers for one delivery |
|
|
36
37
|
<!-- generated:end -->
|
|
37
38
|
|
|
@@ -46,9 +47,12 @@ myapi webhook create "stripe-events" --description "Stripe payment events"
|
|
|
46
47
|
curl -X POST https://api.mywebhookapi.com/webhook/in/<slug> \
|
|
47
48
|
-H "Content-Type: application/json" \
|
|
48
49
|
-d '{"event":"payment.succeeded"}'
|
|
49
|
-
# → responds with {"delivery_id":"<uuid>"}
|
|
50
|
+
# → responds with {"delivery_id":"<uuid>"}
|
|
50
51
|
|
|
51
|
-
# 3.
|
|
52
|
+
# 3. See what actually arrived — list, then read one in full.
|
|
53
|
+
# Don't rely on capturing delivery_id at POST time: for a funnel form that
|
|
54
|
+
# response goes to the visitor's browser, not to you.
|
|
55
|
+
myapi webhook deliveries
|
|
52
56
|
myapi webhook delivery <delivery_id>
|
|
53
57
|
```
|
|
54
58
|
|