@myapihq/cli 1.0.27 → 1.0.28
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/auth.d.ts +1 -0
- package/dist/commands/auth.js +84 -13
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +15 -2
- package/dist/commands/funnel.js +8 -6
- package/dist/commands/setup.js +87 -36
- package/dist/commands/update.js +4 -2
- package/dist/config.d.ts +19 -1
- package/dist/config.js +85 -21
- package/dist/index.js +43 -21
- package/dist/skills/my-api-hq.md +116 -0
- package/dist/skills/my-domain-api.md +83 -0
- package/dist/skills/my-funnel-api.md +35 -0
- package/package.json +2 -2
- package/scripts/copy-skills.js +3 -1
- package/src/commands/auth.ts +81 -14
- package/src/commands/config.ts +15 -2
- package/src/commands/funnel.ts +9 -7
- package/src/commands/setup.ts +90 -37
- package/src/commands/update.ts +4 -2
- package/src/config.ts +93 -23
- package/src/index.ts +30 -19
- package/src/skills/my-funnel-api.md +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// AUTO-GENERATED by scripts/generate-indexes.js — do not edit manually
|
|
3
3
|
import { parseArgs } from './utils.js';
|
|
4
|
-
import { error, info } from './output.js';
|
|
4
|
+
import { error, info, success } from './output.js';
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
import { MyApiError } from '@myapihq/sdk';
|
|
7
7
|
import * as fs from 'fs';
|
|
@@ -11,11 +11,11 @@ import * as keysCmd from './commands/keys.js';
|
|
|
11
11
|
import * as billingCmd from './commands/billing.js';
|
|
12
12
|
import * as orgCmd from './commands/org.js';
|
|
13
13
|
import * as setupCmd from './commands/setup.js';
|
|
14
|
-
import * as configCmd from './commands/config.js';
|
|
15
|
-
import * as authCmd from './commands/auth.js';
|
|
16
14
|
import * as updateCmd from './commands/update.js';
|
|
17
15
|
import * as domainCmd from './commands/domain.js';
|
|
18
16
|
import * as funnelCmd from './commands/funnel.js';
|
|
17
|
+
import * as authCmd from './commands/auth.js';
|
|
18
|
+
import * as configCmd from './commands/config.js';
|
|
19
19
|
async function main() {
|
|
20
20
|
// Fire-and-forget auto-update check — never blocks the command.
|
|
21
21
|
updateCmd.checkForUpdate(pkg.version).catch(() => { });
|
|
@@ -31,8 +31,8 @@ async function main() {
|
|
|
31
31
|
if (args.length === 0) {
|
|
32
32
|
const config = loadConfig();
|
|
33
33
|
if (!config?.api_key) {
|
|
34
|
-
info('No account found. Run: myapi setup');
|
|
35
|
-
|
|
34
|
+
info('No account found. Run: myapi auth setup');
|
|
35
|
+
info('');
|
|
36
36
|
}
|
|
37
37
|
printHelp();
|
|
38
38
|
process.exit(0);
|
|
@@ -40,16 +40,39 @@ async function main() {
|
|
|
40
40
|
const [command, subcommand, ...restArgs] = args;
|
|
41
41
|
try {
|
|
42
42
|
switch (command) {
|
|
43
|
-
case 'setup':
|
|
44
|
-
await setupCmd.setup();
|
|
45
|
-
break;
|
|
46
43
|
case 'auth':
|
|
47
|
-
if (subcommand
|
|
48
|
-
|
|
44
|
+
if (!subcommand || flags.help) {
|
|
45
|
+
info('Usage: myapi auth <subcommand>\n\nSubcommands:\n setup Configure your account\n whoami Show current account\n signup Upgrade anonymous account to registered\n switch Switch between accounts\n config Manage CLI defaults (org_id, domain…)\n install-skills Install the MyAPI skills pack\n api-keys Manage API keys');
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (subcommand === 'setup')
|
|
49
|
+
await setupCmd.setup();
|
|
49
50
|
else if (subcommand === 'whoami')
|
|
50
51
|
await authCmd.whoami();
|
|
52
|
+
else if (subcommand === 'signup')
|
|
53
|
+
await authCmd.signup();
|
|
54
|
+
else if (subcommand === 'switch')
|
|
55
|
+
await authCmd.switchCmd();
|
|
56
|
+
else if (subcommand === 'install-skills') {
|
|
57
|
+
await setupCmd.installSkills();
|
|
58
|
+
success('› Skills installed.');
|
|
59
|
+
}
|
|
60
|
+
else if (subcommand === 'config')
|
|
61
|
+
await configCmd.run(restArgs[0], restArgs.slice(1), flags);
|
|
62
|
+
else if (subcommand === 'api-keys') {
|
|
63
|
+
if (!restArgs[0]) {
|
|
64
|
+
info('Usage: myapi auth api-keys <list|create|revoke>');
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (restArgs[0] === 'create')
|
|
68
|
+
await keysCmd.createNew(flags);
|
|
69
|
+
else if (restArgs[0] === 'list')
|
|
70
|
+
await keysCmd.list(flags);
|
|
71
|
+
else if (restArgs[0] === 'revoke')
|
|
72
|
+
await keysCmd.revoke(restArgs[1], flags);
|
|
73
|
+
}
|
|
51
74
|
else
|
|
52
|
-
info('
|
|
75
|
+
info('Unknown subcommand. Run: myapi auth --help');
|
|
53
76
|
break;
|
|
54
77
|
case 'update':
|
|
55
78
|
await updateCmd.update();
|
|
@@ -102,9 +125,6 @@ async function main() {
|
|
|
102
125
|
else
|
|
103
126
|
printHelp();
|
|
104
127
|
break;
|
|
105
|
-
case 'config':
|
|
106
|
-
await configCmd.run(subcommand, restArgs, flags);
|
|
107
|
-
break;
|
|
108
128
|
case 'domain':
|
|
109
129
|
await domainCmd.run(subcommand, restArgs, flags);
|
|
110
130
|
break;
|
|
@@ -127,6 +147,13 @@ async function main() {
|
|
|
127
147
|
}
|
|
128
148
|
}
|
|
129
149
|
function printHelp() {
|
|
150
|
+
const config = loadConfig();
|
|
151
|
+
const quickStart = config?.api_key
|
|
152
|
+
? `Quick start:
|
|
153
|
+
myapi funnel create
|
|
154
|
+
echo '<h1>Hello!</h1>' | myapi funnel push <funnel_id> /`
|
|
155
|
+
: `Quick start:
|
|
156
|
+
myapi setup`;
|
|
130
157
|
info(`myapi - MyAPI command-line interface
|
|
131
158
|
|
|
132
159
|
Usage: myapi <command> [subcommand] [args]
|
|
@@ -136,18 +163,13 @@ Commands:
|
|
|
136
163
|
keys Manage API keys
|
|
137
164
|
billing Check balance and manage billing
|
|
138
165
|
org Manage organizations
|
|
139
|
-
setup Configure
|
|
140
|
-
config Manage CLI defaults like org_id and domain
|
|
141
|
-
auth Manage authentication (signup, whoami)
|
|
166
|
+
setup Configure account · whoami · signup · config · install-skills
|
|
142
167
|
update Update CLI and skills to the latest version
|
|
143
168
|
domain Manage domain configurations
|
|
144
169
|
funnel Manage headless funnels and pages
|
|
145
170
|
|
|
146
171
|
Run "myapi <command> --help" for subcommand help.
|
|
147
172
|
|
|
148
|
-
|
|
149
|
-
myapi setup
|
|
150
|
-
myapi funnel create
|
|
151
|
-
echo '<h1>Hello!</h1>' | myapi funnel push <funnel_id> /`);
|
|
173
|
+
${quickStart}`);
|
|
152
174
|
}
|
|
153
175
|
main().catch(err => { error(err.message || (typeof err === 'object' ? JSON.stringify(err) : String(err))); });
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-api-hq
|
|
3
|
+
description: >
|
|
4
|
+
Core Identity and Billing hub. Manage auth, organizations (get org_id), and billing (checkout/topup).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyApiHQ Skill
|
|
8
|
+
Root entry point for the ecosystem. All other skills require an `api_key` and often an `org_id` from here.
|
|
9
|
+
|
|
10
|
+
## Platform Conventions
|
|
11
|
+
|
|
12
|
+
### Response Envelope
|
|
13
|
+
Every response across all services is wrapped in:
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"success": true,
|
|
17
|
+
"data": { ... },
|
|
18
|
+
"error": null,
|
|
19
|
+
"meta": { "request_id": "...", "latency_ms": 12, "service": "...", "version": "v1" }
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
On error, `success` is `false`, `data` is `null`, and `error` contains a string error code or object. Always check `success` before reading `data`.
|
|
23
|
+
|
|
24
|
+
### Pagination
|
|
25
|
+
List endpoints accept `?limit=` and `?offset=` and return `total`, `limit`, `offset` in the body.
|
|
26
|
+
|
|
27
|
+
## Authentication & Key Management
|
|
28
|
+
|
|
29
|
+
You need to go to myapihq.com and register an account. Generate an api key and export it to pass it to the agent (as env).
|
|
30
|
+
|
|
31
|
+
### Account Login
|
|
32
|
+
```
|
|
33
|
+
POST /hq/account/login
|
|
34
|
+
{ "email": "...", "password": "..." }
|
|
35
|
+
→ { "data": { "token": "<JWT>" } }
|
|
36
|
+
```
|
|
37
|
+
Errors: `401` wrong password.
|
|
38
|
+
|
|
39
|
+
**Refresh Token:**
|
|
40
|
+
```
|
|
41
|
+
POST /hq/account/refresh
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Google OAuth:**
|
|
45
|
+
- `GET /hq/auth/google/redirect` — Initiates Google OAuth flow.
|
|
46
|
+
- `GET /hq/auth/google/callback` — OAuth callback.
|
|
47
|
+
|
|
48
|
+
### API Key Management
|
|
49
|
+
**Generate Persistent Key:**
|
|
50
|
+
```
|
|
51
|
+
POST /hq/account/create/key
|
|
52
|
+
Authorization: Bearer <JWT from login>
|
|
53
|
+
{ "name": "MyKey" }
|
|
54
|
+
→ { "data": { "api_key": "hq_live_...", "id": "...", "prefix": "..." } }
|
|
55
|
+
```
|
|
56
|
+
Errors: `401` no auth.
|
|
57
|
+
|
|
58
|
+
Use `Authorization: Bearer <api_key>` for all subsequent requests across the entire ecosystem.
|
|
59
|
+
|
|
60
|
+
**List Keys:** `GET /hq/account/keys`
|
|
61
|
+
|
|
62
|
+
**Revoke Key:** `DELETE /hq/account/delete/key/{id}`
|
|
63
|
+
|
|
64
|
+
### Cross-Session Storage
|
|
65
|
+
**Always persist credentials immediately.** Write a `.env` file:
|
|
66
|
+
```
|
|
67
|
+
MYAPI_API_KEY=hq_live_...
|
|
68
|
+
MYAPI_ACCOUNT_ID=...
|
|
69
|
+
```
|
|
70
|
+
Also save to agent memory if your runtime supports it (e.g., Claude Code memory). On every new session, check for credentials before creating a new account.
|
|
71
|
+
|
|
72
|
+
## Organization Management
|
|
73
|
+
**You MUST create an org to get an `org_id` for other APIs.**
|
|
74
|
+
|
|
75
|
+
### Create Org (sync)
|
|
76
|
+
```
|
|
77
|
+
POST /hq/orgs
|
|
78
|
+
{ "name": "Acme Inc" (required), "tagline", "description", "business_sector",
|
|
79
|
+
"logo_url", "favicon_url", "og_image_url",
|
|
80
|
+
"color_palette": { "primary": "#hex", ... },
|
|
81
|
+
"font_family", "imagery_style", "headline", "subheadline", "cta_text",
|
|
82
|
+
"value_propositions": ["..."],
|
|
83
|
+
"social_links": { "twitter": "url", ... },
|
|
84
|
+
"canonical_url", "privacy_policy_url", "cookie_policy_url", "terms_url",
|
|
85
|
+
"gdpr_enabled": false, "default_language": "en", "tracking": {} }
|
|
86
|
+
→ { "data": { "id": "<org_id>", ... } }
|
|
87
|
+
```
|
|
88
|
+
Errors: `400` invalid_json · `422` name_required, invalid_field:color_palette, invalid_field:value_propositions, invalid_field:social_links, invalid_field:tracking · `402` insufficient balance (org creation has a cost on paid plan).
|
|
89
|
+
|
|
90
|
+
### Async Brand Import
|
|
91
|
+
```
|
|
92
|
+
POST /hq/org-imports
|
|
93
|
+
{ "org_id": "<id>" (required), "domain": "example.com" (required), "auto_accept": false }
|
|
94
|
+
→ { "data": { "job_id": "...", "status": "pending" } }
|
|
95
|
+
|
|
96
|
+
GET /hq/org-imports/{job_id}
|
|
97
|
+
→ Poll until status = "awaiting_confirm". Returns brand_preview.
|
|
98
|
+
|
|
99
|
+
POST /hq/org-imports/{job_id}/confirm
|
|
100
|
+
{ ...optional overrides matching POST /hq/orgs payload... }
|
|
101
|
+
→ { "data": { "id": "<org_id>", ... } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Manage Orgs
|
|
105
|
+
- `GET /hq/orgs` — list all orgs.
|
|
106
|
+
- `GET /hq/orgs/{id}` — get org details. Errors: `404` org_not_found.
|
|
107
|
+
- `PATCH /hq/orgs/{id}` — partial update, same fields as create. Errors: `400` invalid_json · `404` org_not_found · `422` invalid_field:*.
|
|
108
|
+
- `DELETE /hq/orgs/{id}` — delete org and cascade. Errors: `404` org_not_found.
|
|
109
|
+
|
|
110
|
+
## Billing
|
|
111
|
+
- **Setup Payment Method:** You need to do this from the myapihq dashboard directly.
|
|
112
|
+
- **Check Balance:** `GET /hq/billing/balance` → `{ "data": { "balance_cents": 1000, "balance_display": "$10.00", "credits_cents": 500, "credits_display": "$5.00", "has_payment_method": true } }`.
|
|
113
|
+
- **Billing History:** `GET /hq/billing/history`
|
|
114
|
+
- **Top Up:** `POST /hq/billing/topup` — `{ "amount_cents": 1000 }` → `{ "data": { "new_balance_cents": 2000, "new_balance_display": "$20.00" } }`.
|
|
115
|
+
|
|
116
|
+
**On 402 from any service:** check balance and top up here before retrying.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-domain-api
|
|
3
|
+
description: >
|
|
4
|
+
Register new domains, check availability and pricing, import existing domains, and manage edge settings. Use this before creating mailboxes or funnels — both require an owned domain.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyDomainAPI Skill
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
1. `GET /domain/orgs/{org_id}/list?filter=all` — lists domains owned by your account. `filter` can be `all`, `unassigned`, or `org` (default).
|
|
11
|
+
2. `GET /domain/orgs/{org_id}/check/available/{domain}` — confirm availability and price.
|
|
12
|
+
3. `POST /domain/orgs/{org_id}/register` with `domain` and optional `years`.
|
|
13
|
+
4. Proceed to `my-email-api` for mailboxes or `my-funnel-api` for a website.
|
|
14
|
+
|
|
15
|
+
DNS is fully managed by the platform — enabling seamless email deliverability, tracking pixel, and edge delivery integration. Manual DNS record management is not exposed.
|
|
16
|
+
|
|
17
|
+
## Dependencies & Backlinks
|
|
18
|
+
- **Auth & Billing:** 401/402 → fall back to `my-api-hq`.
|
|
19
|
+
- **Next Steps:** After registration → `my-email-api` for mailboxes or `my-funnel-api` for a website.
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
`Authorization: Bearer <api_key>` (from `my-api-hq`).
|
|
23
|
+
|
|
24
|
+
## Endpoints
|
|
25
|
+
|
|
26
|
+
### Check Availability
|
|
27
|
+
```
|
|
28
|
+
GET /domain/orgs/{org_id}/check/available/{domain}
|
|
29
|
+
→ { "available": true, "price_cents": 1200 }
|
|
30
|
+
```
|
|
31
|
+
Errors: `400` INVALID_DOMAIN, TLD_NOT_SUPPORTED.
|
|
32
|
+
|
|
33
|
+
### Register Domain
|
|
34
|
+
```
|
|
35
|
+
POST /domain/orgs/{org_id}/register
|
|
36
|
+
{ "domain": "example.com", "years": 1 }
|
|
37
|
+
→ { "domain": "...", "status": "provisioning", "domain_id": "..." }
|
|
38
|
+
```
|
|
39
|
+
Errors: `400` invalid request, INVALID_DOMAIN, TLD_NOT_SUPPORTED · `409` DOMAIN_ALREADY_OWNED, DOMAIN_UNAVAILABLE · `402` INSUFFICIENT_BALANCE (includes `required_cents`) or UPGRADE_REQUIRED (free account) · `403` `already_owned` flag is not permitted.
|
|
40
|
+
|
|
41
|
+
### Import Existing Domain
|
|
42
|
+
```
|
|
43
|
+
POST /domain/orgs/{org_id}/import
|
|
44
|
+
{ "domain": "example.com", "namecheap_api_user": "optional", "namecheap_api_key": "optional" }
|
|
45
|
+
```
|
|
46
|
+
Sets up DNS and email infrastructure automatically. Optionally updates Namecheap NS if credentials are provided.
|
|
47
|
+
Errors: `402` insufficient balance.
|
|
48
|
+
|
|
49
|
+
To use Namecheap automation: go to **Profile > Tools > Namecheap API Access**, generate an API Key, and whitelist the MyAPI-HQ server IP — otherwise the API calls will be rejected.
|
|
50
|
+
|
|
51
|
+
### List & Status
|
|
52
|
+
```
|
|
53
|
+
GET /domain/orgs/{org_id}/list
|
|
54
|
+
GET /domain/orgs/{org_id}/{domain}/status
|
|
55
|
+
```
|
|
56
|
+
Errors (status): `404` DOMAIN_NOT_FOUND.
|
|
57
|
+
|
|
58
|
+
### Assign / Unassign Domain
|
|
59
|
+
```
|
|
60
|
+
POST /domain/orgs/{org_id}/{domain}/assign
|
|
61
|
+
{ "org_id": "<target_org_id>" } // Pass null to unassign
|
|
62
|
+
```
|
|
63
|
+
Associates a domain already in the account with a specific organization, or removes it from its current organization if `org_id` is null.
|
|
64
|
+
Errors: `404` DOMAIN_NOT_FOUND · `422` ORG_NOT_FOUND.
|
|
65
|
+
|
|
66
|
+
### Edge Settings
|
|
67
|
+
|
|
68
|
+
**Update:**
|
|
69
|
+
```
|
|
70
|
+
POST /domain/orgs/{org_id}/{domain}/settings
|
|
71
|
+
{
|
|
72
|
+
"security_level": "essentially_off", // essentially_off | medium | high | under_attack
|
|
73
|
+
"browser_check": "off", // on | off
|
|
74
|
+
"purge_cache": true
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
*To allow AI training bots and crawlers: set `security_level: "essentially_off"` and `browser_check: "off"`.*
|
|
78
|
+
|
|
79
|
+
**Get:**
|
|
80
|
+
```
|
|
81
|
+
GET /domain/orgs/{org_id}/{domain}/settings
|
|
82
|
+
→ { "domain": "...", "security_level": "...", "browser_check": "...", "ai_bots_protection": "disabled", "is_robots_txt_managed": false }
|
|
83
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: my-funnel-api:funnel
|
|
3
|
+
description: >
|
|
4
|
+
A lean CRUD and CDN Publishing API. Manage funnel configurations, push raw HTML pages, and deploy static assets to the edge KV.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MyFunnelAPI Skill
|
|
8
|
+
|
|
9
|
+
## 1. Funnel Management (Authenticated)
|
|
10
|
+
These endpoints manage the database records and structural configuration of funnels.
|
|
11
|
+
|
|
12
|
+
- `GET /funnel/orgs/{org_id}/funnels`
|
|
13
|
+
Lists all funnels for the specified organization.
|
|
14
|
+
- `POST /funnel/orgs/{org_id}/funnels`
|
|
15
|
+
Creates a new funnel entry. Expects basic configuration metadata (name, domain, etc.). Body: `{ "domain": "example.com" }`
|
|
16
|
+
- `GET /funnel/orgs/{org_id}/funnels/{id}`
|
|
17
|
+
Retrieves the metadata and configuration details of a specific funnel.
|
|
18
|
+
- `DELETE /funnel/orgs/{org_id}/funnels/{id}`
|
|
19
|
+
Deletes a funnel from the database and automatically purges all of its preview and published pages from the edge KV cache.
|
|
20
|
+
|
|
21
|
+
## 2. Publishing & Edge Deployment (Authenticated)
|
|
22
|
+
These endpoints interact with the edge KV cache to push HTML/JS content to the edge domains. As soon as you push a page, it is live.
|
|
23
|
+
|
|
24
|
+
- `POST /funnel/orgs/{org_id}/funnels/{id}/push-page`
|
|
25
|
+
Deploys raw HTML to a specific slug on the live funnel (e.g., pushing custom HTML to /contact). Body: `{"slug": "/route", "html": "..."}`.
|
|
26
|
+
- `POST /funnel/orgs/{org_id}/funnels/{id}/verify`
|
|
27
|
+
Pre-publish verification. Validates syntax and structure of raw HTML or an existing page slug.
|
|
28
|
+
|
|
29
|
+
## 3. Public Proxies (Unauthenticated)
|
|
30
|
+
These endpoints are called directly by the end-users' browsers (via the deployed static HTML). They do not require API keys. They are stateless and act as routing proxies to the Webhook API.
|
|
31
|
+
|
|
32
|
+
- `POST /funnel/funnels/{id}/submit/{slug...}`
|
|
33
|
+
The endpoint for HTML form submissions. Validates the JSON payload, returns a 200 OK to the browser, and asynchronously POSTs the data to the organization's matching webhook (or fallback webhook).
|
|
34
|
+
- `POST /funnel/funnels/{id}/event`
|
|
35
|
+
The endpoint for analytics and tracking scripts. Proxies click events, pageviews, and pixel tracking data to the configured webhook endpoints. Includes built-in rate limiting (max 60 req/min per funnel).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "MyAPI command-line interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"prebuild": "node scripts/copy-skills.js",
|
|
12
|
-
"build": "tsc",
|
|
12
|
+
"build": "tsc && rm -rf dist/skills && cp -r src/skills dist/skills",
|
|
13
13
|
"dev": "tsc --watch"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
package/scripts/copy-skills.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copies skills/*/SKILL.md from the repo root into src/skills/ so they get
|
|
3
3
|
// bundled in the npm package. Runs as prebuild.
|
|
4
|
-
import { readdirSync, mkdirSync, copyFileSync, existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { readdirSync, mkdirSync, copyFileSync, existsSync, readFileSync, rmSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
|
|
@@ -15,6 +15,8 @@ if (!existsSync(skillsRoot)) {
|
|
|
15
15
|
process.exit(0);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Always start clean so unpublished skills don't linger.
|
|
19
|
+
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
|
|
18
20
|
mkdirSync(dest, { recursive: true });
|
|
19
21
|
|
|
20
22
|
let copied = 0;
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as readline from 'readline';
|
|
2
2
|
|
|
3
|
-
import { loadConfig, saveConfig } from '../config.js';
|
|
3
|
+
import { loadConfig, saveConfig, addAccount, switchAccount, listAccounts } from '../config.js';
|
|
4
4
|
import { info, success, error } from '../output.js';
|
|
5
5
|
|
|
6
6
|
const API_BASE = process.env.MYAPI_API_BASE ?? 'https://api.myapihq.com';
|
|
@@ -9,6 +9,12 @@ function ask(rl: readline.Interface, q: string): Promise<string> {
|
|
|
9
9
|
return new Promise(resolve => rl.question(q, resolve));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function yn(answer: string, defaultYes = true): boolean {
|
|
13
|
+
const t = answer.trim().toLowerCase();
|
|
14
|
+
if (t === '') return defaultYes;
|
|
15
|
+
return t === 'y' || t === 'yes';
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
async function post(path: string, body: unknown, apiKey?: string): Promise<unknown> {
|
|
13
19
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
14
20
|
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
@@ -36,14 +42,26 @@ async function patch(path: string, body: unknown, apiKey: string): Promise<unkno
|
|
|
36
42
|
// myapi auth signup — upgrade anonymous session to registered account.
|
|
37
43
|
export async function signup() {
|
|
38
44
|
const config = loadConfig();
|
|
39
|
-
if (!config?.api_key) error('Not configured. Run: myapi setup');
|
|
45
|
+
if (!config?.api_key) error('Not configured. Run: myapi auth setup');
|
|
40
46
|
if (!config!.is_anonymous) error('Already registered. Use your existing account.');
|
|
41
47
|
|
|
42
48
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
43
49
|
try {
|
|
44
50
|
const email = (await ask(rl, '› Email? ')).trim();
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
let upgradeOk = true;
|
|
53
|
+
try {
|
|
54
|
+
await patch('/hq/account/upgrade', { email }, config!.api_key);
|
|
55
|
+
} catch (err: any) {
|
|
56
|
+
if (err.message !== 'EMAIL_TAKEN') throw err;
|
|
57
|
+
upgradeOk = false;
|
|
58
|
+
info(`› That email already has an account — accounts cannot be merged.`);
|
|
59
|
+
info(`› Your anonymous account will be kept and you can switch back to it anytime.`);
|
|
60
|
+
const ans = (await ask(rl, '› Sign in and add it as a second account? (Y/n) ')).trim();
|
|
61
|
+
if (!yn(ans)) return;
|
|
62
|
+
await post('/hq/account/send-code', { email });
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
info(`› Sent a code to ${email} · paste it below`);
|
|
48
66
|
|
|
49
67
|
const code = (await ask(rl, '› Code? ')).trim();
|
|
@@ -54,16 +72,32 @@ export async function signup() {
|
|
|
54
72
|
default_funnel: string;
|
|
55
73
|
};
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
if (upgradeOk) {
|
|
76
|
+
// Upgrade: update current account in place.
|
|
77
|
+
saveConfig({
|
|
78
|
+
...config!,
|
|
79
|
+
api_key: data.api_key,
|
|
80
|
+
account_id: data.account_id,
|
|
81
|
+
email,
|
|
82
|
+
default_org: data.default_org || config!.default_org,
|
|
83
|
+
default_funnel: data.default_funnel || config!.default_funnel,
|
|
84
|
+
is_anonymous: false,
|
|
85
|
+
});
|
|
86
|
+
success(`› Welcome! Account upgraded · ${email}`);
|
|
87
|
+
} else {
|
|
88
|
+
// Add as new account and switch to it.
|
|
89
|
+
const idx = addAccount({
|
|
90
|
+
api_key: data.api_key,
|
|
91
|
+
account_id: data.account_id,
|
|
92
|
+
email,
|
|
93
|
+
pin: '',
|
|
94
|
+
default_org: data.default_org,
|
|
95
|
+
default_funnel: data.default_funnel,
|
|
96
|
+
is_anonymous: false,
|
|
97
|
+
});
|
|
98
|
+
success(`› Signed in · ${email} (account #${idx + 1})`);
|
|
99
|
+
info(`› Use "myapi auth switch" to toggle between accounts.`);
|
|
100
|
+
}
|
|
67
101
|
} finally {
|
|
68
102
|
rl.close();
|
|
69
103
|
}
|
|
@@ -72,10 +106,43 @@ export async function signup() {
|
|
|
72
106
|
// myapi auth whoami — show current session info.
|
|
73
107
|
export async function whoami() {
|
|
74
108
|
const config = loadConfig();
|
|
75
|
-
if (!config?.api_key) error('Not configured. Run: myapi setup');
|
|
109
|
+
if (!config?.api_key) error('Not configured. Run: myapi auth setup');
|
|
76
110
|
|
|
111
|
+
if (config!.email) info(`Email: ${config!.email}`);
|
|
77
112
|
info(`Account: ${config!.account_id}`);
|
|
78
113
|
info(`Org: ${config!.default_org ?? '(none)'}`);
|
|
79
114
|
info(`Funnel: ${config!.default_funnel ?? '(none)'}`);
|
|
80
115
|
info(`Type: ${config!.is_anonymous ? 'anonymous' : 'registered'}`);
|
|
81
116
|
}
|
|
117
|
+
|
|
118
|
+
// myapi auth switch — switch between saved accounts.
|
|
119
|
+
export async function switchCmd() {
|
|
120
|
+
const accounts = listAccounts();
|
|
121
|
+
if (accounts.length === 0) error('No accounts configured. Run: myapi auth setup');
|
|
122
|
+
if (accounts.length === 1) {
|
|
123
|
+
info(`Only one account configured: ${accounts[0].email ?? accounts[0].account_id}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
info('Accounts:');
|
|
128
|
+
for (const a of accounts) {
|
|
129
|
+
const label = a.email ?? (a.is_anonymous ? `anonymous · ${a.account_id.slice(0, 8)}` : a.account_id.slice(0, 8));
|
|
130
|
+
const marker = a.active ? ' ◀ active' : '';
|
|
131
|
+
info(` ${a.index + 1}. ${label}${marker}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
135
|
+
try {
|
|
136
|
+
const ans = (await ask(rl, `› Switch to account (1-${accounts.length})? `)).trim();
|
|
137
|
+
const idx = parseInt(ans, 10) - 1;
|
|
138
|
+
if (isNaN(idx) || idx < 0 || idx >= accounts.length) {
|
|
139
|
+
error('Invalid selection.');
|
|
140
|
+
}
|
|
141
|
+
if (switchAccount(idx)) {
|
|
142
|
+
const a = accounts[idx];
|
|
143
|
+
success(`› Switched to ${a.email ?? a.account_id}`);
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
rl.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/commands/config.ts
CHANGED
|
@@ -12,6 +12,17 @@ export async function setOrg(id: string, flags: Record<string, string | boolean>
|
|
|
12
12
|
success(`Default organization set to: ${id}`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export async function setFunnel(id: string, flags: Record<string, string | boolean>) {
|
|
16
|
+
if (!id || flags.help) {
|
|
17
|
+
info("Usage: myapi config set-funnel <id>\n\nSets a default funnel ID for subsequent commands.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const config = requireConfig();
|
|
21
|
+
config.default_funnel = id;
|
|
22
|
+
saveConfig(config);
|
|
23
|
+
success(`Default funnel set to: ${id}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
export async function setDomain(domain: string, flags: Record<string, string | boolean>) {
|
|
16
27
|
if (!domain || flags.help) {
|
|
17
28
|
info("Usage: myapi config set-domain <domain>\n\nSets a default domain for subsequent commands.");
|
|
@@ -29,18 +40,20 @@ export async function view(flags: Record<string, string | boolean>) {
|
|
|
29
40
|
return;
|
|
30
41
|
}
|
|
31
42
|
const config = requireConfig();
|
|
32
|
-
info(`Default Org:
|
|
43
|
+
info(`Default Org: ${config.default_org || 'Not set'}`);
|
|
44
|
+
info(`Default Funnel: ${config.default_funnel || 'Not set'}`);
|
|
33
45
|
info(`Default Domain: ${config.default_domain || 'Not set'}`);
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
export async function run(subcommand: string | undefined, args: string[], flags: Record<string, string | boolean>) {
|
|
37
49
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
38
|
-
info('Usage: myapi config <subcommand>\n\nSubcommands:\n view View current config\n set-org Set default organization\n set-domain Set default domain');
|
|
50
|
+
info('Usage: myapi config <subcommand>\n\nSubcommands:\n view View current config\n set-org Set default organization\n set-funnel Set default funnel\n set-domain Set default domain');
|
|
39
51
|
return;
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
if (subcommand === 'view') await view(flags);
|
|
43
55
|
else if (subcommand === 'set-org') await setOrg(args[0], flags);
|
|
56
|
+
else if (subcommand === 'set-funnel') await setFunnel(args[0], flags);
|
|
44
57
|
else if (subcommand === 'set-domain') await setDomain(args[0], flags);
|
|
45
58
|
else error(`Unknown subcommand: ${subcommand}. Run "myapi config --help" for a list of valid subcommands.`);
|
|
46
59
|
}
|
package/src/commands/funnel.ts
CHANGED
|
@@ -44,9 +44,11 @@ export async function del(id: string, flags: Record<string, string | boolean>) {
|
|
|
44
44
|
export async function push(id: string, slug: string, flags: Record<string, string | boolean>) {
|
|
45
45
|
const config = requireConfig();
|
|
46
46
|
const orgId = (flags.org as string) || config.default_org;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const funnelId = id || config.default_funnel;
|
|
48
|
+
const finalSlug = slug || (flags.slug as string) || '/';
|
|
49
|
+
|
|
50
|
+
if (!orgId || !funnelId) {
|
|
51
|
+
error("Missing required arguments.\nUsage: myapi funnel push [funnel_id] [slug] < index.html\n(Defaults to your configured funnel and slug '/' when omitted)");
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
const html = await new Promise<string>((resolve, reject) => {
|
|
@@ -59,14 +61,14 @@ export async function push(id: string, slug: string, flags: Record<string, strin
|
|
|
59
61
|
|
|
60
62
|
if (!html) error("No HTML provided via stdin");
|
|
61
63
|
|
|
62
|
-
const result = await sdkFunnel.pushFunnelPage(config.api_key, orgId,
|
|
63
|
-
success(`Pushed page to ${
|
|
64
|
+
const result = await sdkFunnel.pushFunnelPage(config.api_key, orgId, funnelId, { slug: finalSlug, html });
|
|
65
|
+
success(`Pushed page to ${finalSlug}`);
|
|
64
66
|
if (result?.url) {
|
|
65
67
|
info(`Preview: ${result.url}`);
|
|
66
68
|
} else {
|
|
67
69
|
const org = await hq.getOrg(config.api_key, orgId);
|
|
68
70
|
if (org.preview_subdomain) {
|
|
69
|
-
info(`Preview: https://${org.preview_subdomain}.makeautonomous.com
|
|
71
|
+
info(`Preview: https://${org.preview_subdomain}.makeautonomous.com${finalSlug}`);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
@@ -92,7 +94,7 @@ export async function run(subcommand: string | undefined, args: string[], flags:
|
|
|
92
94
|
else if (subcommand === 'create') info('Usage: myapi funnel create --org <id>');
|
|
93
95
|
else if (subcommand === 'get') info('Usage: myapi funnel get <id> --org <id>');
|
|
94
96
|
else if (subcommand === 'delete') info('Usage: myapi funnel delete <id> --org <id>');
|
|
95
|
-
else if (subcommand === 'push') info('Usage: myapi funnel push
|
|
97
|
+
else if (subcommand === 'push') info('Usage: myapi funnel push [funnel_id] [slug] < index.html\n\nPushes HTML from stdin. Uses your default funnel and slug "/" when omitted.');
|
|
96
98
|
else if (subcommand === 'verify') info('Usage: myapi funnel verify <id> --org <id> [--slug <slug>]\n\nVerifies syntax and structure before pushing.');
|
|
97
99
|
return;
|
|
98
100
|
}
|