@myapihq/cli 2.7.0 → 2.7.2

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.
Files changed (33) hide show
  1. package/dist/commands/account.js +15 -0
  2. package/dist/commands/audience.js +7 -5
  3. package/dist/commands/container.js +50 -26
  4. package/dist/commands/crm/companies.js +4 -4
  5. package/dist/commands/crm/contacts.js +4 -4
  6. package/dist/commands/crm/index.js +3 -0
  7. package/dist/commands/crm/origin-flag.test.d.ts +1 -0
  8. package/dist/commands/crm/origin-flag.test.js +38 -0
  9. package/dist/commands/crm/pagination.d.ts +2 -0
  10. package/dist/commands/crm/pagination.js +9 -0
  11. package/dist/commands/database.js +21 -14
  12. package/dist/commands/domain.js +11 -0
  13. package/dist/commands/fn.js +10 -5
  14. package/dist/commands/task.js +12 -4
  15. package/dist/skills/my-api-hq/SKILL.md +25 -1
  16. package/dist/skills/my-audience-api/SKILL.md +5 -5
  17. package/dist/skills/my-auth-api/SKILL.md +8 -1
  18. package/dist/skills/my-company-api/SKILL.md +3 -3
  19. package/dist/skills/my-container-api/SKILL.md +47 -30
  20. package/dist/skills/my-crm-api/SKILL.md +6 -6
  21. package/dist/skills/my-database-api/README.md +1 -1
  22. package/dist/skills/my-database-api/SKILL.md +25 -4
  23. package/dist/skills/my-domain-api/SKILL.md +18 -1
  24. package/dist/skills/my-email-api/SKILL.md +36 -1
  25. package/dist/skills/my-function-api/SKILL.md +38 -10
  26. package/dist/skills/my-funnel-api/SKILL.md +6 -3
  27. package/dist/skills/my-git-api/SKILL.md +10 -4
  28. package/dist/skills/my-people-api/SKILL.md +3 -3
  29. package/dist/skills/my-pixel-api/SKILL.md +12 -1
  30. package/dist/skills/my-storage-api/SKILL.md +31 -2
  31. package/dist/skills/my-task-api/SKILL.md +9 -1
  32. package/dist/skills/my-webhook-api/SKILL.md +12 -1
  33. package/package.json +3 -2
@@ -25,6 +25,21 @@ export const SCHEMA = {
25
25
  'no-skills': 'boolean',
26
26
  anonymous: 'boolean',
27
27
  anon: 'boolean',
28
+ // Flags belonging to SUBCOMMANDS of `account`. They have to be declared here
29
+ // because the foreign-flag check runs against the top-level command's schema
30
+ // and cannot see subcommand parsers. Omitting them made the CLI print
31
+ // "--registrant-json is not a flag of `myapi account` — the value was
32
+ // ignored" and then use the value anyway: a warning that was itself false,
33
+ // which is worse than the missing check it was added to provide.
34
+ 'registrant-json': 'string',
35
+ 'registrant-name': 'string',
36
+ 'registrant-email': 'string',
37
+ 'registrant-phone': 'string',
38
+ 'registrant-street': 'string',
39
+ 'registrant-city': 'string',
40
+ 'registrant-state': 'string',
41
+ 'registrant-postal-code': 'string',
42
+ 'registrant-country-code': 'string',
28
43
  };
29
44
  export const HELP = `Usage: myapi account <subcommand>
30
45
 
@@ -15,6 +15,8 @@ export const SCHEMA = {
15
15
  org: 'string',
16
16
  name: 'string',
17
17
  description: 'string',
18
+ from: 'string',
19
+ // Deprecated alias for --from; undocumented, removable next minor.
18
20
  source: 'string',
19
21
  filter: 'string',
20
22
  limit: 'number',
@@ -47,11 +49,11 @@ function summarizeAudience(a) {
47
49
  }
48
50
  async function create(nameArg, flags) {
49
51
  const config = requireConfig();
50
- const orgId = requireOrg(flags, config, 'myapi audience create <name> --source <people|company> --filter <json>');
52
+ const orgId = requireOrg(flags, config, 'myapi audience create <name> --from <people|company> --filter <json>');
51
53
  const name = nameArg || flags.name;
52
54
  if (!name)
53
- error('Missing required argument <name>.\nUsage: myapi audience create <name> --source <people|company> --filter <json>');
54
- const source = flags.source;
55
+ error('Missing required argument <name>.\nUsage: myapi audience create <name> --from <people|company> --filter <json>');
56
+ const source = (flags.from ?? flags.source);
55
57
  if (source !== 'people' && source !== 'company') {
56
58
  error(`--source must be "people" or "company" (got: ${source ?? '<missing>'}).`);
57
59
  }
@@ -76,7 +78,7 @@ async function list(flags) {
76
78
  }
77
79
  printTable(res.map(summarizeAudience), {
78
80
  flags,
79
- empty: 'No audiences yet. Create one with: myapi audience create <name> --source <people|company> --filter <json>',
81
+ empty: 'No audiences yet. Create one with: myapi audience create <name> --from <people|company> --filter <json>',
80
82
  });
81
83
  }
82
84
  async function get(id, flags) {
@@ -168,7 +170,7 @@ async function refresh(id, flags) {
168
170
  success(`Refreshed. ${res.previous} → ${res.total} (${sign}${res.delta})`);
169
171
  }
170
172
  const SUBCOMMAND_USAGE = {
171
- 'create': `myapi audience create <name> --source <people|company> --filter <json> [--description <text>] [--org <id>]
173
+ 'create': `myapi audience create <name> --from <people|company> --filter <json> [--description <text>] [--org <id>]
172
174
 
173
175
  The Goldfox filter shape is shared across people/company/audience:
174
176
  {
@@ -133,7 +133,7 @@ export async function create(nameArg, flags) {
133
133
  info(`Name: ${result.container.name}`);
134
134
  info(`Type: ${result.container.type}${result.container.cron_schedule ? ` (${result.container.cron_schedule})` : ''}`);
135
135
  info(`Resources: ${result.container.cpu} CPU, ${result.container.memory}, instances ${result.container.min_instances}-${result.container.max_instances}`);
136
- // The scoped key is returned ONCE — it's delivered to the running
136
+ // The scoped API key is returned ONCE — it's delivered to the running
137
137
  // container as the MYAPI_KEY env var. Deploy rotates it.
138
138
  info('');
139
139
  info(`Scoped API key (returned once — save it if you need it):`);
@@ -221,6 +221,36 @@ export async function deploy(id, image, flags) {
221
221
  const orgId = requireOrg(flags, config, 'myapi container deploy <id> <image-ref> | --source <dir|tar> [--org <id>]');
222
222
  if (!id)
223
223
  error('Missing id.\nUsage: myapi container deploy <id> <image-ref> (or --source <dir|tar>)');
224
+ // --no-promote and --smoke are REFUSED, not honoured, as of 2026-07-28.
225
+ //
226
+ // They shipped in 2.7.0 and do not work on either deploy path. A customer
227
+ // found it by testing rather than trusting the flags, which is the outcome
228
+ // this CLI spends most of its error messages trying to prevent:
229
+ //
230
+ // --source: the multipart body accepts only `source`. The flags were
231
+ // silently dropped — accepted by our flag parser, never sent. That was
232
+ // our bug, and it is the exact failure these flags exist to stop.
233
+ //
234
+ // image ref: the API accepts promote/smoke, then the deploy fails with a
235
+ // runtime error setting traffic to the revision it just created. The
236
+ // previous revision keeps serving, so it is safe, but it does not work.
237
+ //
238
+ // Refusing is strictly better than accepting. A missing guard makes people
239
+ // write their own; a guard that silently passes makes them stop. Restore
240
+ // these the moment the upstream fix lands — see
241
+ // docs/cross-repo-prompts/backend-consolidated-2026-07-28.md.
242
+ if (flags['no-promote'] === true || typeof flags.smoke === 'string') {
243
+ const which = flags['no-promote'] === true ? '--no-promote' : '--smoke';
244
+ error(`${which} is not honoured yet, so this CLI refuses it rather than letting you believe a deploy was guarded.\n\n` +
245
+ 'It shipped in 2.7.0 and does not work end to end on either deploy path:\n' +
246
+ ' --source the API accepts only the tarball on that path; the flag never reaches it\n' +
247
+ ' <image-ref> the API accepts the flag, then fails while moving traffic\n\n' +
248
+ 'Until it lands, the safe sequence is:\n' +
249
+ ` 1. deploy to a non-production container first\n` +
250
+ ` 2. check it yourself (curl for a string only a real build emits)\n` +
251
+ ` 3. deploy the same image to production\n\n` +
252
+ 'Reported upstream; this message goes away when the flag works.');
253
+ }
224
254
  const source = typeof flags.source === 'string' ? flags.source : undefined;
225
255
  // --image is an alias for the positional image ref.
226
256
  if (!image && typeof flags.image === 'string')
@@ -290,15 +320,10 @@ export async function deploy(id, image, flags) {
290
320
  // ── Pre-built image path (sync) ─────────────────────────────────────────
291
321
  if (!image)
292
322
  error('Missing image ref.\nUsage: myapi container deploy <id> <image-ref>\n or: myapi container deploy <id> --source <dir|tar>\n\n→ <image-ref> is a pre-built container image (e.g. a registry path).');
293
- const opts = {};
294
- if (flags['no-promote'] === true)
295
- opts.promote = false;
296
- if (typeof flags.smoke === 'string')
297
- opts.smoke = _parseSmoke(flags.smoke);
298
- if (opts.promote === false && opts.smoke) {
299
- error('--smoke already withholds traffic until the check passes, then promotes.\nUse one or the other: --smoke to verify-and-promote, --no-promote to hold the revision back.');
300
- }
301
- const result = await sdkContainer.deployContainer(config.api_key, orgId, id, image, opts);
323
+ // No DeployOptions built here: --no-promote and --smoke are refused above
324
+ // until the upstream fix lands. The SDK still carries them so the wiring is
325
+ // one commit away, and sdk-container.test.ts keeps them covered.
326
+ const result = await sdkContainer.deployContainer(config.api_key, orgId, id, image);
302
327
  if (flags.json) {
303
328
  printJson(result);
304
329
  return;
@@ -306,6 +331,9 @@ export async function deploy(id, image, flags) {
306
331
  // An unpromoted revision must NOT read like a completed deploy. A response
307
332
  // that looked the same either way is how an agent concludes it has shipped
308
333
  // when it has not — the original outage in miniature.
334
+ // Unreachable while the flags are refused above. Kept because it is the
335
+ // render we want the moment they are restored, and deleting it would mean
336
+ // rewriting it from memory later.
309
337
  if (result.promoted === false) {
310
338
  success(`Revision ${result.revision_id} built — NOT serving traffic`);
311
339
  info(`Test it: ${result.revision_url ?? '(no revision URL returned)'}`);
@@ -392,8 +420,10 @@ export async function revisions(id, flags) {
392
420
  if (revs.length > 0 && revs.every(r => !r.serving && !r.traffic_percent) && container?.status === 'active') {
393
421
  info('');
394
422
  info('Note: every revision reports 0% traffic while this container is active and serving.');
395
- info('That is a known reporting bug for containers deployed before 2026-07-28, not an outage.');
396
- info('Rollback depends on this data, so it is unreliable here until the container is redeployed.');
423
+ info('The traffic column is wrong, not the container. Verified on a container created');
424
+ info('today, so this is not limited to older ones an earlier version of this message');
425
+ info('said redeploying fixes it, which was wrong.');
426
+ info('promote depends on this data and currently fails. Reported upstream.');
397
427
  }
398
428
  }
399
429
  // promote moves all traffic to one revision. Omitting the revision rolls back
@@ -535,23 +565,19 @@ Two ways to deploy:
535
565
  built server-side (typically ~4 minutes), then deployed.
536
566
  Asynchronous — the CLI polls until it's live.
537
567
 
538
- Options:
539
- --no-promote Build the revision without giving it traffic. Test it at
540
- the returned URL, then: myapi container promote <id> <rev>
541
- --smoke '<assertion>' Deploy, assert against the new revision, and promote it
542
- ONLY if the assertion holds. A failure leaves the
543
- previous revision serving.
568
+ TEMPORARILY REFUSED: --no-promote and --smoke
569
+
570
+ Both shipped in 2.7.0 and do not work end to end. Rather than accept a flag
571
+ and deploy anyway, the CLI now refuses them and explains what to do instead.
572
+ A guard that silently passes is worse than no guard.
544
573
 
545
- Grammar: [GET|HEAD] [/path] [status N] [contains TEXT]
546
- Assert on content, not just status — "returns 200" is
547
- true of a placeholder page too.
574
+ Until they land: deploy to a non-production container, verify it yourself,
575
+ then deploy the same image to production.
548
576
 
549
577
  Examples:
550
578
  myapi container deploy <id> registry.example.com/my-app:v2
551
579
  myapi container deploy <id> --source ./my-app
552
- myapi container deploy <id> --source ./context.tar.gz
553
- myapi container deploy <id> <image> --no-promote
554
- myapi container deploy <id> <image> --smoke 'GET / contains assets/'`,
580
+ myapi container deploy <id> --source ./context.tar.gz`,
555
581
  'revisions': `myapi container revisions <id> [--org <id>] [--json]
556
582
 
557
583
  Every revision the runtime currently holds, newest first, with the traffic
@@ -595,10 +621,8 @@ dependencies and long execution.
595
621
  Subcommands:
596
622
  build-logs <id> Why the last --source build failed (--tail N; default 100)
597
623
  create Register a container and get its scoped API key (returned once)
598
- (--health-check /livez to probe with HTTP, not a bare TCP connect)
599
624
  delete <id> Soft-delete and revoke its scoped API key
600
625
  deploy <id> <image> Ship a pre-built image (or --source <dir|tar> to build) and go live
601
- (--no-promote to hold it back; --smoke to verify before promoting)
602
626
  domain <id> <domain> Bind a custom domain (--remove to unbind)
603
627
  get <id> Inspect a container
604
628
  list List containers in your org
@@ -3,7 +3,7 @@ import { crm } from '@myapihq/sdk';
3
3
  import { requireConfig } from '../../config.js';
4
4
  import { success, error, info, printTable, printJson } from '../../output.js';
5
5
  import { requireOrg, requireArg } from '../../helpers.js';
6
- import { pageLine } from './pagination.js';
6
+ import { pageLine, originFlag } from './pagination.js';
7
7
  export const EXPOSES = [
8
8
  'POST /crm/orgs/{org_id}/companies',
9
9
  'POST /crm/orgs/{org_id}/companies/promote',
@@ -32,7 +32,7 @@ function parseCustom(v) {
32
32
  function buildSearchFilter(flags) {
33
33
  return {
34
34
  lifecycle_stage: csv(flags.stage),
35
- source: csv(flags.source),
35
+ source: csv(originFlag(flags)),
36
36
  domain: typeof flags.domain === 'string' ? flags.domain : undefined,
37
37
  include_deleted: flags['include-deleted'] === true || undefined,
38
38
  limit: typeof flags.limit === 'number' ? flags.limit : undefined,
@@ -144,11 +144,11 @@ async function promote(domain, flags) {
144
144
  // ── Dispatcher ──────────────────────────────────────────────────────────
145
145
  const SUBCOMMAND_USAGE = {
146
146
  list: 'myapi crm companies list [--limit N] [--offset N] [--org <id>] [--json]',
147
- search: `myapi crm companies search [--stage <csv>] [--source <csv>] [--domain <d>]
147
+ search: `myapi crm companies search [--stage <csv>] [--origin <csv>] [--domain <d>]
148
148
  [--include-deleted] [--limit N] [--offset N] [--org <id>] [--json]
149
149
 
150
150
  --stage cold, warm, qualified, customer, churned
151
- --source goldfox, email, pixel, webhook, manual`,
151
+ --origin goldfox, email, pixel, webhook, manual`,
152
152
  create: 'myapi crm companies create <domain> [--name <n>] [--stage <s>] [--custom-json <json>] [--org <id>]',
153
153
  get: 'myapi crm companies get <id> [--org <id>]',
154
154
  update: 'myapi crm companies update <id> [--stage <s>] [--name <n>] [--custom-json <json>] [--org <id>]',
@@ -3,7 +3,7 @@ import { crm } from '@myapihq/sdk';
3
3
  import { requireConfig } from '../../config.js';
4
4
  import { success, error, info, printTable, printJson } from '../../output.js';
5
5
  import { requireOrg, requireArg } from '../../helpers.js';
6
- import { pageLine } from './pagination.js';
6
+ import { pageLine, originFlag } from './pagination.js';
7
7
  export const EXPOSES = [
8
8
  'POST /crm/orgs/{org_id}/contacts',
9
9
  'POST /crm/orgs/{org_id}/contacts/promote',
@@ -35,7 +35,7 @@ function parseCustom(v) {
35
35
  function buildSearchFilter(flags) {
36
36
  return {
37
37
  lifecycle_stage: csv(flags.stage),
38
- source: csv(flags.source),
38
+ source: csv(originFlag(flags)),
39
39
  email: typeof flags.email === 'string' ? flags.email : undefined,
40
40
  company_id: typeof flags['company-id'] === 'string' ? flags['company-id'] : undefined,
41
41
  min_last_engagement_days: typeof flags['min-last-engagement-days'] === 'number' ? flags['min-last-engagement-days'] : undefined,
@@ -179,12 +179,12 @@ async function events(id, flags) {
179
179
  // ── Dispatcher ──────────────────────────────────────────────────────────
180
180
  const SUBCOMMAND_USAGE = {
181
181
  list: 'myapi crm contacts list [--limit N] [--offset N] [--org <id>] [--json]',
182
- search: `myapi crm contacts search [--stage <csv>] [--source <csv>] [--email <e>]
182
+ search: `myapi crm contacts search [--stage <csv>] [--origin <csv>] [--email <e>]
183
183
  [--company-id <id>] [--min-last-engagement-days N] [--max-last-engagement-days N]
184
184
  [--include-deleted] [--limit N] [--offset N] [--org <id>] [--json]
185
185
 
186
186
  --stage cold, warm, qualified, customer, churned
187
- --source goldfox, email, pixel, webhook, manual
187
+ --origin goldfox, email, pixel, webhook, manual
188
188
 
189
189
  Engagement filters:
190
190
  --min-last-engagement-days N contacts engaged within N days
@@ -16,6 +16,9 @@ export const SCHEMA = {
16
16
  'last-name': 'string',
17
17
  name: 'string',
18
18
  stage: 'string',
19
+ origin: 'string',
20
+ // Deprecated alias for --origin. Kept so existing scripts keep working;
21
+ // undocumented, and removable no earlier than the next minor.
19
22
  source: 'string',
20
23
  'company-id': 'string',
21
24
  'custom-json': 'string',
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ // `--source` meant five different things across the CLI: a container build
2
+ // context, a git merge branch, an audience dataset, a task origin, and a CRM
3
+ // provenance enum. An agent that learned it in one place carried a wrong prior
4
+ // into the others, and no linter could catch it — `--source` really is valid
5
+ // for every one of those commands, so nothing was ever "unknown".
6
+ //
7
+ // It is `--origin` for provenance now (crm, task) and `--from` for the
8
+ // audience dataset. `--source` keeps its container meaning, and stays on
9
+ // `git merge` where `--target` anchors it and the pair is idiomatic.
10
+ //
11
+ // The old spellings still work, undocumented, so nobody's scripts break.
12
+ import { describe, it, expect } from 'vitest';
13
+ import { originFlag } from './pagination.js';
14
+ describe('originFlag', () => {
15
+ it('reads the new flag', () => {
16
+ expect(originFlag({ origin: 'webhook' })).toBe('webhook');
17
+ });
18
+ it('still accepts the old one', () => {
19
+ expect(originFlag({ source: 'webhook' })).toBe('webhook');
20
+ });
21
+ // Someone mid-migration may have both in a script. The new name is the
22
+ // one they meant.
23
+ it('prefers --origin when both are given', () => {
24
+ expect(originFlag({ origin: 'manual', source: 'webhook' })).toBe('manual');
25
+ });
26
+ it('is undefined when neither is given', () => {
27
+ expect(originFlag({})).toBeUndefined();
28
+ });
29
+ // csv() downstream splits this; the helper must not mangle it.
30
+ it('passes a comma-separated list through untouched', () => {
31
+ expect(originFlag({ origin: 'goldfox,webhook' })).toBe('goldfox,webhook');
32
+ });
33
+ // An explicitly empty --origin is a real (if useless) input, and must not
34
+ // silently fall through to a stale --source in the same command.
35
+ it('does not fall back when --origin is present but empty', () => {
36
+ expect(originFlag({ origin: '', source: 'webhook' })).toBe('');
37
+ });
38
+ });
@@ -1,3 +1,5 @@
1
+ import { type Flags } from '../../helpers.js';
1
2
  import type { Exposes } from '../../exposes.js';
2
3
  export declare const EXPOSES: Exposes;
4
+ export declare function originFlag(flags: Flags): string | boolean | number | undefined;
3
5
  export declare function pageLine(returned: number, total: number | undefined, hasMore: boolean | undefined, singular: string, plural: string): string;
@@ -18,6 +18,15 @@
18
18
  // exempted, so the coverage gate's "every module states its surface" rule
19
19
  // stays absolute.
20
20
  export const EXPOSES = [];
21
+ // --source meant five different things across the CLI: a build context, a
22
+ // merge branch, a dataset, a task origin, and this — where a contact came
23
+ // from. An agent that learned it once carried a wrong prior everywhere else,
24
+ // and no linter could catch it because --source really is valid for each.
25
+ //
26
+ // It is --origin here now. The old name still works and is undocumented.
27
+ export function originFlag(flags) {
28
+ return flags.origin !== undefined ? flags.origin : flags.source;
29
+ }
21
30
  // "3 contacts" · "3 of 128 contacts" · "3 of 128 contacts (more available)".
22
31
  //
23
32
  // `total` is only rendered when the API supplied it, and the more-available
@@ -106,8 +106,8 @@ async function nsDelete(name, flags) {
106
106
  // ── Keys ─────────────────────────────────────────────────────────────────
107
107
  async function keysList(flags) {
108
108
  const config = requireConfig();
109
- const orgId = requireOrg(flags, config, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>]');
110
- const ns = requireNs(flags, 'myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values]');
109
+ const orgId = requireOrg(flags, config, 'myapi database entries --ns <ns> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>]');
110
+ const ns = requireNs(flags, 'myapi database entries --ns <ns> [--prefix <p>] [--limit N] [--values]');
111
111
  const opts = {};
112
112
  if (typeof flags.prefix === 'string')
113
113
  opts.prefix = flags.prefix;
@@ -197,12 +197,16 @@ const SUBCOMMAND_USAGE = {
197
197
  'create': 'myapi database create <name> [--org <id>]',
198
198
  'delete-namespace': `myapi database delete-namespace <name> [--org <id>]
199
199
 
200
- Deletes the namespace AND all keys in it. Irreversible.`,
201
- 'keys': `myapi database keys --ns <namespace> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>] [--json]
200
+ Deletes the namespace AND all entries in it. Irreversible.`,
201
+ 'entries': `myapi database entries --ns <namespace> [--prefix <p>] [--limit N] [--values] [--cursor <c>] [--org <id>] [--json]
202
202
 
203
- Default render is one key per line. --values inlines each value alongside
204
- its key + etag. --cursor takes the next_cursor printed at the end of a
205
- previous page.`,
203
+ Default render is one entry key per line. --values inlines each value
204
+ alongside its key + etag. --cursor takes the next_cursor printed at the end
205
+ of a previous page.
206
+
207
+ (\`myapi database keys\` still works. It was renamed because "key" also means
208
+ an API credential everywhere else on the platform, and an agent reading
209
+ "list keys" could not tell which was meant.)`,
206
210
  'get': `myapi database get <key> --ns <namespace> [--org <id>] [--json]
207
211
 
208
212
  Prints the value as compact JSON on stdout; etag + updated_at go to
@@ -226,17 +230,17 @@ export async function run(subcommand, args, flags) {
226
230
 
227
231
  Namespaces:
228
232
  create <name> Create a namespace
229
- delete-namespace <name> Delete a namespace AND all its keys (irreversible)
233
+ delete-namespace <name> Delete a namespace AND all its entries (irreversible)
230
234
  namespaces List namespaces in the org
231
235
 
232
- Keys (require --ns <namespace>):
233
- keys List keys in a namespace
234
- get <key> Get a key's value + etag
235
- set <key> <value-json> Set a key. Value is JSON; CAS via --if-match
236
- del <key> Delete a key (CAS via --if-match)
236
+ Entries (require --ns <namespace>):
237
+ entries List entries in a namespace
238
+ get <key> Get an entry's value + etag
239
+ set <key> <value-json> Set an entry. Value is JSON; CAS via --if-match
240
+ del <key> Delete an entry (CAS via --if-match)
237
241
 
238
242
  All commands accept --org <id> (or set default: myapi config set-org <id>).
239
- Values are JSON; per-key size cap is 256 KB. Use myapi storage for binary.`);
243
+ Values are JSON; per-entry size cap is 256 KB. Use myapi storage for binary.`);
240
244
  return;
241
245
  }
242
246
  if (flags.help) {
@@ -251,6 +255,9 @@ Values are JSON; per-key size cap is 256 KB. Use myapi storage for binary.`);
251
255
  case 'namespaces': return nsList(flags);
252
256
  case 'create': return nsCreate(args[0], flags);
253
257
  case 'delete-namespace': return nsDelete(args[0], flags);
258
+ // `keys` kept as an undocumented alias: renaming a verb people have in
259
+ // scripts should not break them. Dropped no earlier than the next minor.
260
+ case 'entries':
254
261
  case 'keys': return keysList(flags);
255
262
  case 'get': return keyGet(args[0], flags);
256
263
  case 'set': return keySet(args[0], args[1], flags);
@@ -89,6 +89,17 @@ export async function check(domainArg, flags) {
89
89
  else {
90
90
  const msg = typeof res.message === 'string' ? res.message : res.message ? JSON.stringify(res.message) : 'Not available';
91
91
  info(`${domain} — ${msg} ✗`);
92
+ // "This TLD is not currently supported" reads as a dead end, and a team
93
+ // building for Switzerland took it as one and settled for a .com. It is
94
+ // not: registering elsewhere and importing the nameservers gives the same
95
+ // DNS, SSL and mail behaviour, with no registrar credentials needed. The
96
+ // answer belongs in the same message as the refusal.
97
+ if (/TLD is not (currently )?supported|not supported/i.test(msg)) {
98
+ info('');
99
+ info(`→ You can still use ${domain} on MyAPI. Register it with any registrar,`);
100
+ info(` point its nameservers at MyAPI, then: myapi domain import ${domain}`);
101
+ info(' DNS, SSL and mail records work the same way afterwards.');
102
+ }
92
103
  }
93
104
  }
94
105
  export async function register(domainArg, flags) {
@@ -22,7 +22,7 @@ export const SCHEMA = {
22
22
  scope: 'list',
23
23
  set: 'string',
24
24
  };
25
- // Backend: Story 1 (function CRUD + scoped key) and Story 2/4/5 (deploy a
25
+ // Backend: Story 1 (function CRUD + scoped API key) and Story 2/4/5 (deploy a
26
26
  // JS bundle to the edge runtime, list runs, set env secrets).
27
27
  // Mirrors validateName in myapi-hq/internal/routes/function/crud.go. We
28
28
  // pre-validate client-side so typos fail before the network call; backend
@@ -54,7 +54,10 @@ function summarizeFn(f) {
54
54
  id: f.id,
55
55
  name: f.name,
56
56
  trigger: f.trigger_type === 'cron' ? `cron ${f.cron_schedule ?? '?'}` : 'http',
57
- url: f.invocation_url || '(not deployed)',
57
+ // A cron function is never reachable over HTTP — its URL 404s. Printing
58
+ // one contradicted the docs more loudly than the docs denied it, and a
59
+ // user wrote a `fetch` handler for manual runs that could never fire.
60
+ url: f.trigger_type === 'cron' ? '— (cron: not HTTP-invocable)' : (f.invocation_url || '(not deployed)'),
58
61
  updated_at: f.updated_at,
59
62
  };
60
63
  }
@@ -89,7 +92,7 @@ export async function create(nameArg, flags) {
89
92
  success(`Function created: ${result.function.id}`);
90
93
  info(`Name: ${result.function.name}`);
91
94
  info(`Trigger: ${result.function.trigger_type}${result.function.cron_schedule ? ` (${result.function.cron_schedule})` : ''}`);
92
- // The scoped key is returned ONCE — surface it prominently. It's used by
95
+ // The scoped API key is returned ONCE — surface it prominently. It's used by
93
96
  // the function runtime shim to call other slot endpoints without a
94
97
  // baked-in auth token. Deploy rotates this key.
95
98
  info('');
@@ -128,7 +131,9 @@ export async function get(id, flags) {
128
131
  info(`ID: ${fn.id}`);
129
132
  info(`Name: ${fn.name}`);
130
133
  info(`Trigger: ${fn.trigger_type}${fn.cron_schedule ? ` (${fn.cron_schedule})` : ''}`);
131
- info(`Invocation URL: ${fn.invocation_url || '(not deployed)'}`);
134
+ info(fn.trigger_type === 'cron'
135
+ ? 'Invocation URL: — (cron functions are not reachable over HTTP)'
136
+ : `Invocation URL: ${fn.invocation_url || '(not deployed)'}`);
132
137
  info(`Created: ${fn.created_at}`);
133
138
  info(`Updated: ${fn.updated_at}`);
134
139
  }
@@ -205,7 +210,7 @@ async function waitForLive(url, budgetMs = 75_000) {
205
210
  clearLine();
206
211
  info(`Status: not serving yet after ${Math.round(budgetMs / 1000)}s.`);
207
212
  info(` The deploy itself succeeded — give it another minute.`);
208
- info(` Do NOT redeploy to "fix" it: that rotates the scoped key again.`);
213
+ info(` Do NOT redeploy to "fix" it: that rotates the scoped API key again.`);
209
214
  }
210
215
  // _parseSetPairs parses `--set K=V` entries into a map. Accepts a single
211
216
  // string (comma-joined: K=V,K2=V2) or an array of strings (when the flag is
@@ -15,6 +15,12 @@ export const EXPOSES = [
15
15
  'POST /task/orgs/{org_id}/tasks/{id}/fail',
16
16
  'POST /task/orgs/{org_id}/tasks/{id}/resolve',
17
17
  ];
18
+ // --source meant five different things across the CLI. Here it is --origin;
19
+ // the old name still works and is undocumented. See crm/pagination.ts.
20
+ function taskOrigin(flags) {
21
+ const v = flags.origin !== undefined ? flags.origin : flags.source;
22
+ return typeof v === 'string' ? v : undefined;
23
+ }
18
24
  export const SCHEMA = {
19
25
  body: 'string',
20
26
  importance: 'string',
@@ -24,6 +30,8 @@ export const SCHEMA = {
24
30
  'depends-on': 'string',
25
31
  'dedup-key': 'string',
26
32
  'resolve-on': 'string',
33
+ origin: 'string',
34
+ // Deprecated alias for --origin; undocumented, removable next minor.
27
35
  source: 'string',
28
36
  status: 'string',
29
37
  limit: 'number',
@@ -106,7 +114,7 @@ export async function create(description, flags) {
106
114
  dependsOn: _splitList(flags['depends-on']),
107
115
  dedupKey: typeof flags['dedup-key'] === 'string' ? flags['dedup-key'] : undefined,
108
116
  resolveOn,
109
- source: typeof flags.source === 'string' ? flags.source : undefined,
117
+ source: taskOrigin(flags),
110
118
  });
111
119
  if (flags.json) {
112
120
  printJson(t);
@@ -125,7 +133,7 @@ export async function list(flags) {
125
133
  tag: typeof flags.tag === 'string' ? flags.tag : undefined,
126
134
  importance: typeof flags.importance === 'string' ? flags.importance : undefined,
127
135
  assignee: typeof flags.assignee === 'string' ? flags.assignee : undefined,
128
- source: typeof flags.source === 'string' ? flags.source : undefined,
136
+ source: taskOrigin(flags),
129
137
  limit: typeof flags.limit === 'number' ? flags.limit : undefined,
130
138
  });
131
139
  if (flags.json) {
@@ -235,8 +243,8 @@ export async function cancel(id, flags) {
235
243
  }
236
244
  // ── Dispatcher ───────────────────────────────────────────────────────────────
237
245
  const SUBCOMMAND_USAGE = {
238
- 'create': 'myapi task create "<description>" [--body <md|@file>] [--importance <i>] [--due <rfc3339>] [--assignee <email>] [--tag <t,t>] [--depends-on <id,id>] [--dedup-key <k>] [--resolve-on <event[:field=value]>] [--source <s>] [--org <id>]',
239
- 'list': 'myapi task list [--status <s>] [--tag <t>] [--importance <i>] [--assignee <email>] [--source <s>] [--limit <n>] [--org <id>] [--json]',
246
+ 'create': 'myapi task create "<description>" [--body <md|@file>] [--importance <i>] [--due <rfc3339>] [--assignee <email>] [--tag <t,t>] [--depends-on <id,id>] [--dedup-key <k>] [--resolve-on <event[:field=value]>] [--origin <s>] [--org <id>]',
247
+ 'list': 'myapi task list [--status <s>] [--tag <t>] [--importance <i>] [--assignee <email>] [--origin <s>] [--limit <n>] [--org <id>] [--json]',
240
248
  'get': 'myapi task get <id> [--body] [--org <id>] [--json]\n\n--body additionally fetches the Markdown body tier (a separate read).',
241
249
  'claim': 'myapi task claim <id> [--lease <seconds>] [--worker <name>] [--org <id>]',
242
250
  'extend': 'myapi task extend <id> [--lease <seconds>] [--org <id>]',
@@ -4,7 +4,7 @@ version: 1.0.0
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-80bee1daf601eb33abc5d66b7f8053b20abec1c9c0ec07f26e86e26a2d3bdb78
7
+ checksum: sha256-fab324491446026e1600ac531bfbb164c10d46a617e528e6886be74b7dbdb4b1
8
8
  ---
9
9
 
10
10
  # MyApiHQ
@@ -98,4 +98,28 @@ Each org gets a free preview subdomain (`*.makeautonomous.com`) usable before re
98
98
  - API keys have format `hq_live_...` and are sent as `Authorization: Bearer <key>`.
99
99
  - `org sync-brand` is async (scrapes the site, polls the job).
100
100
 
101
+ ## Minting a least-privilege API key
102
+
103
+ A key's authority is inline and always a subset of the key that mints it, so
104
+ you can hand work a key that cannot exceed its job:
105
+
106
+ ```bash
107
+ myapi keys create --name ci --grant funnel:write,storage:read
108
+ myapi keys create --name readonly --grant '*:read' # read anything, write nothing
109
+ myapi keys create --name billing-fn --org <id> --grant email --spend-cap 25
110
+ ```
111
+
112
+ - `--grant <list>` — `slot:read` / `slot:write`; a bare slot means write, `*`
113
+ means all. **Omitting `--grant` mints an unrestricted key.**
114
+ - `--org <id>` — lock it to one org. Omit for account-wide.
115
+ - `--spend-cap <usd>` — hard ceiling; `0` means the key cannot spend at all.
116
+ - `keys revoke-all --kind function|manual|account` narrows the kill switch.
117
+
118
+ ## Org profile fields
119
+
120
+ `myapi org create <name>` also takes `--tagline`, `--description`,
121
+ `--business-sector` and `--logo-url`. They populate the org's public profile
122
+ and the funnel created alongside it, so setting them at create avoids editing
123
+ two places later.
124
+
101
125
  Run `myapi --help` or `myapi <command> --help` for full flag reference.
@@ -4,7 +4,7 @@ version: 1.0.0
4
4
  description: >
5
5
  Saved audiences = named Goldfox-filter snapshots over the people or company database. Build a target list once, name it, reuse it across campaigns, refresh to re-evaluate against current data. The persistence layer on top of my-people-api + my-company-api.
6
6
  triggers: [audience, segment, target list, saved filter, goldfox, lead list, abm list, refresh, members, prospect database]
7
- checksum: sha256-5d9a2731d587c476eff2955241001fecb8481bbc5e567f06b8e9e76a079eda92
7
+ checksum: sha256-a2c7142e880932ac2aa53cdb29b8632ce2c4d5ef33c3f8b6df705f7e987ab6af
8
8
  ---
9
9
 
10
10
  # MyAudienceAPI
@@ -70,7 +70,7 @@ Allowed values:
70
70
  <!-- generated:start -->
71
71
  | Command | What it does |
72
72
  |---|---|
73
- | `myapi audience create <name> --source <people\|company> --filter '<json>' [--description <text>]` | Save a Goldfox filter as a named audience; returns id + initial member_count |
73
+ | `myapi audience create <name> --from <people\|company> --filter '<json>' [--description <text>]` | Save a Goldfox filter as a named audience; returns id + initial member_count |
74
74
  | `myapi audience list` | List all audiences in the org |
75
75
  | `myapi audience get <id>` | Single audience (name, filter, member_count, timestamps) |
76
76
  | `myapi audience update <id> [--name <x>] [--description <y>] [--filter '<json>']` | Patch name/description/filter; member_count re-evaluates if filter changes |
@@ -84,7 +84,7 @@ Allowed values:
84
84
  ```bash
85
85
  # 1. Create the audience
86
86
  AID=$(myapi audience create "EU decision makers w/ corporate emails" \
87
- --source people \
87
+ --from people \
88
88
  --filter '{"seniority":["c_level","vp_director"],"country":["DE","FR","GB"],"email_type":["corporate"]}' \
89
89
  --json | jq -r .id)
90
90
 
@@ -108,7 +108,7 @@ myapi audience delete $AID
108
108
  ```bash
109
109
  # Build with quality controls — definitive links + registered companies + careers signal
110
110
  AID=$(myapi audience create "EU growth-stage decision makers" \
111
- --source people \
111
+ --from people \
112
112
  --filter '{"seniority":["c_level","vp_director"],"country":["DE","FR","GB"],"email_type":["corporate"],"min_link_confidence":0.9,"has_careers_page":true,"is_registered_entity":true}' \
113
113
  --json | jq -r .id)
114
114
 
@@ -123,7 +123,7 @@ myapi audience refresh $AID
123
123
 
124
124
  ```bash
125
125
  myapi audience create "EU growth-stage SaaS accounts" \
126
- --source company \
126
+ --from company \
127
127
  --filter '{"country":["DE","FR","GB","NL"],"has_careers_page":true,"has_decision_maker":true,"is_registered_entity":true,"min_source_count":3}'
128
128
  ```
129
129
  <!-- llm:end -->