@myapihq/cli 2.7.2 → 2.8.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/feedback.d.ts +10 -0
- package/dist/commands/feedback.js +173 -0
- package/dist/commands/flag-reachability.test.d.ts +1 -0
- package/dist/commands/flag-reachability.test.js +274 -0
- package/dist/commands/llm.d.ts +1 -0
- package/dist/commands/llm.js +25 -9
- package/dist/completion.js +2 -1
- package/dist/exposes.test.js +1 -0
- package/dist/index.js +7 -0
- package/dist/skills/my-function-api/SKILL.md +7 -1
- package/dist/skills/my-llm-api/SKILL.md +17 -4
- package/package.json +6 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FlagSchema } from '../flags.js';
|
|
2
|
+
import { type Flags } from '../helpers.js';
|
|
3
|
+
import type { Exposes } from '../exposes.js';
|
|
4
|
+
export declare const EXPOSES: Exposes;
|
|
5
|
+
export declare const SCHEMA: FlagSchema;
|
|
6
|
+
export declare function list(flags: Flags): Promise<void>;
|
|
7
|
+
export declare function create(bodyArg: string | undefined, flags: Flags): Promise<void>;
|
|
8
|
+
export declare function resolve(id: string, flags: Flags): Promise<void>;
|
|
9
|
+
export declare function widget(sub: string | undefined, arg: string | undefined, flags: Flags): Promise<void>;
|
|
10
|
+
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// `myapi feedback` — collect what the people using your product tell you.
|
|
2
|
+
//
|
|
3
|
+
// The slot shipped on 2026-07-28 with five endpoints and no CLI. A primitive
|
|
4
|
+
// with no surface is one nobody finds, which is the failure this repo spent a
|
|
5
|
+
// week fixing: two customers concluded a shipped capability did not exist
|
|
6
|
+
// because nothing they read mentioned it.
|
|
7
|
+
import { feedback as sdkFeedback } from '@myapihq/sdk';
|
|
8
|
+
import { requireConfig } from '../config.js';
|
|
9
|
+
import { success, error, info, printTable, printJson, banner } from '../output.js';
|
|
10
|
+
import { formatDate } from '../utils.js';
|
|
11
|
+
import { requireOrg, requireArg, confirmDestructive } from '../helpers.js';
|
|
12
|
+
export const EXPOSES = [
|
|
13
|
+
'GET /feedback/orgs/{org_id}/items',
|
|
14
|
+
'POST /feedback/orgs/{org_id}/items',
|
|
15
|
+
'POST /feedback/orgs/{org_id}/items/{id}/resolve',
|
|
16
|
+
'POST /feedback/orgs/{org_id}/widgets',
|
|
17
|
+
'DELETE /feedback/orgs/{org_id}/widgets/{id}',
|
|
18
|
+
];
|
|
19
|
+
export const SCHEMA = {
|
|
20
|
+
kind: 'string',
|
|
21
|
+
status: 'string',
|
|
22
|
+
body: 'string',
|
|
23
|
+
'page-url': 'string',
|
|
24
|
+
route: 'string',
|
|
25
|
+
origins: 'string',
|
|
26
|
+
};
|
|
27
|
+
const KINDS = ['bug', 'idea', 'praise', 'confusion', 'other'];
|
|
28
|
+
export async function list(flags) {
|
|
29
|
+
const config = requireConfig();
|
|
30
|
+
const orgId = requireOrg(flags, config, 'myapi feedback list [--kind <k>] [--status open|resolved] [--org <id>]');
|
|
31
|
+
if (flags.kind !== undefined && !KINDS.includes(flags.kind)) {
|
|
32
|
+
error(`Invalid --kind "${flags.kind}". Use one of: ${KINDS.join(', ')}.`);
|
|
33
|
+
}
|
|
34
|
+
if (flags.status !== undefined && flags.status !== 'open' && flags.status !== 'resolved') {
|
|
35
|
+
error(`Invalid --status "${flags.status}". Use "open" or "resolved".`);
|
|
36
|
+
}
|
|
37
|
+
const page = await sdkFeedback.listFeedback(config.api_key, orgId, {
|
|
38
|
+
kind: flags.kind,
|
|
39
|
+
status: flags.status,
|
|
40
|
+
limit: typeof flags.limit === 'number' ? flags.limit : undefined,
|
|
41
|
+
offset: typeof flags.offset === 'number' ? flags.offset : undefined,
|
|
42
|
+
});
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
printJson(page);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// `total` here is the match count, not the page size — the platform states
|
|
48
|
+
// that explicitly, having been bitten by the opposite in CRM search.
|
|
49
|
+
const shown = page.items?.length ?? 0;
|
|
50
|
+
info(shown === page.total ? `${shown} item${shown === 1 ? '' : 's'}` : `${shown} of ${page.total} items`);
|
|
51
|
+
printTable((page.items ?? []).map(i => ({
|
|
52
|
+
id: i.id,
|
|
53
|
+
kind: i.kind,
|
|
54
|
+
status: i.status,
|
|
55
|
+
body: i.body.length > 60 ? `${i.body.slice(0, 57)}…` : i.body,
|
|
56
|
+
route: i.route ?? i.page_url ?? '',
|
|
57
|
+
created: i.created_at ? formatDate(i.created_at) : '',
|
|
58
|
+
})), { flags, empty: 'No feedback yet. Put a widget on a page: myapi feedback widget create <name>' });
|
|
59
|
+
if (page.has_more)
|
|
60
|
+
info('More available — raise --limit or pass --offset.');
|
|
61
|
+
}
|
|
62
|
+
export async function create(bodyArg, flags) {
|
|
63
|
+
const config = requireConfig();
|
|
64
|
+
const orgId = requireOrg(flags, config, 'myapi feedback create "<text>" --kind <k> [--org <id>]');
|
|
65
|
+
const body = bodyArg ?? flags.body;
|
|
66
|
+
requireArg(body, 'text', 'myapi feedback create "<text>" --kind bug');
|
|
67
|
+
const kind = flags.kind ?? 'other';
|
|
68
|
+
if (!KINDS.includes(kind)) {
|
|
69
|
+
error(`Invalid --kind "${kind}". Use one of: ${KINDS.join(', ')}.\n\n→ Kind is what the PERSON says it is. "bug" is a claim that the product is broken; do not infer it from the wording.`);
|
|
70
|
+
}
|
|
71
|
+
const item = await sdkFeedback.createFeedback(config.api_key, orgId, {
|
|
72
|
+
kind: kind,
|
|
73
|
+
body: body,
|
|
74
|
+
page_url: typeof flags['page-url'] === 'string' ? flags['page-url'] : undefined,
|
|
75
|
+
route: typeof flags.route === 'string' ? flags.route : undefined,
|
|
76
|
+
});
|
|
77
|
+
if (flags.json) {
|
|
78
|
+
printJson(item);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
success(`Recorded ${item.kind}: ${item.id}`);
|
|
82
|
+
}
|
|
83
|
+
export async function resolve(id, flags) {
|
|
84
|
+
const config = requireConfig();
|
|
85
|
+
const orgId = requireOrg(flags, config, 'myapi feedback resolve <id> [--org <id>]');
|
|
86
|
+
if (!id)
|
|
87
|
+
error('Missing id.\nUsage: myapi feedback resolve <id>');
|
|
88
|
+
await sdkFeedback.resolveFeedback(config.api_key, orgId, id);
|
|
89
|
+
// Already-resolved and not-found answer identically, so this is not proof
|
|
90
|
+
// the id existed. Say so rather than implying a state change happened.
|
|
91
|
+
success(`Resolved ${id}`);
|
|
92
|
+
info('(An unknown id answers the same way, so this is not confirmation the item existed.)');
|
|
93
|
+
}
|
|
94
|
+
export async function widget(sub, arg, flags) {
|
|
95
|
+
const config = requireConfig();
|
|
96
|
+
const orgId = requireOrg(flags, config, 'myapi feedback widget create <name> | revoke <id>');
|
|
97
|
+
if (sub === 'create') {
|
|
98
|
+
requireArg(arg, 'name', 'myapi feedback widget create <name> [--origins a.com,b.com]');
|
|
99
|
+
const origins = typeof flags.origins === 'string'
|
|
100
|
+
? flags.origins.split(',').map(s => s.trim()).filter(Boolean)
|
|
101
|
+
: undefined;
|
|
102
|
+
const w = await sdkFeedback.createWidget(config.api_key, orgId, arg, origins);
|
|
103
|
+
if (flags.json) {
|
|
104
|
+
printJson(w);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
success(`Widget created: ${w.id}`);
|
|
108
|
+
info(`Key: ${w.key}`);
|
|
109
|
+
info('');
|
|
110
|
+
// Saying this plainly matters: a value that looks like a credential and is
|
|
111
|
+
// not one gets treated as a secret, and then nobody puts it in the page.
|
|
112
|
+
info('This key is PUBLIC. It ships in your page source and authenticates nobody —');
|
|
113
|
+
info('it names your org so a visitor can submit without signing in.');
|
|
114
|
+
if (!origins?.length) {
|
|
115
|
+
banner('No --origins set, so any site can post through this key. Set them unless that is intended.');
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (sub === 'revoke') {
|
|
120
|
+
requireArg(arg, 'id', 'myapi feedback widget revoke <id>');
|
|
121
|
+
await confirmDestructive(flags, `revoke widget ${arg} (submissions with it stop immediately)`, 'myapi feedback widget revoke <id> [--yes] [--org <id>]');
|
|
122
|
+
await sdkFeedback.revokeWidget(config.api_key, orgId, arg);
|
|
123
|
+
success(`Widget ${arg} revoked`);
|
|
124
|
+
info('Feedback already collected through it is kept.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
error('Usage: myapi feedback widget create <name> [--origins <list>]\n myapi feedback widget revoke <id>');
|
|
128
|
+
}
|
|
129
|
+
const SUBCOMMAND_USAGE = {
|
|
130
|
+
'list': `myapi feedback list [--kind bug|idea|praise|confusion|other] [--status open|resolved]
|
|
131
|
+
[--limit N] [--offset N] [--org <id>] [--json]
|
|
132
|
+
|
|
133
|
+
Newest first. \`total\` is the number of matches, not the page size.`,
|
|
134
|
+
'create': `myapi feedback create "<text>" --kind <k> [--page-url <url>] [--route <path>] [--org <id>]
|
|
135
|
+
|
|
136
|
+
--kind is what the person reporting says it is, not what the text sounds like.`,
|
|
137
|
+
'resolve': 'myapi feedback resolve <id> [--org <id>]',
|
|
138
|
+
'widget': `myapi feedback widget create <name> [--origins a.com,b.com] [--org <id>]
|
|
139
|
+
myapi feedback widget revoke <id> [--yes] [--org <id>]
|
|
140
|
+
|
|
141
|
+
The key a widget mints is PUBLIC — it ships in page source and authenticates
|
|
142
|
+
nobody. --origins stops another site posting through it.`,
|
|
143
|
+
};
|
|
144
|
+
export async function run(subcommand, args, flags) {
|
|
145
|
+
if (!subcommand || (flags.help && !subcommand)) {
|
|
146
|
+
info(`Usage: myapi feedback <subcommand>
|
|
147
|
+
|
|
148
|
+
Collect feedback from the people using what you built. A widget key lets a
|
|
149
|
+
page submit without a credential; you list, filter and resolve the results.
|
|
150
|
+
|
|
151
|
+
Subcommands:
|
|
152
|
+
create "<text>" Record one piece of feedback (--kind bug|idea|praise|confusion|other)
|
|
153
|
+
list List feedback, newest first (--kind, --status, --limit, --offset)
|
|
154
|
+
resolve <id> Close a piece of feedback
|
|
155
|
+
widget create <name> Mint a PUBLIC widget key for a site (--origins to restrict)
|
|
156
|
+
widget revoke <id> Revoke a widget key; collected feedback is kept
|
|
157
|
+
|
|
158
|
+
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (flags.help) {
|
|
162
|
+
const usage = SUBCOMMAND_USAGE[subcommand];
|
|
163
|
+
info(usage ? `Usage: ${usage}` : `Unknown subcommand: ${subcommand}. Run "myapi feedback --help" for the list.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
switch (subcommand) {
|
|
167
|
+
case 'create': return create(args[0], flags);
|
|
168
|
+
case 'list': return list(flags);
|
|
169
|
+
case 'resolve': return resolve(args[0], flags);
|
|
170
|
+
case 'widget': return widget(args[0], args[1], flags);
|
|
171
|
+
default: error(`Unknown subcommand: ${subcommand}. Run "myapi feedback --help" for a list of valid subcommands.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// Does the flag REACH the call, or does it only parse?
|
|
2
|
+
//
|
|
3
|
+
// This file exists because of a bug we shipped and a customer found.
|
|
4
|
+
//
|
|
5
|
+
// `container deploy --no-promote` was wired to the pre-built-image path and
|
|
6
|
+
// not to the `--source` path. `deployContainerSource()` was called without the
|
|
7
|
+
// options object, so on a source build the flag was accepted and silently
|
|
8
|
+
// discarded. Both flags were declared in the command schema, so the
|
|
9
|
+
// foreign-flag check stayed quiet too. A team deployed with it, the build took
|
|
10
|
+
// 100% of traffic anyway, and they reported it.
|
|
11
|
+
//
|
|
12
|
+
// The tests we had at the time all passed. They tested `_parseSmoke` (a pure
|
|
13
|
+
// parser) and `deployContainer` (the SDK function). Nothing tested the CLI
|
|
14
|
+
// handler, so nothing noticed that one of its two branches never passed the
|
|
15
|
+
// options along. The backend hit the identical shape the same day and put it
|
|
16
|
+
// better than we can:
|
|
17
|
+
//
|
|
18
|
+
// "Counting call sites proves the helper is CALLED, not that it is REACHED."
|
|
19
|
+
//
|
|
20
|
+
// Ours is: testing the parser proves the flag PARSES, not that it is SENT.
|
|
21
|
+
//
|
|
22
|
+
// So this file tests HANDLERS, with the SDK mocked, asserting what actually
|
|
23
|
+
// arrives at the boundary. Every case below is a real field report. When you
|
|
24
|
+
// add a flag that changes a request, add a case here — a unit test on its
|
|
25
|
+
// parser is not cover.
|
|
26
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
27
|
+
const ORG = '11111111-1111-4111-8111-111111111111';
|
|
28
|
+
// The SDK is mocked wholesale so we can see exactly what the handler passes.
|
|
29
|
+
// Typed loosely on purpose: the later describes assign whole slot objects
|
|
30
|
+
// (sdk.task = {...}) and TS would otherwise reject each one. The assertions
|
|
31
|
+
// still check real shapes.
|
|
32
|
+
const sdk = vi.hoisted(() => ({
|
|
33
|
+
container: {
|
|
34
|
+
deployContainer: vi.fn(),
|
|
35
|
+
deployContainerSource: vi.fn(),
|
|
36
|
+
getContainerLogs: vi.fn(),
|
|
37
|
+
listContainers: vi.fn(),
|
|
38
|
+
getContainer: vi.fn(),
|
|
39
|
+
listRevisions: vi.fn(),
|
|
40
|
+
promoteRevision: vi.fn(),
|
|
41
|
+
createContainer: vi.fn(),
|
|
42
|
+
},
|
|
43
|
+
crm: { searchContacts: vi.fn(), searchCompanies: vi.fn() },
|
|
44
|
+
fn: { listFunctions: vi.fn() },
|
|
45
|
+
// Top-level SDK exports the handlers reach for. Mocking the module
|
|
46
|
+
// wholesale drops anything not listed, and the failure reads as a missing
|
|
47
|
+
// export rather than a missing mock — so they are enumerated explicitly.
|
|
48
|
+
withFundsRetry: vi.fn(async (f) => f()),
|
|
49
|
+
MyApiError: class MyApiError extends Error {
|
|
50
|
+
code = '';
|
|
51
|
+
status = 0;
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
vi.mock('@myapihq/sdk', () => sdk);
|
|
55
|
+
vi.mock('../config.js', () => ({
|
|
56
|
+
requireConfig: () => ({ api_key: 'hq_live_test', default_org: ORG }),
|
|
57
|
+
loadConfig: () => ({ api_key: 'hq_live_test', default_org: ORG }),
|
|
58
|
+
CONFIG_DIR: '/tmp/nowhere',
|
|
59
|
+
}));
|
|
60
|
+
let exitError;
|
|
61
|
+
beforeEach(async () => {
|
|
62
|
+
exitError = null;
|
|
63
|
+
vi.clearAllMocks();
|
|
64
|
+
const output = await import('../output.js');
|
|
65
|
+
vi.spyOn(output, 'error').mockImplementation(((m) => {
|
|
66
|
+
exitError = m;
|
|
67
|
+
throw new Error('__EXIT__');
|
|
68
|
+
}));
|
|
69
|
+
vi.spyOn(output, 'info').mockImplementation(() => { });
|
|
70
|
+
vi.spyOn(output, 'success').mockImplementation(() => { });
|
|
71
|
+
vi.spyOn(output, 'printTable').mockImplementation(() => { });
|
|
72
|
+
vi.spyOn(output, 'printJson').mockImplementation(() => { });
|
|
73
|
+
vi.spyOn(output, 'banner').mockImplementation(() => { });
|
|
74
|
+
});
|
|
75
|
+
afterEach(() => vi.restoreAllMocks());
|
|
76
|
+
// Runs a handler and swallows the synthetic exit thrown by a mocked error().
|
|
77
|
+
async function run(fn) {
|
|
78
|
+
try {
|
|
79
|
+
await fn();
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (e?.message !== '__EXIT__')
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
describe('container deploy — the flag must be refused on BOTH paths', () => {
|
|
87
|
+
// The original bug: this branch dropped the options entirely. Now the flags
|
|
88
|
+
// are refused platform-wide, and the refusal has to fire here too — a
|
|
89
|
+
// refusal wired to one branch is the same defect wearing a different hat.
|
|
90
|
+
it('refuses --no-promote on the --source path, and never calls the SDK', async () => {
|
|
91
|
+
const { deploy } = await import('./container.js');
|
|
92
|
+
await run(() => deploy('c1', '', { source: './app', 'no-promote': true, org: ORG }));
|
|
93
|
+
expect(exitError).toMatch(/not honoured yet/);
|
|
94
|
+
expect(sdk.container.deployContainerSource).not.toHaveBeenCalled();
|
|
95
|
+
expect(sdk.container.deployContainer).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
it('refuses --smoke on the --source path', async () => {
|
|
98
|
+
const { deploy } = await import('./container.js');
|
|
99
|
+
await run(() => deploy('c1', '', { source: './app', smoke: 'GET / contains x', org: ORG }));
|
|
100
|
+
expect(exitError).toMatch(/not honoured yet/);
|
|
101
|
+
expect(sdk.container.deployContainerSource).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
it('refuses --no-promote on the image path', async () => {
|
|
104
|
+
const { deploy } = await import('./container.js');
|
|
105
|
+
await run(() => deploy('c1', 'img:v1', { 'no-promote': true, org: ORG }));
|
|
106
|
+
expect(exitError).toMatch(/not honoured yet/);
|
|
107
|
+
expect(sdk.container.deployContainer).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
// The refusal must not become a blanket block on deploying at all.
|
|
110
|
+
it('still deploys normally when neither flag is passed', async () => {
|
|
111
|
+
sdk.container.deployContainer.mockResolvedValue({
|
|
112
|
+
container_id: 'c1', revision_id: 'r1', url: 'https://x', status: 'active', scoped_api_key: 'k',
|
|
113
|
+
});
|
|
114
|
+
const { deploy } = await import('./container.js');
|
|
115
|
+
await run(() => deploy('c1', 'img:v1', { org: ORG }));
|
|
116
|
+
expect(exitError).toBeNull();
|
|
117
|
+
expect(sdk.container.deployContainer).toHaveBeenCalledWith('hq_live_test', ORG, 'c1', 'img:v1');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('crm pagination — --offset must reach the SDK', () => {
|
|
121
|
+
// Reported by a customer as returning page one forever. The API ignored it
|
|
122
|
+
// at the time; once fixed, the CLI had to actually send it, and only a
|
|
123
|
+
// handler-level test proves that.
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
sdk.crm.searchContacts.mockResolvedValue({ contacts: [], total: 0, has_more: false });
|
|
126
|
+
sdk.crm.searchCompanies.mockResolvedValue({ companies: [], total: 0, has_more: false });
|
|
127
|
+
});
|
|
128
|
+
it('passes --offset through on contacts list', async () => {
|
|
129
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
130
|
+
await run(() => crmRun('contacts', ['list'], { offset: 25, limit: 10, org: ORG }));
|
|
131
|
+
expect(sdk.crm.searchContacts).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ offset: 25, limit: 10 }));
|
|
132
|
+
});
|
|
133
|
+
it('passes --offset through on contacts search, alongside filters', async () => {
|
|
134
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
135
|
+
await run(() => crmRun('contacts', ['search'], { offset: 5, origin: 'webhook', org: ORG }));
|
|
136
|
+
expect(sdk.crm.searchContacts).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ offset: 5 }));
|
|
137
|
+
});
|
|
138
|
+
it('passes --offset through on companies', async () => {
|
|
139
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
140
|
+
await run(() => crmRun('companies', ['list'], { offset: 7, org: ORG }));
|
|
141
|
+
expect(sdk.crm.searchCompanies).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ offset: 7 }));
|
|
142
|
+
});
|
|
143
|
+
// The deprecated spelling must still reach the request, or the alias is a
|
|
144
|
+
// promise we are not keeping.
|
|
145
|
+
it('still honours the deprecated --source spelling for provenance', async () => {
|
|
146
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
147
|
+
await run(() => crmRun('contacts', ['search'], { source: 'goldfox', org: ORG }));
|
|
148
|
+
expect(sdk.crm.searchContacts).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ source: ['goldfox'] }));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('container logs — --scope must reach the SDK', () => {
|
|
152
|
+
it('sends scope=all when asked', async () => {
|
|
153
|
+
sdk.container.getContainerLogs.mockResolvedValue([]);
|
|
154
|
+
const { logs } = await import('./container.js');
|
|
155
|
+
await run(() => logs('c1', { scope: 'all', tail: 500, org: ORG }));
|
|
156
|
+
expect(sdk.container.getContainerLogs).toHaveBeenCalledWith('hq_live_test', ORG, 'c1', 500, 'all');
|
|
157
|
+
});
|
|
158
|
+
it('sends no scope by default, rather than the string "container"', async () => {
|
|
159
|
+
sdk.container.getContainerLogs.mockResolvedValue([]);
|
|
160
|
+
const { logs } = await import('./container.js');
|
|
161
|
+
await run(() => logs('c1', { org: ORG }));
|
|
162
|
+
expect(sdk.container.getContainerLogs).toHaveBeenCalledWith('hq_live_test', ORG, 'c1', undefined, undefined);
|
|
163
|
+
});
|
|
164
|
+
it('refuses an invalid scope instead of passing it on', async () => {
|
|
165
|
+
const { logs } = await import('./container.js');
|
|
166
|
+
await run(() => logs('c1', { scope: 'everything', org: ORG }));
|
|
167
|
+
expect(exitError).toMatch(/Invalid --scope/);
|
|
168
|
+
expect(sdk.container.getContainerLogs).not.toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('container create — --health-check is validated before the call', () => {
|
|
172
|
+
it('refuses /healthz client-side and never calls the SDK', async () => {
|
|
173
|
+
const { create } = await import('./container.js');
|
|
174
|
+
await run(() => create('probe', { 'health-check': '/healthz', org: ORG }));
|
|
175
|
+
expect(exitError).toMatch(/intercepts \/healthz/);
|
|
176
|
+
expect(sdk.container.createContainer).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
it('refuses a path that is not a path', async () => {
|
|
179
|
+
const { create } = await import('./container.js');
|
|
180
|
+
await run(() => create('probe', { 'health-check': 'livez', org: ORG }));
|
|
181
|
+
expect(exitError).toMatch(/must be a path/);
|
|
182
|
+
expect(sdk.container.createContainer).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ── Wider coverage ──────────────────────────────────────────────────────────
|
|
186
|
+
//
|
|
187
|
+
// The cases above are the ones a customer found. These are the same class of
|
|
188
|
+
// risk elsewhere: a flag that changes WHAT GETS WRITTEN, where losing it in
|
|
189
|
+
// transit produces a wrong record rather than an error.
|
|
190
|
+
//
|
|
191
|
+
// Coverage is partial and worth stating: this file exercises 8 commands of the
|
|
192
|
+
// 32 that take flags. It covers the ones where a dropped flag is silent and
|
|
193
|
+
// consequential. `list`/`get` verbs are omitted deliberately — a dropped
|
|
194
|
+
// filter there is visible in the output, which is a different and much
|
|
195
|
+
// cheaper failure.
|
|
196
|
+
describe('task create — flags that change the stored record', () => {
|
|
197
|
+
beforeEach(() => { sdk.task = { createTask: vi.fn().mockResolvedValue({ id: 't1' }), listTasks: vi.fn().mockResolvedValue([]) }; });
|
|
198
|
+
it('sends --dedup-key, which is what makes creation idempotent', async () => {
|
|
199
|
+
const { create } = await import('./task.js');
|
|
200
|
+
await run(() => create('do a thing', { 'dedup-key': 'evt-123', org: ORG }));
|
|
201
|
+
expect(sdk.task.createTask).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ dedupKey: 'evt-123' }));
|
|
202
|
+
});
|
|
203
|
+
it('sends --origin, and still honours the deprecated --source', async () => {
|
|
204
|
+
const { create } = await import('./task.js');
|
|
205
|
+
await run(() => create('x', { origin: 'agent', org: ORG }));
|
|
206
|
+
expect(sdk.task.createTask).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ source: 'agent' }));
|
|
207
|
+
vi.clearAllMocks();
|
|
208
|
+
await run(() => create('x', { source: 'legacy', org: ORG }));
|
|
209
|
+
expect(sdk.task.createTask).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ source: 'legacy' }));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('webhook create — the CRM ingest path must survive', () => {
|
|
213
|
+
beforeEach(() => { sdk.webhook = { createEndpoint: vi.fn().mockResolvedValue({ id: 'w1', url: 'u' }) }; });
|
|
214
|
+
// Losing this silently means submissions stop becoming contacts, with no
|
|
215
|
+
// error anywhere — the endpoint keeps accepting deliveries.
|
|
216
|
+
it('sends --crm-email-path', async () => {
|
|
217
|
+
const { create } = await import('./webhook.js');
|
|
218
|
+
await run(() => create('stripe', { 'crm-email-path': 'data.object.customer_email', org: ORG }));
|
|
219
|
+
const [, , , opts] = sdk.webhook.createEndpoint.mock.calls[0];
|
|
220
|
+
expect(opts).toMatchObject({ crm_email_path: 'data.object.customer_email' });
|
|
221
|
+
});
|
|
222
|
+
it('sends --forward-url', async () => {
|
|
223
|
+
const { create } = await import('./webhook.js');
|
|
224
|
+
await run(() => create('gh', { 'forward-url': 'https://example.com/hook', org: ORG }));
|
|
225
|
+
const [, , , opts] = sdk.webhook.createEndpoint.mock.calls[0];
|
|
226
|
+
expect(opts).toMatchObject({ forward_url: 'https://example.com/hook' });
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('audience create — --from selects the dataset', () => {
|
|
230
|
+
beforeEach(() => { sdk.audience = { createAudience: vi.fn().mockResolvedValue({ id: 'a1', member_count: 0 }) }; });
|
|
231
|
+
// Picking the wrong dataset builds an audience of the wrong KIND of record.
|
|
232
|
+
// Nothing errors; the list is simply of companies when you wanted people.
|
|
233
|
+
it('sends --from', async () => {
|
|
234
|
+
const { run: audRun } = await import('./audience.js');
|
|
235
|
+
await run(() => audRun('create', ['my-list'], { from: 'company', filter: '{}', org: ORG }));
|
|
236
|
+
expect(sdk.audience.createAudience).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ source: 'company' }));
|
|
237
|
+
});
|
|
238
|
+
it('still honours the deprecated --source spelling', async () => {
|
|
239
|
+
const { run: audRun } = await import('./audience.js');
|
|
240
|
+
await run(() => audRun('create', ['my-list'], { source: 'people', filter: '{}', org: ORG }));
|
|
241
|
+
expect(sdk.audience.createAudience).toHaveBeenCalledWith('hq_live_test', ORG, expect.objectContaining({ source: 'people' }));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('git commit — authorship must not silently default', () => {
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
sdk.git = {
|
|
247
|
+
commit: vi.fn().mockResolvedValue({ sha: 'abc1234' }),
|
|
248
|
+
// The handler resolves the branch tip before committing; without this it
|
|
249
|
+
// refuses rather than guessing a base, which is the right behaviour and
|
|
250
|
+
// has to be satisfied to reach the call we are testing.
|
|
251
|
+
listRefs: vi.fn().mockResolvedValue({ branches: [{ name: 'main', sha: 'base123' }] }),
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
// Without these every agent-written commit is attributed to the key's
|
|
255
|
+
// account — wrong quietly rather than loudly.
|
|
256
|
+
it('sends --author-name and --author-email', async () => {
|
|
257
|
+
const { commit } = await import('./git.js');
|
|
258
|
+
await run(() => commit('repo', {
|
|
259
|
+
branch: 'main', message: 'm',
|
|
260
|
+
changes: '[{"path":"a.txt","content":"hi"}]',
|
|
261
|
+
'author-name': 'Ada', 'author-email': 'ada@example.com', org: ORG,
|
|
262
|
+
}));
|
|
263
|
+
expect(sdk.git.commit).toHaveBeenCalled();
|
|
264
|
+
const payload = sdk.git.commit.mock.calls[0][3];
|
|
265
|
+
expect(payload.author).toMatchObject({ name: 'Ada', email: 'ada@example.com' });
|
|
266
|
+
});
|
|
267
|
+
it('omits author entirely when neither flag is given', async () => {
|
|
268
|
+
const { commit } = await import('./git.js');
|
|
269
|
+
await run(() => commit('repo', {
|
|
270
|
+
branch: 'main', message: 'm', changes: '[{"path":"a.txt","content":"hi"}]', org: ORG,
|
|
271
|
+
}));
|
|
272
|
+
expect(sdk.git.commit.mock.calls[0][3].author).toBeUndefined();
|
|
273
|
+
});
|
|
274
|
+
});
|
package/dist/commands/llm.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ import { type Flags } from '../helpers.js';
|
|
|
3
3
|
import type { Exposes } from '../exposes.js';
|
|
4
4
|
export declare const EXPOSES: Exposes;
|
|
5
5
|
export declare const SCHEMA: FlagSchema;
|
|
6
|
+
export declare function _parseJsonObjectFlag(raw: unknown, flagName: string): Record<string, unknown> | undefined;
|
|
6
7
|
export declare function run(subcommand: string | undefined, args: string[], flags: Flags): Promise<void>;
|
package/dist/commands/llm.js
CHANGED
|
@@ -24,6 +24,9 @@ export const SCHEMA = {
|
|
|
24
24
|
schema: 'string',
|
|
25
25
|
style: 'string',
|
|
26
26
|
kind: 'string',
|
|
27
|
+
facts: 'string',
|
|
28
|
+
directives: 'string',
|
|
29
|
+
// Deprecated alias for --facts; undocumented, removable next minor.
|
|
27
30
|
context: 'string',
|
|
28
31
|
prompt: 'string',
|
|
29
32
|
tier: 'string',
|
|
@@ -103,21 +106,33 @@ function parseSchemaFlag(flags) {
|
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
// --facts is referent data quoted into the prompt as reference; --directives
|
|
110
|
+
// are writer controls (tone, max_words, format). The platform split them
|
|
111
|
+
// because they are trusted differently, and `--context` predated the split.
|
|
112
|
+
export function _parseJsonObjectFlag(raw, flagName) {
|
|
113
|
+
if (typeof raw !== 'string' || !raw)
|
|
108
114
|
return undefined;
|
|
109
115
|
try {
|
|
110
|
-
const parsed = JSON.parse(
|
|
111
|
-
if (typeof parsed === 'object' && parsed !== null)
|
|
116
|
+
const parsed = JSON.parse(raw);
|
|
117
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
112
118
|
return parsed;
|
|
113
|
-
|
|
119
|
+
}
|
|
120
|
+
error(`${flagName} must be a JSON object`);
|
|
114
121
|
return undefined;
|
|
115
122
|
}
|
|
116
123
|
catch {
|
|
117
|
-
error(
|
|
124
|
+
error(`${flagName} must be valid JSON`);
|
|
118
125
|
return undefined;
|
|
119
126
|
}
|
|
120
127
|
}
|
|
128
|
+
function parseContextFlag(flags) {
|
|
129
|
+
// --facts wins; --context is the old spelling and still works.
|
|
130
|
+
return _parseJsonObjectFlag(flags.facts, '--facts')
|
|
131
|
+
?? _parseJsonObjectFlag(flags.context, '--context');
|
|
132
|
+
}
|
|
133
|
+
function parseDirectivesFlag(flags) {
|
|
134
|
+
return _parseJsonObjectFlag(flags.directives, '--directives');
|
|
135
|
+
}
|
|
121
136
|
function tierFromFlag(flags) {
|
|
122
137
|
if (typeof flags.tier !== 'string' || !flags.tier)
|
|
123
138
|
return undefined;
|
|
@@ -269,7 +284,7 @@ async function summarize(inputArg, flags) {
|
|
|
269
284
|
}
|
|
270
285
|
async function draft(inputArg, flags) {
|
|
271
286
|
const config = requireConfig();
|
|
272
|
-
const orgId = requireOrg(flags, config, 'myapi llm draft --kind <what> [--prompt "<instructions>"] [--
|
|
287
|
+
const orgId = requireOrg(flags, config, 'myapi llm draft --kind <what> [--prompt "<instructions>"] [--facts <json>] ["<source text>"] [--tier <t>] [--org <id>]');
|
|
273
288
|
if (typeof flags.kind !== 'string' || !flags.kind) {
|
|
274
289
|
error('Missing required flag: --kind <email|message|reply|...>');
|
|
275
290
|
return;
|
|
@@ -281,12 +296,13 @@ async function draft(inputArg, flags) {
|
|
|
281
296
|
const promptText = typeof flags.prompt === 'string' ? flags.prompt : '';
|
|
282
297
|
const ctx = parseContextFlag(flags);
|
|
283
298
|
if (!input.trim() && !promptText.trim() && (!ctx || Object.keys(ctx).length === 0)) {
|
|
284
|
-
error('draft needs at least one of: <source text> (arg or --file), --prompt, or --
|
|
299
|
+
error('draft needs at least one of: <source text> (arg or --file), --prompt, or --facts.');
|
|
285
300
|
}
|
|
286
301
|
const res = await retryFunds(() => sdkLlm.draft(config.api_key, orgId, {
|
|
287
302
|
input: input || undefined,
|
|
288
303
|
kind,
|
|
289
|
-
|
|
304
|
+
facts: ctx,
|
|
305
|
+
directives: parseDirectivesFlag(flags),
|
|
290
306
|
prompt: promptText || undefined,
|
|
291
307
|
tier: tierFromFlag(flags),
|
|
292
308
|
}));
|
package/dist/completion.js
CHANGED
|
@@ -29,7 +29,7 @@ export const COMMANDS = [
|
|
|
29
29
|
'account', 'audience', 'auth', 'billing', 'company', 'completion', 'config', 'container',
|
|
30
30
|
'crm', 'database', 'domain', 'email', 'fn', 'funnel', 'git', 'help', 'image',
|
|
31
31
|
'doctor', 'install-skills', 'keys', 'llm', 'login', 'org', 'payments', 'people', 'pixel',
|
|
32
|
-
'queue', 'setup', 'status', 'storage', 'task', 'update', 'url', 'webhook', 'whoami',
|
|
32
|
+
'feedback', 'queue', 'setup', 'status', 'storage', 'task', 'update', 'url', 'webhook', 'whoami',
|
|
33
33
|
'workflow',
|
|
34
34
|
];
|
|
35
35
|
// command → subcommands, for `myapi <command> <TAB>`. Mirrors each
|
|
@@ -61,6 +61,7 @@ export const SUBCOMMANDS = {
|
|
|
61
61
|
payments: ['connect', 'status', 'charge', 'list', 'get', 'refund'],
|
|
62
62
|
container: ['create', 'deploy', 'list', 'get', 'logs', 'domain', 'delete'],
|
|
63
63
|
git: ['create', 'list', 'get', 'delete', 'refs', 'log', 'show', 'tree', 'blob', 'diff', 'commit', 'create-branch', 'delete-branch', 'tag', 'merge', 'repack'],
|
|
64
|
+
feedback: ['create', 'list', 'resolve', 'widget'],
|
|
64
65
|
queue: ['create', 'list', 'get', 'delete', 'enqueue', 'jobs', 'job'],
|
|
65
66
|
task: ['create', 'list', 'get', 'claim', 'extend', 'resolve', 'fail', 'cancel'],
|
|
66
67
|
completion: ['install', 'uninstall'],
|
package/dist/exposes.test.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ import * as paymentsCmd from './commands/payments.js';
|
|
|
41
41
|
import * as containerCmd from './commands/container.js';
|
|
42
42
|
import * as gitCmd from './commands/git.js';
|
|
43
43
|
import * as queueCmd from './commands/queue.js';
|
|
44
|
+
import * as feedbackCmd from './commands/feedback.js';
|
|
44
45
|
import * as taskCmd from './commands/task.js';
|
|
45
46
|
import * as doctorCmd from './commands/doctor.js';
|
|
46
47
|
import * as loginCmd from './commands/login.js';
|
|
@@ -85,6 +86,7 @@ const COMMAND_SCHEMAS = {
|
|
|
85
86
|
people: peopleCmd.SCHEMA,
|
|
86
87
|
pixel: pixelCmd.SCHEMA,
|
|
87
88
|
queue: queueCmd.SCHEMA,
|
|
89
|
+
feedback: feedbackCmd.SCHEMA,
|
|
88
90
|
status: statusCmd.SCHEMA,
|
|
89
91
|
storage: storageCmd.SCHEMA,
|
|
90
92
|
task: taskCmd.SCHEMA,
|
|
@@ -134,6 +136,7 @@ const COMBINED_SCHEMA = {
|
|
|
134
136
|
...containerCmd.SCHEMA,
|
|
135
137
|
...gitCmd.SCHEMA,
|
|
136
138
|
...queueCmd.SCHEMA,
|
|
139
|
+
...feedbackCmd.SCHEMA,
|
|
137
140
|
...taskCmd.SCHEMA,
|
|
138
141
|
...doctorCmd.SCHEMA,
|
|
139
142
|
...loginCmd.SCHEMA,
|
|
@@ -258,6 +261,9 @@ async function main() {
|
|
|
258
261
|
case 'queue':
|
|
259
262
|
await queueCmd.run(subcommand, restArgs, flags);
|
|
260
263
|
break;
|
|
264
|
+
case 'feedback':
|
|
265
|
+
await feedbackCmd.run(subcommand, restArgs, flags);
|
|
266
|
+
break;
|
|
261
267
|
case 'task':
|
|
262
268
|
await taskCmd.run(subcommand, restArgs, flags);
|
|
263
269
|
break;
|
|
@@ -463,6 +469,7 @@ Commands:
|
|
|
463
469
|
doctor Org-wide consistency check — funnels, webhooks, domains, containers
|
|
464
470
|
domain Manage domain configurations
|
|
465
471
|
email Manage mailboxes, send/read email, templates, and campaigns
|
|
472
|
+
feedback Collect feedback from the people using what you built
|
|
466
473
|
fn Create and deploy functions on the edge runtime
|
|
467
474
|
funnel Manage websites (publish pages, custom domains, funnels)
|
|
468
475
|
git Hosted git repositories — repos, commits, branches, history
|
|
@@ -4,7 +4,7 @@ version: 1.0.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-a29b3c96645c59317c0896c9a886d9904bdb2ebde9eb8582d0cb7071d0b9ac2c
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFunctionAPI
|
|
@@ -129,4 +129,10 @@ curl -H "Authorization: Bearer $SCOPED_KEY" \
|
|
|
129
129
|
- Deploy rotates the scoped API key on every call — re-capture the printed value if other systems use it.
|
|
130
130
|
- `myapi fn env <id> --set KEY=VALUE,OTHER=VALUE` sets several secrets in one call instead of one command each.
|
|
131
131
|
|
|
132
|
+
**`--scope` is create-only, so treat it as permanent.** There is no
|
|
133
|
+
`fn scope --add`. Adding a slot later means delete + recreate, which mints a
|
|
134
|
+
**new function id and a new invocation URL**, breaking every reference already
|
|
135
|
+
handed out — docs, front-end config, webhooks, anything given to a third
|
|
136
|
+
party. Decide the full slot list before you publish the URL.
|
|
137
|
+
|
|
132
138
|
Run `myapi fn --help` or `myapi fn <subcommand> --help` for full flag reference.
|
|
@@ -7,7 +7,7 @@ description: >
|
|
|
7
7
|
(classify / extract / summarize / draft) that hide the model behind a
|
|
8
8
|
task. Pricing in cents per 1M tokens; charged from your MyAPI balance.
|
|
9
9
|
triggers: [llm, completion, chat, embed, embedding, inference, classify, extract, summarize, draft, qwen]
|
|
10
|
-
checksum: sha256-
|
|
10
|
+
checksum: sha256-2a9070e118134aea98a1be986d29f9c4e3a969aa960e7dfc87137bbc490ac04e
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# MyLLMAPI
|
|
@@ -21,9 +21,9 @@ Pricing is cents per 1M tokens at the actual upstream rate, debited from your My
|
|
|
21
21
|
|
|
22
22
|
## Capabilities
|
|
23
23
|
<!-- llm:start -->
|
|
24
|
-
|
|
24
|
+
For workflow tasks — summarize, classify, extract, draft. The reply goes to stdout and the usage footer (tokens, cost, finish reason) to stderr, so `myapi llm complete ... | jq` works.
|
|
25
25
|
|
|
26
|
-
**Don't use this as your own model.** If you
|
|
26
|
+
**Don't use this as your own model.** If you are an agent reading this, you already have a more capable model. Reach for the LLM verbs when scripting a recurring step where a small, cheap model is the right tool — not for one-shot reasoning you can do yourself.
|
|
27
27
|
|
|
28
28
|
Reach for raw `complete` when shape matters (you build the `messages` array and set `max_tokens`/`temperature`/`stop`); reach for a verb when you want a *result* and don't care which model produced it.
|
|
29
29
|
|
|
@@ -88,7 +88,7 @@ The model/provider is **never** named in the verb response — the verb is the c
|
|
|
88
88
|
|
|
89
89
|
### OpenAI-compatible drop-in
|
|
90
90
|
|
|
91
|
-
`POST /llm/orgs/{org_id}/chat/completions` (
|
|
91
|
+
`POST /llm/orgs/{org_id}/chat/completions` (alias `/v1/chat/completions`) takes and returns the OpenAI shape — **no envelope**. Same catalog and pricing as `complete`. Use it when an existing OpenAI SDK or LangChain integration should point at MyAPI unchanged.
|
|
92
92
|
|
|
93
93
|
```python
|
|
94
94
|
from openai import OpenAI
|
|
@@ -160,3 +160,16 @@ INTENT=$(printf '%s' "$BODY" | myapi llm classify - \
|
|
|
160
160
|
- **Self-hosted raw, server-picked verbs.** Raw runs on MyAPI's TPU; verbs route wherever the server picks.
|
|
161
161
|
- **Cost + latency.** `usage.cost_cents` is authoritative — no markup. Varies by tier: 200–600 ms to first token, 1–3 s end-to-end.
|
|
162
162
|
- **Live catalog, no streaming, no BYOK.** Don't hard-code ids — `models` is truth (CLI auto-picks if `--model` omitted). Full reply only.
|
|
163
|
+
|
|
164
|
+
## `--facts` vs `--directives` on draft
|
|
165
|
+
|
|
166
|
+
`--facts '<json>'` is referent data, quoted as reference and never as
|
|
167
|
+
instructions (recipient, dates, amounts). `--directives '<json>'` is writer
|
|
168
|
+
controls only: tone, max_words, format, style. They are trusted differently.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
myapi llm draft --kind email --prompt "the invoice is due" \
|
|
172
|
+
--facts '{"to":"Ada"}' --directives '{"tone":"warm"}'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`--context` is the old name for `--facts`; accepted, deprecated upstream.
|
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.8.0",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -38,11 +38,15 @@
|
|
|
38
38
|
"audit:doctor": "npm run build && node scripts/audit-doctor.js",
|
|
39
39
|
"lint:docs": "node scripts/lint-docs.js",
|
|
40
40
|
"lint:skill-coverage": "node scripts/lint-skill-coverage.js",
|
|
41
|
+
"bench:discoverability": "node scripts/bench-discoverability.js",
|
|
42
|
+
"lint:claims": "node scripts/verify-claims.js --lint",
|
|
43
|
+
"verify:claims": "npm run build && node scripts/verify-claims.js",
|
|
44
|
+
"audit:fields": "npm run build && node scripts/audit-field-honoured.js",
|
|
41
45
|
"lint:skills": "node scripts/copy-skills.js && node scripts/lint-skills.js",
|
|
42
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {
|
|
45
|
-
"@myapihq/sdk": "^2.
|
|
49
|
+
"@myapihq/sdk": "^2.8.0"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
52
|
"@types/node": "^25.6.0",
|