@myapihq/cli 2.16.4 → 2.17.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.
@@ -11,6 +11,7 @@ export const EXPOSES = [
11
11
  'GET /container/orgs/{org_id}/containers',
12
12
  'GET /container/orgs/{org_id}/containers/{id}',
13
13
  'DELETE /container/orgs/{org_id}/containers/{id}',
14
+ 'PATCH /container/orgs/{org_id}/containers/{id}',
14
15
  'POST /container/orgs/{org_id}/containers/{id}/deploy',
15
16
  'GET /container/orgs/{org_id}/containers/{id}/logs',
16
17
  'GET /container/orgs/{org_id}/containers/{id}/revisions',
@@ -28,6 +29,7 @@ export const SCHEMA = {
28
29
  'max-instances': 'number',
29
30
  port: 'number',
30
31
  env: 'string',
32
+ unset: 'string',
31
33
  tail: 'number',
32
34
  scope: 'string',
33
35
  'no-promote': 'boolean',
@@ -587,6 +589,18 @@ Examples:
587
589
  myapi container create --name api --port 8080
588
590
  myapi container create --name nightly --type job --cron "0 3 * * *"
589
591
  myapi container create --name queue-worker --type worker --memory 1Gi`,
592
+ 'env': `myapi container env <id> --env KEY=VALUE[,K2=V2] [--unset KEY3[,KEY4]] [--org <id>] [--json]
593
+
594
+ Change environment variables on a container that already exists, and roll a new
595
+ revision on the SAME image so they take effect. No rebuild, and the container
596
+ keeps its URL, its scoped key and its custom domain.
597
+
598
+ This is a MERGE. Variables you do not name keep their values, so adding one
599
+ entry to an allowlist does not mean resending every secret. --unset removes.
600
+
601
+ Examples:
602
+ myapi container env c-123 --env ALLOWED=a@x.com,b@y.com
603
+ myapi container env c-123 --env LOG_LEVEL=debug --unset LEGACY_FLAG`,
590
604
  'deploy': `myapi container deploy <id> <image-ref> [--org <id>] [--json]
591
605
  myapi container deploy <id> --source <dir|tar.gz> [--org <id>] [--json]
592
606
 
@@ -673,6 +687,7 @@ Subcommands:
673
687
  deploy <id> <image> Ship a pre-built image (or --source <dir|tar> to build) and go live
674
688
  (--smoke to verify before promoting; --no-promote to hold it back)
675
689
  domain <id> <domain> Bind a custom domain (--remove to unbind)
690
+ env <id> Change environment variables (merge; rolls a new revision)
676
691
  get <id> Inspect a container
677
692
  list List containers in your org
678
693
  logs <id> Show recent runtime logs (--tail <n>, --scope all) — see build-logs for build failures
@@ -691,8 +706,59 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
691
706
  info(`Unknown subcommand: ${subcommand}. Run "myapi container --help" for the list.`);
692
707
  return;
693
708
  }
709
+ /**
710
+ * `myapi container env <id> --env K=V[,K2=V2] [--unset K3[,K4]]`
711
+ *
712
+ * Until 2026-08-19 the only way to change a variable was to delete the
713
+ * container and create another, losing the URL, the scoped key and the custom
714
+ * domain. The skill said to do exactly that, and a customer went through three
715
+ * generations of one container in a week because of it.
716
+ */
717
+ async function envCmd(id, flags) {
718
+ const config = requireConfig();
719
+ const orgId = requireOrg(flags, config, 'myapi container env <id> --env K=V [--unset K2]');
720
+ if (!id)
721
+ error('Missing id.\nUsage: myapi container env <id> --env KEY=VALUE[,K2=V2] [--unset KEY3[,KEY4]]');
722
+ const env = {};
723
+ if (typeof flags.env === 'string') {
724
+ const parsed = _parseEnv(flags.env);
725
+ if (typeof parsed === 'string')
726
+ error(parsed);
727
+ Object.assign(env, parsed);
728
+ }
729
+ if (typeof flags.unset === 'string') {
730
+ for (const k of flags.unset.split(',').map(s => s.trim()).filter(Boolean))
731
+ env[k] = null;
732
+ }
733
+ if (Object.keys(env).length === 0) {
734
+ error('Nothing to change. Pass --env KEY=VALUE to set one, or --unset KEY to remove one.\n' +
735
+ 'This is a merge: variables you do not name keep their values.');
736
+ }
737
+ info('› Updating environment…');
738
+ const res = await sdkContainer.updateContainerEnv(config.api_key, orgId, id, env);
739
+ if (flags.json) {
740
+ printJson(res);
741
+ return;
742
+ }
743
+ if (res.set?.length)
744
+ info(` set: ${res.set.join(', ')}`);
745
+ if (res.removed?.length)
746
+ info(` removed: ${res.removed.join(', ')}`);
747
+ // Say plainly whether the running container has the new values, because
748
+ // "stored" and "live" are different states and only one of them is what the
749
+ // caller asked for.
750
+ if (res.applied) {
751
+ success(`Redeployed on the same image — the new values are live${res.revision ? ` (${res.revision})` : ''}.`);
752
+ }
753
+ else {
754
+ info(' Stored. This container has no deployment yet, so the values apply on your first deploy.');
755
+ }
756
+ if (res.env)
757
+ info(` ${Object.keys(res.env).length} variable(s) now set: ${Object.keys(res.env).sort().join(', ')}`);
758
+ }
694
759
  switch (subcommand) {
695
760
  case 'create': return create(args[0], flags);
761
+ case 'env': return envCmd(args[0], flags);
696
762
  case 'build-logs': return buildLogs(args[0], flags);
697
763
  case 'deploy': return deploy(args[0], args[1], flags);
698
764
  case 'list': return list(flags);
@@ -144,12 +144,16 @@ describe('container.getContainerLogs', () => {
144
144
  });
145
145
  });
146
146
  describe('container.EXPOSES', () => {
147
- it('covers the 11 container endpoints', () => {
147
+ it('covers the 12 container endpoints', () => {
148
148
  expect(container.EXPOSES).toEqual([
149
149
  'POST /container/orgs/{org_id}/containers',
150
150
  'GET /container/orgs/{org_id}/containers',
151
151
  'GET /container/orgs/{org_id}/containers/{id}',
152
152
  'DELETE /container/orgs/{org_id}/containers/{id}',
153
+ // Added 2026-08-19. Env is the one create-time setting that is NOT
154
+ // immutable: PATCH merges the map and rolls a revision on the same
155
+ // image, so the container keeps its URL, scoped key and custom domain.
156
+ 'PATCH /container/orgs/{org_id}/containers/{id}',
153
157
  'POST /container/orgs/{org_id}/containers/{id}/deploy',
154
158
  // Added 2026-07-28. Promote and rollback are ONE endpoint: naming a
155
159
  // revision promotes it, omitting one rolls back to the previous ready
@@ -1,21 +1,21 @@
1
1
  ---
2
2
  name: my-container-api
3
- version: 1.0.0
3
+ version: 1.1.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-a5aabfe95b85f1e6ceb2b9254441183d540e81f888cefbdcf302385b95db2fd0
7
+ checksum: sha256-0a83428114cbc7065d1a4db4f8a0cf39ff965c51acac78f5eeff159345a08da2
8
8
  ---
9
9
 
10
10
  # MyContainerAPI
11
11
 
12
- A container runs a pre-built image on managed cloud infrastructure. Three types: a **service** (HTTP server, scales to zero), a **worker** (always-on background process), or a **job** (runs to completion — the only type that takes a cron schedule). Containers are the heavier-duty sibling of edge functions (`myapi fn`) — use them for native dependencies, long execution, or a full dynamic app.
12
+ A container runs a pre-built image on managed cloud infrastructure. Three types: a **service** (HTTP server, scales to zero), a **worker** (always-on background process), or a **job** (runs to completion — the only type that takes a cron schedule).
13
13
 
14
14
  ## Capabilities
15
15
  <!-- llm:start -->
16
16
  The lifecycle is **create → deploy → (optionally) bind a custom domain**.
17
17
 
18
- - `create` registers the container and issues a **scoped API key**, returned once. The running container receives it as the `MYAPI_KEY` env var, so your code calls other MyAPI slots with no token handling. Deploy rotates this key.
18
+ - `create` registers the container and issues a **scoped API key**, returned once. The container gets it as `MYAPI_KEY`, so your code calls other MyAPI slots with no token handling. Deploy rotates this key.
19
19
  - `deploy` takes **either** a pre-built image reference **or** a source
20
20
  directory. `--source ./dir` tars the directory, builds it server-side
21
21
  (typically ~4 minutes) and deploys the result — **no Docker on your machine,
@@ -53,12 +53,12 @@ it, so the probe would never reach your container.
53
53
 
54
54
  ### Custom domains (dynamic apps)
55
55
 
56
- `myapi container domain <id> <domain>` binds a custom domain to a **deployed** container, served over HTTPS automatically. This is the path for a dynamic backend on a real domain — distinct from `my-funnel-api`, which serves static sites.
56
+ `myapi container domain <id> <domain>` binds a custom domain to a **deployed** container, over HTTPS automatically. The path for a dynamic backend on a real domain — unlike `my-funnel-api`, which serves static sites.
57
57
 
58
58
  Get it right:
59
59
 
60
60
  - **Deploy first.** Binding a domain to a container that has never deployed fails (422) — there is nothing running to route to.
61
- - **Register the parent domain first.** The domain's MyAPI-managed parent must already be registered via `my-domain-api`. Binding `app.synthesisdaily.com` requires `synthesisdaily.com` registered in MyAPI; otherwise 422.
61
+ - **Register the parent domain first** via `my-domain-api`. Binding `app.synthesisdaily.com` requires `synthesisdaily.com` registered in MyAPI; otherwise 422.
62
62
  - **One domain per container.** Re-binding, or binding a hostname already taken, fails (409).
63
63
  - `--remove` unbinds. `myapi container get <id>` shows the bound `custom_domain`.
64
64
  <!-- llm:end -->
@@ -127,8 +127,13 @@ way that looks like an application bug.
127
127
  - **Any 5xx from your code is replaced by an edge HTML error page.** A JSON
128
128
  error envelope will not reach the caller. Return a 4xx if the reason has to
129
129
  survive.
130
- - **`--env`, `--cpu`, `--memory`, `--max-instances` and `--cron` are set at
131
- `create` and cannot be changed by `deploy`.** Recreate to change them.
130
+ - **`--cpu`, `--memory`, `--max-instances` and `--cron` are set at `create` and
131
+ cannot be changed by `deploy`.** Recreate to change those.
132
+ - **`--env` CAN be changed after create**, with `container env` — a merge
133
+ (`--unset` removes) that rolls a new revision on the same image, keeping the
134
+ URL, the scoped key and the custom domain. Do NOT recreate to edit a variable:
135
+ this page said to for months, and it cost one customer three generations of
136
+ the same container in a week.
132
137
 
133
138
  ### Keeping a service warm
134
139
 
@@ -167,4 +172,4 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
167
172
 
168
173
  Run `myapi container --help` for the full flag reference.
169
174
 
170
- **End-to-end example:** `examples/authenticated-app/` in github.com/myapihq/myapi walks the full seam — hosted login → token verification → per-user KV record → deployed container. Every constraint it hits is in **Runtime constraints** above.
175
+ **End-to-end example:** `examples/authenticated-app/` in github.com/myapihq/myapi walks the full seam — hosted login → token verification → per-user KV record → deployed container.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "2.16.4",
4
+ "version": "2.17.0",
5
5
  "description": "MyAPI command-line interface",
6
6
  "repository": {
7
7
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
47
47
  },
48
48
  "dependencies": {
49
- "@myapihq/sdk": "^2.16.4"
49
+ "@myapihq/sdk": "^2.17.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^25.6.0",