@myapihq/cli 2.24.0 → 2.25.1
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/container.js +1 -0
- package/dist/commands/crm/companies.js +2 -1
- package/dist/commands/crm/contacts.js +10 -1
- package/dist/commands/flag-reachability.test.js +38 -1
- package/dist/commands/help-lists-every-verb.test.d.ts +1 -0
- package/dist/commands/help-lists-every-verb.test.js +146 -0
- package/dist/skills/my-crm-api/SKILL.md +19 -14
- package/package.json +2 -2
|
@@ -786,6 +786,7 @@ Subcommands:
|
|
|
786
786
|
domain <id> <domain> Bind a custom domain (--remove to unbind)
|
|
787
787
|
env <id> Change environment variables (merge; rolls a new revision)
|
|
788
788
|
get <id> Inspect a container
|
|
789
|
+
health-check <id> Set or clear the startup probe (--clear); rolls a new revision
|
|
789
790
|
list List containers in your org
|
|
790
791
|
logs <id> Show recent runtime logs (--tail <n>, --scope all) — see build-logs for build failures
|
|
791
792
|
promote <id> <rev> Move all traffic to a revision (seconds, no rebuild)
|
|
@@ -97,9 +97,10 @@ async function get(id, flags) {
|
|
|
97
97
|
}
|
|
98
98
|
async function update(id, flags) {
|
|
99
99
|
const config = requireConfig();
|
|
100
|
-
const orgId = requireOrg(flags, config, 'myapi crm companies update <id> [--stage <s>] [--name <n>] [--custom-json <json>] [--org <id>]');
|
|
100
|
+
const orgId = requireOrg(flags, config, 'myapi crm companies update <id> [--domain <d>] [--stage <s>] [--name <n>] [--custom-json <json>] [--org <id>]');
|
|
101
101
|
requireArg(id, 'id', 'myapi crm companies update <id>');
|
|
102
102
|
const patch = {
|
|
103
|
+
domain: typeof flags.domain === 'string' ? flags.domain : undefined,
|
|
103
104
|
name: typeof flags.name === 'string' ? flags.name : undefined,
|
|
104
105
|
lifecycle_stage: typeof flags.stage === 'string' ? flags.stage : undefined,
|
|
105
106
|
custom: parseCustom(flags['custom-json']),
|
|
@@ -111,9 +111,10 @@ async function get(id, flags) {
|
|
|
111
111
|
}
|
|
112
112
|
async function update(id, flags) {
|
|
113
113
|
const config = requireConfig();
|
|
114
|
-
const orgId = requireOrg(flags, config, 'myapi crm contacts update <id> [--stage <s>] [--first-name <n>] [--last-name <n>] [--company-id <id>] [--custom-json <json>] [--org <id>]');
|
|
114
|
+
const orgId = requireOrg(flags, config, 'myapi crm contacts update <id> [--email <a>] [--stage <s>] [--first-name <n>] [--last-name <n>] [--company-id <id>] [--custom-json <json>] [--org <id>]');
|
|
115
115
|
requireArg(id, 'id', 'myapi crm contacts update <id>');
|
|
116
116
|
const patch = {
|
|
117
|
+
email: typeof flags.email === 'string' ? flags.email : undefined,
|
|
117
118
|
first_name: typeof flags['first-name'] === 'string' ? flags['first-name'] : undefined,
|
|
118
119
|
last_name: typeof flags['last-name'] === 'string' ? flags['last-name'] : undefined,
|
|
119
120
|
company_id: typeof flags['company-id'] === 'string' ? flags['company-id'] : undefined,
|
|
@@ -125,6 +126,14 @@ async function update(id, flags) {
|
|
|
125
126
|
printJson(c);
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
129
|
+
// The API returns the row it re-read, so a change that did not take is
|
|
130
|
+
// visible here. Email is the one that can collide with another contact, and
|
|
131
|
+
// reporting "updated" for a write that did not happen is worse than the
|
|
132
|
+
// failure itself.
|
|
133
|
+
if (patch.email && c.email?.toLowerCase() !== patch.email.trim().toLowerCase()) {
|
|
134
|
+
error(`The email was NOT changed — it is still ${c.email}.\n` +
|
|
135
|
+
`Another contact in this org most likely already uses ${patch.email}.`);
|
|
136
|
+
}
|
|
128
137
|
success(`Contact ${c.id} updated (stage=${c.lifecycle_stage})`);
|
|
129
138
|
}
|
|
130
139
|
async function del(id, flags) {
|
|
@@ -40,7 +40,7 @@ const sdk = vi.hoisted(() => ({
|
|
|
40
40
|
promoteRevision: vi.fn(),
|
|
41
41
|
createContainer: vi.fn(),
|
|
42
42
|
},
|
|
43
|
-
crm: { searchContacts: vi.fn(), searchCompanies: vi.fn() },
|
|
43
|
+
crm: { searchContacts: vi.fn(), searchCompanies: vi.fn(), updateContact: vi.fn(), updateCompany: vi.fn() },
|
|
44
44
|
fn: { listFunctions: vi.fn() },
|
|
45
45
|
// Top-level SDK exports the handlers reach for. Mocking the module
|
|
46
46
|
// wholesale drops anything not listed, and the failure reads as a missing
|
|
@@ -295,3 +295,40 @@ describe('git commit — authorship must not silently default', () => {
|
|
|
295
295
|
expect(sdk.git.commit.mock.calls[0][3].author).toBeUndefined();
|
|
296
296
|
});
|
|
297
297
|
});
|
|
298
|
+
// A CRM contact's email is its identity: the field most likely to be wrong
|
|
299
|
+
// after an import and the one that changes when someone switches employer.
|
|
300
|
+
// `--email` was declared in the crm flag schema (create and the filters use
|
|
301
|
+
// it), so it parsed on `contacts update` and was then dropped on the floor —
|
|
302
|
+
// the patch object simply never read it. The only way to correct an address
|
|
303
|
+
// was to delete the contact and create another, losing the timeline, the
|
|
304
|
+
// campaign history and the audience it was promoted from.
|
|
305
|
+
//
|
|
306
|
+
// Found by asking the reverse of the usual question: not "does the API accept
|
|
307
|
+
// everything we send", but "do we send everything the API accepts".
|
|
308
|
+
describe('crm update — identity fields must REACH the patch', () => {
|
|
309
|
+
const CONTACT = { id: 'ct1', email: 'new@example.com', lifecycle_stage: 'lead' };
|
|
310
|
+
const COMPANY = { id: 'co1', domain: 'new.example.com', lifecycle_stage: 'lead' };
|
|
311
|
+
it('sends --email on contacts update', async () => {
|
|
312
|
+
sdk.crm.updateContact.mockResolvedValue(CONTACT);
|
|
313
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
314
|
+
await run(() => crmRun('contacts', ['update', 'ct1'], { email: 'new@example.com', org: ORG }));
|
|
315
|
+
expect(sdk.crm.updateContact).toHaveBeenCalledWith('hq_live_test', ORG, 'ct1', expect.objectContaining({ email: 'new@example.com' }));
|
|
316
|
+
});
|
|
317
|
+
it('sends --domain on companies update', async () => {
|
|
318
|
+
sdk.crm.updateCompany.mockResolvedValue(COMPANY);
|
|
319
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
320
|
+
await run(() => crmRun('companies', ['update', 'co1'], { domain: 'new.example.com', org: ORG }));
|
|
321
|
+
expect(sdk.crm.updateCompany).toHaveBeenCalledWith('hq_live_test', ORG, 'co1', expect.objectContaining({ domain: 'new.example.com' }));
|
|
322
|
+
});
|
|
323
|
+
// The backend applies each field with its own UPDATE and does not check the
|
|
324
|
+
// result, so an email colliding with another contact leaves the row
|
|
325
|
+
// unchanged and still answers 200. It re-reads the row before replying, so
|
|
326
|
+
// the truth is in the response — and reporting "updated" for a write that
|
|
327
|
+
// did not happen is worse than the failure.
|
|
328
|
+
it('refuses to report success when the email did not actually change', async () => {
|
|
329
|
+
sdk.crm.updateContact.mockResolvedValue({ ...CONTACT, email: 'old@example.com' });
|
|
330
|
+
const { run: crmRun } = await import('./crm/index.js');
|
|
331
|
+
await run(() => crmRun('contacts', ['update', 'ct1'], { email: 'new@example.com', org: ORG }));
|
|
332
|
+
expect(exitError).toMatch(/NOT changed/);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// A verb you cannot find is a verb you do not have.
|
|
2
|
+
//
|
|
3
|
+
// `container health-check` shipped dispatched, documented in SUBCOMMAND_USAGE,
|
|
4
|
+
// covered by tests and named in the skill — and absent from the list that
|
|
5
|
+
// `myapi container --help` prints. The only way to discover it was to already
|
|
6
|
+
// know it existed. It was found by running the published binary after release,
|
|
7
|
+
// which is not a mechanism.
|
|
8
|
+
//
|
|
9
|
+
// The existing help linter checks that listings are ALPHABETICAL. Nothing
|
|
10
|
+
// checked that they are COMPLETE, so a new verb could be sorted correctly into
|
|
11
|
+
// a list it was not in.
|
|
12
|
+
//
|
|
13
|
+
// This drives each command's own help output — the exact string the user sees,
|
|
14
|
+
// not a listing block matched by a regex, because two earlier attempts at this
|
|
15
|
+
// measured the wrong thing: one required a `Subcommands:` header that `task`
|
|
16
|
+
// does not use, and one accepted an entry in SUBCOMMAND_USAGE, which is what
|
|
17
|
+
// health-check already had while remaining invisible.
|
|
18
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
19
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
const COMMANDS_DIR = fileURLToPath(new URL('.', import.meta.url));
|
|
23
|
+
// Verbs deliberately absent from the printed list, each with a reason.
|
|
24
|
+
// A hidden verb is a decision; an accidentally hidden one is the bug above.
|
|
25
|
+
const HIDDEN_OK = {
|
|
26
|
+
'database.ts': {
|
|
27
|
+
keys: 'undocumented alias of `entries`, kept so existing scripts keep working; ' +
|
|
28
|
+
'the code says it goes no earlier than the next minor',
|
|
29
|
+
},
|
|
30
|
+
'org.ts': {
|
|
31
|
+
import: 'deprecated alias of `sync-brand`; still dispatches and prints a ' +
|
|
32
|
+
'deprecation notice, deliberately absent from the listing',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
function moduleFiles() {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const entry of readdirSync(COMMANDS_DIR).sort()) {
|
|
38
|
+
const p = path.join(COMMANDS_DIR, entry);
|
|
39
|
+
if (statSync(p).isDirectory()) {
|
|
40
|
+
const idx = path.join(p, 'index.ts');
|
|
41
|
+
try {
|
|
42
|
+
statSync(idx);
|
|
43
|
+
out.push(`${entry}/index.ts`);
|
|
44
|
+
}
|
|
45
|
+
catch { /* no dispatcher */ }
|
|
46
|
+
}
|
|
47
|
+
else if (entry.endsWith('.ts') && !entry.includes('.test.')) {
|
|
48
|
+
out.push(entry);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
// Only the TOP-LEVEL dispatcher's cases. A nested switch — `account registrant
|
|
54
|
+
// set`, `domain records create`, `llm cache list` — belongs to a sub-verb that
|
|
55
|
+
// the parent line documents inline, and counting those made the check report
|
|
56
|
+
// three commands that are fine.
|
|
57
|
+
function dispatchedVerbs(src) {
|
|
58
|
+
const start = src.indexOf('export async function run(');
|
|
59
|
+
if (start === -1)
|
|
60
|
+
return [];
|
|
61
|
+
let i = src.indexOf('{', start);
|
|
62
|
+
if (i === -1)
|
|
63
|
+
return [];
|
|
64
|
+
let depth = 0, end = src.length;
|
|
65
|
+
for (let j = i; j < src.length; j++) {
|
|
66
|
+
if (src[j] === '{')
|
|
67
|
+
depth++;
|
|
68
|
+
else if (src[j] === '}') {
|
|
69
|
+
depth--;
|
|
70
|
+
if (depth === 0) {
|
|
71
|
+
end = j;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const body = src.slice(i, end);
|
|
77
|
+
return [...new Set([...body.matchAll(/case\s+'([a-z][a-z0-9-]*)'\s*:/g)].map(m => m[1]))];
|
|
78
|
+
}
|
|
79
|
+
let printed;
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
printed = [];
|
|
82
|
+
const output = await import('../output.js');
|
|
83
|
+
const capture = ((m) => { printed.push(String(m ?? '')); });
|
|
84
|
+
vi.spyOn(output, 'info').mockImplementation(capture);
|
|
85
|
+
vi.spyOn(output, 'success').mockImplementation(capture);
|
|
86
|
+
vi.spyOn(output, 'printJson').mockImplementation(capture);
|
|
87
|
+
vi.spyOn(output, 'error').mockImplementation(((m) => {
|
|
88
|
+
printed.push(String(m));
|
|
89
|
+
throw new Error('__EXIT__');
|
|
90
|
+
}));
|
|
91
|
+
});
|
|
92
|
+
afterEach(() => vi.restoreAllMocks());
|
|
93
|
+
async function helpTextOf(file) {
|
|
94
|
+
const mod = await import(`./${file}`);
|
|
95
|
+
if (typeof mod.run !== 'function')
|
|
96
|
+
return '';
|
|
97
|
+
try {
|
|
98
|
+
await mod.run(undefined, [], {});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
if (e?.message !== '__EXIT__')
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
return printed.join('\n');
|
|
105
|
+
}
|
|
106
|
+
describe('every dispatched verb appears in its command help', () => {
|
|
107
|
+
const files = moduleFiles();
|
|
108
|
+
it('no waiver claims to hide a verb that is listed anyway', async () => {
|
|
109
|
+
const stale = [];
|
|
110
|
+
for (const [file, waived] of Object.entries(HIDDEN_OK)) {
|
|
111
|
+
const help = await helpTextOf(file);
|
|
112
|
+
for (const verb of Object.keys(waived)) {
|
|
113
|
+
if (new RegExp(`^\\s+\`?${verb}\\b`, 'm').test(help))
|
|
114
|
+
stale.push(`${file}: ${verb}`);
|
|
115
|
+
}
|
|
116
|
+
printed = [];
|
|
117
|
+
}
|
|
118
|
+
expect(stale, 'these are in the help now, so the waiver claims a gap that is ' +
|
|
119
|
+
'closed — delete the entry.').toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
it('found command modules to check (or this test proves nothing)', () => {
|
|
122
|
+
expect(files.length).toBeGreaterThan(10);
|
|
123
|
+
});
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const src = readFileSync(path.join(COMMANDS_DIR, file), 'utf8');
|
|
126
|
+
const verbs = dispatchedVerbs(src);
|
|
127
|
+
if (!verbs.length)
|
|
128
|
+
continue;
|
|
129
|
+
it(`${file} lists all ${verbs.length} of its verbs`, async () => {
|
|
130
|
+
const help = await helpTextOf(file);
|
|
131
|
+
// A module whose bare `run()` does not print help has nothing to check
|
|
132
|
+
// here; its verbs are reachable from the parent listing.
|
|
133
|
+
if (!help.trim())
|
|
134
|
+
return;
|
|
135
|
+
const waived = HIDDEN_OK[file] ?? {};
|
|
136
|
+
const missing = verbs
|
|
137
|
+
.filter(v => !(v in waived))
|
|
138
|
+
// Listed means: named at the start of an indented line, the shape
|
|
139
|
+
// every command uses for its entries.
|
|
140
|
+
.filter(v => !new RegExp(`^\\s+\`?${v}\\b`, 'm').test(help));
|
|
141
|
+
expect(missing, `${file}: ${missing.join(', ')} dispatch but are not in the ` +
|
|
142
|
+
'help this command prints. A verb nobody can find is a verb nobody has — ' +
|
|
143
|
+
'add it to the listing, or to HIDDEN_OK with a reason.').toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-crm-api
|
|
3
|
-
version: 1.2.
|
|
3
|
+
version: 1.2.1
|
|
4
4
|
description: >
|
|
5
5
|
The canonical store of engaged contacts + companies for an org. Auto-ingests from inbound webhooks via a configurable dot-path. Fixed lifecycle_stage enum (cold | warm | qualified | customer | churned). Append-only event timeline with reserved kinds. Soft delete + restore. Promote-from-Goldfox closes the discovery → engagement loop.
|
|
6
6
|
triggers: [crm, contact, company, lead, engagement, pipeline, lifecycle, qualified, customer, webhook ingest, promote]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-8bc061c79940045d1f95ec4104a452c83342f9ebe2be3c0e087b4c4953524ce6
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyCRMAPI
|
|
11
11
|
|
|
12
|
-
The store that closes the funnel
|
|
12
|
+
The store that closes the funnel: discover people (Goldfox) → audience → email → pixel → form-fill via webhook → and then nowhere. CRM is where people who *engage* land.
|
|
13
13
|
|
|
14
14
|
## Capabilities
|
|
15
15
|
<!-- llm:start -->
|
|
@@ -32,6 +32,10 @@ cold | warm | qualified | customer | churned
|
|
|
32
32
|
|
|
33
33
|
Move stage with `myapi crm contacts update <id> --stage qualified`. Every stage change emits a `stage_changed` event with `{from, to}` so the timeline shows the journey.
|
|
34
34
|
|
|
35
|
+
Correct an address with `--email` (companies: `--domain`) rather than recreating
|
|
36
|
+
the record — a new contact has no timeline, no campaign history and no link to
|
|
37
|
+
its source audience. A collision with another contact is refused.
|
|
38
|
+
|
|
35
39
|
### Contact sources (fixed enum)
|
|
36
40
|
|
|
37
41
|
```
|
|
@@ -54,7 +58,7 @@ Agents cannot write events directly — the enum is closed on purpose. For custo
|
|
|
54
58
|
|
|
55
59
|
### Auto-ingest
|
|
56
60
|
|
|
57
|
-
- **Webhook** (live): set per endpoint via `crm_email_path`, a JSON dot-path. Default `"email"
|
|
61
|
+
- **Webhook** (live): set per endpoint via `crm_email_path`, a JSON dot-path. Default `"email"`. Stripe: `data.object.customer_email`; GitHub: `sender.email`. Empty string disables ingest.
|
|
58
62
|
|
|
59
63
|
Coming next:
|
|
60
64
|
- **Email**: every `myapi email message send` writes `email_sent`; opens/clicks fire `email_opened`/`email_clicked`
|
|
@@ -66,7 +70,7 @@ An unknown email auto-creates the contact with `source=` the originating service
|
|
|
66
70
|
|
|
67
71
|
### Soft delete + restore
|
|
68
72
|
|
|
69
|
-
`myapi crm contacts delete <id>` sets `deleted_at` but **keeps the event timeline**.
|
|
73
|
+
`myapi crm contacts delete <id>` sets `deleted_at` but **keeps the event timeline**. Soft-deleted contacts are excluded from search unless you pass `--include-deleted`. Restore with `myapi crm contacts restore <id>`.
|
|
70
74
|
|
|
71
75
|
### Goldfox enrichment (deferred)
|
|
72
76
|
|
|
@@ -94,7 +98,7 @@ A promoted contact carries a `goldfox_person_id`; the embedded `goldfox_person`
|
|
|
94
98
|
| `myapi crm contacts search [--stage ...] [--origin ...] [--email ...] [--min/max-last-engagement-days N]` | Filter contacts |
|
|
95
99
|
| `myapi crm contacts create <email> [--first-name ...] [--last-name ...] [--stage ...] [--custom-json ...]` | Manually create (source='manual') |
|
|
96
100
|
| `myapi crm contacts get <id>` | Fetch one contact (with embedded Goldfox enrichment when available) |
|
|
97
|
-
| `myapi crm contacts update <id> [--stage ...] [...]` | Patch fields. Stage change emits `stage_changed` event |
|
|
101
|
+
| `myapi crm contacts update <id> [--email <a>] [--stage ...] [...]` | Patch fields, `--email` included. Stage change emits `stage_changed` event |
|
|
98
102
|
| `myapi crm contacts delete <id>` | Soft delete (events retained) |
|
|
99
103
|
| `myapi crm contacts restore <id>` | Restore a soft-deleted contact |
|
|
100
104
|
| `myapi crm contacts promote <goldfox_person_id>` | Idempotent Goldfox → CRM promote |
|
|
@@ -132,9 +136,11 @@ myapi crm contacts create alice@acme.com \
|
|
|
132
136
|
--custom-json '{"intro_via":"riccardo","topic":"video editing"}'
|
|
133
137
|
|
|
134
138
|
# Update stage as the deal progresses — emits a stage_changed event
|
|
135
|
-
myapi crm contacts update <id> --stage qualified
|
|
136
139
|
myapi crm contacts update <id> --stage customer
|
|
137
140
|
|
|
141
|
+
# Fix a mistyped address without losing the timeline
|
|
142
|
+
myapi crm contacts update <id> --email alice@acme.com
|
|
143
|
+
|
|
138
144
|
# See the full engagement timeline
|
|
139
145
|
myapi crm contacts events <id>
|
|
140
146
|
|
|
@@ -159,14 +165,13 @@ myapi crm contacts events <id> --kind webhook_received
|
|
|
159
165
|
|
|
160
166
|
## Notes
|
|
161
167
|
|
|
162
|
-
- **Paginate with `--limit` + `--offset`.**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
- **`external_id` on an event payload** is the backend's idempotency key — a duplicate of the action's natural id (`goldfox_person_id`, `delivery_id`). Read the semantic field instead; legacy `message_id` rows hold the same value.
|
|
168
|
+
- **Paginate with `--limit` + `--offset`.** Branch on `has_more`, not on
|
|
169
|
+
arithmetic against `total`. (Cached guidance saying CRM cannot paginate is
|
|
170
|
+
stale — fixed 2026-07-28.)
|
|
171
|
+
- **Reserved event kinds — no custom events in v1.** For custom per-contact state use `myapi database` keyed by contact id; the curated timeline stays authoritative.
|
|
172
|
+
- **`external_id` on an event payload** is the backend's idempotency key, duplicating the action's natural id (`goldfox_person_id`, `delivery_id`). Read the semantic field instead.
|
|
168
173
|
- **Only `webhook_received` fires today.** Email and pixel auto-ingest are coming; the CLI surface will not change.
|
|
169
|
-
- **Free in v1.** Metered later if usage warrants.
|
|
174
|
+
- **Free in v1.** Metered later if usage warrants it.
|
|
170
175
|
|
|
171
176
|
## HTTP (from deployed code)
|
|
172
177
|
|
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.25.1",
|
|
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.25.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|