@myapihq/cli 2.12.0 → 2.13.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.
|
@@ -14,10 +14,16 @@ export const EXPOSES = [
|
|
|
14
14
|
'POST /feedback/orgs/{org_id}/items',
|
|
15
15
|
'POST /feedback/orgs/{org_id}/items/{id}/resolve',
|
|
16
16
|
'DELETE /feedback/orgs/{org_id}/items/{id}',
|
|
17
|
+
'GET /feedback/orgs/{org_id}/widgets',
|
|
18
|
+
'PATCH /feedback/orgs/{org_id}/widgets/{id}',
|
|
17
19
|
'POST /feedback/orgs/{org_id}/widgets',
|
|
18
20
|
'DELETE /feedback/orgs/{org_id}/widgets/{id}',
|
|
19
21
|
];
|
|
20
22
|
export const SCHEMA = {
|
|
23
|
+
routes: 'string',
|
|
24
|
+
accent: 'string',
|
|
25
|
+
position: 'string',
|
|
26
|
+
label: 'string',
|
|
21
27
|
kind: 'string',
|
|
22
28
|
status: 'string',
|
|
23
29
|
body: 'string',
|
|
@@ -130,6 +136,58 @@ export async function widget(sub, arg, flags) {
|
|
|
130
136
|
}
|
|
131
137
|
return;
|
|
132
138
|
}
|
|
139
|
+
if (sub === 'list') {
|
|
140
|
+
const ws = await sdkFeedback.listWidgets(config.api_key, orgId);
|
|
141
|
+
if (flags.json) {
|
|
142
|
+
printJson(ws);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!ws.length) {
|
|
146
|
+
info('No widgets yet. Mint one: myapi feedback widget create <name>');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
for (const w of ws) {
|
|
150
|
+
info(`${w.id} ${w.name || '(unnamed)'}`);
|
|
151
|
+
// The platform builds the tag; printing it beats making anyone assemble
|
|
152
|
+
// the URL from a key and a base by hand.
|
|
153
|
+
info(` ${w.script_tag || `<script src="https://api.myapihq.com/feedback/in/${w.key}/widget.js" async></script>`}`);
|
|
154
|
+
info(` routes: ${w.routes?.length ? w.routes.join(', ') : '(everywhere)'}`);
|
|
155
|
+
info(` origins: ${w.allowed_origins?.length ? w.allowed_origins.join(', ') : '(any — anyone can post with this key)'}`);
|
|
156
|
+
info('');
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (sub === 'update') {
|
|
161
|
+
requireArg(arg, 'id', 'myapi feedback widget update <id> [--routes /a,/b] [--origins a.com] [--accent #2563eb] [--position bottom-right] [--label "Feedback"]');
|
|
162
|
+
const patch = {};
|
|
163
|
+
const csv = (v) => typeof v === 'string' ? v.split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
164
|
+
if (flags.routes !== undefined)
|
|
165
|
+
patch.routes = csv(flags.routes);
|
|
166
|
+
if (flags.origins !== undefined)
|
|
167
|
+
patch.allowed_origins = csv(flags.origins);
|
|
168
|
+
if (typeof flags.name === 'string')
|
|
169
|
+
patch.name = flags.name;
|
|
170
|
+
const theme = {};
|
|
171
|
+
for (const k of ['accent', 'position', 'label']) {
|
|
172
|
+
if (typeof flags[k] === 'string')
|
|
173
|
+
theme[k] = flags[k];
|
|
174
|
+
}
|
|
175
|
+
if (Object.keys(theme).length)
|
|
176
|
+
patch.theme = theme;
|
|
177
|
+
if (Object.keys(patch).length === 0) {
|
|
178
|
+
error('Nothing to change. Pass at least one of --routes, --origins, --name, --accent, --position, --label.');
|
|
179
|
+
}
|
|
180
|
+
const w = await sdkFeedback.updateWidget(config.api_key, orgId, arg, patch);
|
|
181
|
+
if (flags.json) {
|
|
182
|
+
printJson(w);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
success(`Widget ${arg} updated`);
|
|
186
|
+
// The key is unchanged, which is the reason to use this instead of
|
|
187
|
+
// revoke + create — the customer's page does not need editing.
|
|
188
|
+
info('Same key, so your page needs no change. Route changes reach browsers within ~5 minutes.');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
133
191
|
if (sub === 'revoke') {
|
|
134
192
|
requireArg(arg, 'id', 'myapi feedback widget revoke <id>');
|
|
135
193
|
await confirmDestructive(flags, `revoke widget ${arg} (submissions with it stop immediately)`, 'myapi feedback widget revoke <id> [--yes] [--org <id>]');
|
|
@@ -138,7 +196,7 @@ export async function widget(sub, arg, flags) {
|
|
|
138
196
|
info('Feedback already collected through it is kept.');
|
|
139
197
|
return;
|
|
140
198
|
}
|
|
141
|
-
error('Usage: myapi feedback widget create <name> [--origins <list>]\n myapi feedback widget revoke <id>');
|
|
199
|
+
error('Usage: myapi feedback widget create <name> [--origins <list>]\n myapi feedback widget list\n myapi feedback widget update <id> [--routes <list>] [--origins <list>]\n myapi feedback widget revoke <id>');
|
|
142
200
|
}
|
|
143
201
|
const SUBCOMMAND_USAGE = {
|
|
144
202
|
'delete': `myapi feedback delete <id> [--yes] [--org <id>]
|
|
@@ -154,7 +212,8 @@ Newest first. \`total\` is the number of matches, not the page size.`,
|
|
|
154
212
|
|
|
155
213
|
--kind is what the person reporting says it is, not what the text sounds like.`,
|
|
156
214
|
'resolve': 'myapi feedback resolve <id> [--org <id>]',
|
|
157
|
-
'widget': `myapi feedback widget
|
|
215
|
+
'widget': `myapi feedback widget list
|
|
216
|
+
myapi feedback widget create <name> [--origins a.com,b.com] [--org <id>]
|
|
158
217
|
myapi feedback widget revoke <id> [--yes] [--org <id>]
|
|
159
218
|
|
|
160
219
|
The key a widget mints is PUBLIC — it ships in page source and authenticates
|
|
@@ -173,7 +232,9 @@ Subcommands:
|
|
|
173
232
|
list List feedback, newest first (--kind, --status, --limit, --offset)
|
|
174
233
|
resolve <id> Close a piece of feedback
|
|
175
234
|
widget create <name> Mint a PUBLIC widget key for a site (--origins to restrict)
|
|
235
|
+
widget list List widgets with the script tag to paste
|
|
176
236
|
widget revoke <id> Revoke a widget key; collected feedback is kept
|
|
237
|
+
widget update <id> Change routes/origins/theme, keeping the same key
|
|
177
238
|
|
|
178
239
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
179
240
|
return;
|
package/dist/commands/fn.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function get(id: string, flags: Flags): Promise<void>;
|
|
|
12
12
|
export declare function del(id: string, flags: Flags): Promise<void>;
|
|
13
13
|
export declare function deploy(id: string, bundlePath: string, flags: Flags): Promise<void>;
|
|
14
14
|
export declare function _parseSetPairs(raw: string | string[]): Record<string, string> | string;
|
|
15
|
+
export declare function scopes(id: string | undefined, flags: Flags): Promise<void>;
|
|
15
16
|
export declare function setEnv(id: string, name: string, value: string, flags: Flags): Promise<void>;
|
|
16
17
|
export declare function runs(id: string, flags: Flags): Promise<void>;
|
|
17
18
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
package/dist/commands/fn.js
CHANGED
|
@@ -3,7 +3,7 @@ import { fn as sdkFn } from '@myapihq/sdk';
|
|
|
3
3
|
import { requireConfig } from '../config.js';
|
|
4
4
|
import { success, error, printTable, info, printJson, banner, spinnerFrame, spinnerWrite, clearLine } from '../output.js';
|
|
5
5
|
import { formatDate } from '../utils.js';
|
|
6
|
-
import { requireOrg, confirmDestructive } from '../helpers.js';
|
|
6
|
+
import { requireOrg, requireArg, confirmDestructive } from '../helpers.js';
|
|
7
7
|
export const EXPOSES = [
|
|
8
8
|
'POST /function/orgs/{org_id}/functions',
|
|
9
9
|
'GET /function/orgs/{org_id}/functions',
|
|
@@ -12,6 +12,7 @@ export const EXPOSES = [
|
|
|
12
12
|
'POST /function/orgs/{org_id}/functions/{id}/bundle',
|
|
13
13
|
'POST /function/orgs/{org_id}/functions/{id}/env',
|
|
14
14
|
'GET /function/orgs/{org_id}/functions/{id}/runs',
|
|
15
|
+
'PATCH /function/orgs/{org_id}/functions/{id}/scopes',
|
|
15
16
|
];
|
|
16
17
|
export const SCHEMA = {
|
|
17
18
|
// `create` takes a positional name; --name stays accepted for
|
|
@@ -231,6 +232,42 @@ export function _parseSetPairs(raw) {
|
|
|
231
232
|
}
|
|
232
233
|
return env;
|
|
233
234
|
}
|
|
235
|
+
// scopes changes which slots a deployed function may call, in place.
|
|
236
|
+
//
|
|
237
|
+
// This closes the trap the skill used to have to warn about: --scope was
|
|
238
|
+
// create-only, so adding a slot meant delete + recreate, which minted a NEW
|
|
239
|
+
// function id and invocation URL and broke every reference already handed out.
|
|
240
|
+
// One customer hit that twice in a day and was left over-provisioning scopes
|
|
241
|
+
// up front — the opposite of the least privilege the flag exists to encourage.
|
|
242
|
+
export async function scopes(id, flags) {
|
|
243
|
+
const usage = "myapi fn scopes <id> --set <slot,slot> [--org <id>]";
|
|
244
|
+
const config = requireConfig();
|
|
245
|
+
const orgId = requireOrg(flags, config, usage);
|
|
246
|
+
requireArg(id, 'id', usage);
|
|
247
|
+
const raw = typeof flags.set === 'string' ? flags.set : '';
|
|
248
|
+
const list = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
249
|
+
if (list.length === 0) {
|
|
250
|
+
error(`Missing --set.\nUsage: ${usage}\n\n` +
|
|
251
|
+
`→ --set REPLACES the scope list; name every slot the function should reach.\n` +
|
|
252
|
+
` See the current list with: myapi fn get ${id ?? '<id>'}`);
|
|
253
|
+
}
|
|
254
|
+
const res = await sdkFn.setFunctionScopes(config.api_key, orgId, id, list);
|
|
255
|
+
if (flags.json) {
|
|
256
|
+
printJson(res);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
success(`Scopes for ${id}: ${list.join(', ')}`);
|
|
260
|
+
info('The function id and invocation URL are unchanged — references already handed out keep working.');
|
|
261
|
+
// Same rotation as a deploy, and for the same reason: a narrowing that left
|
|
262
|
+
// the old key alive would narrow nothing. Say so, because a caller holding a
|
|
263
|
+
// manual copy of the key needs to know it just went stale.
|
|
264
|
+
info('');
|
|
265
|
+
info('The scoped key was replaced and pushed to the running function.');
|
|
266
|
+
if (res.scoped_api_key)
|
|
267
|
+
info(` New value (returned once): ${res.scoped_api_key}`);
|
|
268
|
+
else
|
|
269
|
+
info(' env.__MYAPI_KEY inside the function is already current; a manual copy is now stale.');
|
|
270
|
+
}
|
|
234
271
|
// env sets encrypted secret(s) (Stripe key, etc.) on a deployed function.
|
|
235
272
|
// Single form: myapi fn env <id> <name> <value>
|
|
236
273
|
// Bulk form: myapi fn env <id> --set K=V[,K2=V2 ...]
|
|
@@ -325,6 +362,14 @@ in MyAPI or echoed back. The function must already be deployed.
|
|
|
325
362
|
|
|
326
363
|
Single: myapi fn env <id> STRIPE_KEY sk_live_...
|
|
327
364
|
Bulk: myapi fn env <id> --set STRIPE_KEY=sk_live_...,WEBHOOK_SECRET=whsec_...`,
|
|
365
|
+
'scopes': `myapi fn scopes <id> --set <slot,slot> [--org <id>]
|
|
366
|
+
|
|
367
|
+
Change which slots a deployed function may call, in place. The function id and
|
|
368
|
+
invocation URL are unchanged, so references already handed out keep working.
|
|
369
|
+
|
|
370
|
+
--set REPLACES the list: name every slot the function should reach, not just
|
|
371
|
+
the new one. The scoped key is replaced and pushed to the running function, the
|
|
372
|
+
same as a deploy does — env.__MYAPI_KEY stays current, a manual copy goes stale.`,
|
|
328
373
|
'runs': `myapi fn runs <id> [--org <id>] [--json]
|
|
329
374
|
|
|
330
375
|
Lists recent invocation records (most recent first, up to 100).`,
|
|
@@ -346,6 +391,7 @@ Subcommands:
|
|
|
346
391
|
get <id> Inspect a function
|
|
347
392
|
list List functions in your org
|
|
348
393
|
runs <id> List recent invocation records
|
|
394
|
+
scopes <id> Change which slots the function may call (--set a,b)
|
|
349
395
|
|
|
350
396
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
351
397
|
return;
|
|
@@ -362,6 +408,7 @@ All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
|
362
408
|
case 'create': return create(args[0], flags);
|
|
363
409
|
case 'deploy': return deploy(args[0], args[1], flags);
|
|
364
410
|
case 'env': return setEnv(args[0], args[1], args[2], flags);
|
|
411
|
+
case 'scopes': return scopes(args[0], flags);
|
|
365
412
|
case 'runs': return runs(args[0], flags);
|
|
366
413
|
case 'list': return list(flags);
|
|
367
414
|
case 'get': return get(args[0], flags);
|
|
@@ -213,7 +213,7 @@ describe('fn.listFunctionRuns', () => {
|
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
215
|
describe('fn.EXPOSES', () => {
|
|
216
|
-
it('matches the Story 1 + Story 2/4/5 contract — exactly
|
|
216
|
+
it('matches the Story 1 + Story 2/4/5 contract, plus in-place scopes — exactly 8 endpoints', () => {
|
|
217
217
|
expect(fn.EXPOSES).toEqual([
|
|
218
218
|
'POST /function/orgs/{org_id}/functions',
|
|
219
219
|
'GET /function/orgs/{org_id}/functions',
|
|
@@ -222,6 +222,10 @@ describe('fn.EXPOSES', () => {
|
|
|
222
222
|
'POST /function/orgs/{org_id}/functions/{id}/bundle',
|
|
223
223
|
'POST /function/orgs/{org_id}/functions/{id}/env',
|
|
224
224
|
'GET /function/orgs/{org_id}/functions/{id}/runs',
|
|
225
|
+
// Added 2026-08-13. Scopes became changeable in place, which is what
|
|
226
|
+
// ends the delete-and-recreate trap that cost a customer two invocation
|
|
227
|
+
// URLs in one day.
|
|
228
|
+
'PATCH /function/orgs/{org_id}/functions/{id}/scopes',
|
|
225
229
|
]);
|
|
226
230
|
});
|
|
227
231
|
it('exposes deploy/env/runs but not /logs (still pending)', () => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-feedback-api
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.3.0
|
|
4
4
|
description: >
|
|
5
5
|
Collect feedback from the people using what you built. A public widget key lets a page submit without a credential; you list, filter and resolve the results. Kind is chosen by the person reporting, not inferred from their wording.
|
|
6
6
|
triggers: [feedback, bug report, user feedback, feature request, widget, support, complaints, praise]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-de137a7db55ba3173aecdd489b4568e66166a0d6c145f5a9fa165c5ef252bc01
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFeedbackAPI
|
|
@@ -43,7 +43,8 @@ theme baked in. This is the intended way in — you do not build a UI:
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
It renders a button, shows itself only on the routes the widget is configured
|
|
46
|
-
for
|
|
46
|
+
for (set them with `widget update <id> --routes /app,/app/**`; changes reach
|
|
47
|
+
browsers within ~5 minutes with no redeploy of your site), and lets the person point at an element or drag a region. It sends `kind`,
|
|
47
48
|
`body`, `page_url`, `route`, `viewport`, plus **either** `target_selector` (they
|
|
48
49
|
clicked something) **or** `target_region` `{x,y,w,h}` (they dragged a box).
|
|
49
50
|
|
|
@@ -104,6 +105,8 @@ that is deliberate, so ids cannot be probed across orgs.
|
|
|
104
105
|
| `myapi feedback resolve <id>` | Close an item, keeping it |
|
|
105
106
|
| `myapi feedback delete <id>` | Erase an item — for spam, or personal details typed into the box |
|
|
106
107
|
| `myapi feedback widget create <name> [--origins a.com,b.com]` | Mint a PUBLIC widget key for a site |
|
|
108
|
+
| `myapi feedback widget list` | List widgets, each with the script tag to paste |
|
|
109
|
+
| `myapi feedback widget update <id>` | Change `--routes`, `--origins`, `--name`, or the look (`--accent`, `--position`, `--label`), keeping the same key |
|
|
107
110
|
| `myapi feedback widget revoke <id>` | Revoke a key; collected feedback is kept |
|
|
108
111
|
<!-- generated:end -->
|
|
109
112
|
|
|
@@ -114,7 +117,13 @@ that is deliberate, so ids cannot be probed across orgs.
|
|
|
114
117
|
myapi feedback widget create marketing --origins example.com,www.example.com
|
|
115
118
|
# → prints a PUBLIC key to embed in the page
|
|
116
119
|
|
|
117
|
-
# 2.
|
|
120
|
+
# 2. See your widgets and the exact tag to paste
|
|
121
|
+
myapi feedback widget list
|
|
122
|
+
|
|
123
|
+
# 3. Change where it appears — same key, so your page needs no edit
|
|
124
|
+
myapi feedback widget update <id> --routes /app,/app/**
|
|
125
|
+
|
|
126
|
+
# 4. Read what came back, newest first
|
|
118
127
|
myapi feedback list --status open
|
|
119
128
|
myapi feedback list --kind bug --limit 20
|
|
120
129
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-function-api
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.1.0
|
|
4
4
|
description: >
|
|
5
5
|
Deploy JavaScript functions to the MyAPI edge runtime. Register a function, upload a single-file JS bundle, get a live HTTP invocation URL or run it on a cron schedule. Each function gets a scoped capability key for cross-slot calls.
|
|
6
6
|
triggers: [function, deploy function, edge function, serverless, cloudflare worker, cron, scoped api key, capability key, invocation url, bundle]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-3702cfd630b5ff8a892704c4f6aee9f6678881c73fd3522ce4bd52be865e639e
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFunctionAPI
|
|
@@ -40,6 +40,7 @@ Name rules (validated client- and server-side, kept identical):
|
|
|
40
40
|
| `myapi fn runs <id>` | List recent invocation records (status, duration, errors) |
|
|
41
41
|
| `myapi fn list` | List functions in your org |
|
|
42
42
|
| `myapi fn get <id>` | Inspect a function (name, trigger, invocation URL) |
|
|
43
|
+
| `myapi fn scopes <id> --set <slot,slot>` | Change which slots the function may call, in place. Replaces the list; rotates the scoped key |
|
|
43
44
|
| `myapi fn delete <id>` | Soft-delete the record + revoke the scoped API key |
|
|
44
45
|
<!-- generated:end -->
|
|
45
46
|
|
|
@@ -70,6 +71,9 @@ myapi fn create --name daily-report --cron "0 8 * * *"
|
|
|
70
71
|
# Register with a narrowed key — the function can only call email + storage
|
|
71
72
|
myapi fn create --name mailer --scope email,storage
|
|
72
73
|
|
|
74
|
+
# Need another slot later? Change it in place — same id, same URL.
|
|
75
|
+
myapi fn scopes fn_abc123 --set email,storage,crm
|
|
76
|
+
|
|
73
77
|
# List + inspect
|
|
74
78
|
myapi fn list
|
|
75
79
|
myapi fn get fn_abc123
|
|
@@ -129,11 +133,18 @@ curl -H "Authorization: Bearer $SCOPED_KEY" \
|
|
|
129
133
|
- Deploy rotates the scoped API key on every call — re-capture the printed value if other systems use it.
|
|
130
134
|
- `myapi fn env <id> --set KEY=VALUE,OTHER=VALUE` sets several secrets in one call instead of one command each.
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
**Scopes are changeable in place — `myapi fn scopes <id> --set <slot,slot>`.**
|
|
137
|
+
The function id and invocation URL are unchanged, so references already handed
|
|
138
|
+
out keep working. This used to be create-only, and adding a slot meant delete +
|
|
139
|
+
recreate with a new id and URL; that is no longer the trade-off.
|
|
140
|
+
|
|
141
|
+
Two things to know:
|
|
142
|
+
|
|
143
|
+
- **`--set` REPLACES the list**, it does not add to it. Name every slot the
|
|
144
|
+
function should reach. `myapi fn get <id>` shows the current set.
|
|
145
|
+
- **It rotates the scoped key**, exactly as a deploy does — a narrowing that
|
|
146
|
+
left the old key alive would narrow nothing. `env.__MYAPI_KEY` inside the
|
|
147
|
+
function is updated for you; a copy you saved elsewhere goes stale.
|
|
137
148
|
|
|
138
149
|
## HTTP (from deployed code)
|
|
139
150
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.13.0",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@myapihq/sdk": "^2.
|
|
49
|
+
"@myapihq/sdk": "^2.13.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|