@zanfia/cli 0.2.0 → 0.3.10
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/README.md +33 -33
- package/dist/index.js +271 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
# @zanfia/cli (`zanfia`)
|
|
2
|
-
|
|
3
|
-
Thin HTTP client over the Zanfia Public API.
|
|
4
|
-
|
|
5
|
-
> **Status:**
|
|
6
|
-
|
|
7
|
-
## Local usage
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# from the monorepo root, after `pnpm install`
|
|
11
|
-
pnpm --filter @zanfia/cli dev products list
|
|
12
|
-
|
|
13
|
-
# or run a one-off
|
|
14
|
-
pnpm --filter @zanfia/cli dev auth whoami
|
|
15
|
-
|
|
16
|
-
# global flags (--json, --verbose, --api-key) must come BEFORE the subcommand:
|
|
17
|
-
pnpm --filter @zanfia/cli dev --json products list
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Auth resolution order
|
|
21
|
-
|
|
22
|
-
1. `--api-key` flag
|
|
23
|
-
2. `ZANFIA_API_KEY` environment variable
|
|
24
|
-
3. Stored key in `~/.config/zanfia/credentials.json`
|
|
25
|
-
4. Prompt to run `zanfia auth set-key`
|
|
26
|
-
|
|
27
|
-
## Environment
|
|
28
|
-
|
|
29
|
-
| Variable
|
|
30
|
-
|
|
31
|
-
| `ZANFIA_API_URL`
|
|
32
|
-
| `ZANFIA_API_KEY`
|
|
33
|
-
| `ZANFIA_CLI_MOCK` | Force mock mode even if `ZANFIA_API_URL` is set. |
|
|
1
|
+
# @zanfia/cli (`zanfia`)
|
|
2
|
+
|
|
3
|
+
Thin HTTP client over the Zanfia Public API.
|
|
4
|
+
|
|
5
|
+
> **Status:** published to npm as `@zanfia/cli` (bin `zanfia`), talking to the production Workspace API by default (`ZANFIA_CLI_TARGET=prod` bakes the URLs at build time). Set `ZANFIA_API_URL` to target another environment; local `tsx` runs without baked defaults fall back to the in-memory mock.
|
|
6
|
+
|
|
7
|
+
## Local usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# from the monorepo root, after `pnpm install`
|
|
11
|
+
pnpm --filter @zanfia/cli dev products list
|
|
12
|
+
|
|
13
|
+
# or run a one-off
|
|
14
|
+
pnpm --filter @zanfia/cli dev auth whoami
|
|
15
|
+
|
|
16
|
+
# global flags (--json, --verbose, --api-key) must come BEFORE the subcommand:
|
|
17
|
+
pnpm --filter @zanfia/cli dev --json products list
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Auth resolution order
|
|
21
|
+
|
|
22
|
+
1. `--api-key` flag
|
|
23
|
+
2. `ZANFIA_API_KEY` environment variable
|
|
24
|
+
3. Stored key in `~/.config/zanfia/credentials.json`
|
|
25
|
+
4. Prompt to run `zanfia auth set-key`
|
|
26
|
+
|
|
27
|
+
## Environment
|
|
28
|
+
|
|
29
|
+
| Variable | Purpose |
|
|
30
|
+
| ----------------- | ------------------------------------------------ |
|
|
31
|
+
| `ZANFIA_API_URL` | Public API base URL. Unset → mock mode. |
|
|
32
|
+
| `ZANFIA_API_KEY` | API key (overrides stored credentials). |
|
|
33
|
+
| `ZANFIA_CLI_MOCK` | Force mock mode even if `ZANFIA_API_URL` is set. |
|
package/dist/index.js
CHANGED
|
@@ -6908,7 +6908,8 @@ var CONFIG_DIR = join(homedir(), ".config", "zanfia");
|
|
|
6908
6908
|
var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
|
|
6909
6909
|
var orUndefined = (value) => value.length > 0 ? value : void 0;
|
|
6910
6910
|
var backendDefaults = {
|
|
6911
|
-
apiUrl: true ? orUndefined("https://
|
|
6911
|
+
apiUrl: true ? orUndefined("https://api.zanfia.com") : void 0,
|
|
6912
|
+
communityApiUrl: true ? orUndefined("https://api.zanfia.com/community") : void 0,
|
|
6912
6913
|
loginUrl: true ? orUndefined("https://app.zanfia.com/cli/auth") : void 0,
|
|
6913
6914
|
exchangeUrl: true ? orUndefined("https://europe-central2-mailingr-54736.cloudfunctions.net/users-v1/cli/auth/exchange") : void 0,
|
|
6914
6915
|
memberExchangeUrl: true ? orUndefined("https://europe-central2-mailingr-54736.cloudfunctions.net/users-v1/member/cli/auth/exchange") : void 0,
|
|
@@ -6974,7 +6975,7 @@ function resolveCommunityApiUrl() {
|
|
|
6974
6975
|
if (!apiUrl) return null;
|
|
6975
6976
|
if (/\/api-community\/?$/.test(apiUrl)) return apiUrl.replace(/\/$/, "");
|
|
6976
6977
|
if (/\/api-v1\/?$/.test(apiUrl)) return apiUrl.replace(/\/api-v1\/?$/, "/api-community");
|
|
6977
|
-
return null;
|
|
6978
|
+
return backendDefaults.communityApiUrl ?? null;
|
|
6978
6979
|
}
|
|
6979
6980
|
function isMockMode() {
|
|
6980
6981
|
if (process.env.ZANFIA_CLI_MOCK === "1" || process.env.ZANFIA_CLI_MOCK === "true") {
|
|
@@ -7086,6 +7087,11 @@ var clients = [
|
|
|
7086
7087
|
totalSpentCents: 5800,
|
|
7087
7088
|
activeProducts: 1,
|
|
7088
7089
|
language: "en",
|
|
7090
|
+
discordId: null,
|
|
7091
|
+
githubId: null,
|
|
7092
|
+
tagIds: [],
|
|
7093
|
+
emailSubscriptionStatus: null,
|
|
7094
|
+
attribution: null,
|
|
7089
7095
|
createdAt: "2026-03-02T09:00:00.000Z"
|
|
7090
7096
|
},
|
|
7091
7097
|
{
|
|
@@ -7098,6 +7104,11 @@ var clients = [
|
|
|
7098
7104
|
totalSpentCents: 0,
|
|
7099
7105
|
activeProducts: 0,
|
|
7100
7106
|
language: null,
|
|
7107
|
+
discordId: null,
|
|
7108
|
+
githubId: null,
|
|
7109
|
+
tagIds: [],
|
|
7110
|
+
emailSubscriptionStatus: null,
|
|
7111
|
+
attribution: null,
|
|
7101
7112
|
createdAt: "2026-03-05T09:00:00.000Z"
|
|
7102
7113
|
}
|
|
7103
7114
|
];
|
|
@@ -7324,6 +7335,11 @@ var mockBackend = {
|
|
|
7324
7335
|
totalSpentCents: 0,
|
|
7325
7336
|
activeProducts: 0,
|
|
7326
7337
|
language: input.language ?? null,
|
|
7338
|
+
discordId: null,
|
|
7339
|
+
githubId: null,
|
|
7340
|
+
tagIds: [],
|
|
7341
|
+
emailSubscriptionStatus: null,
|
|
7342
|
+
attribution: null,
|
|
7327
7343
|
createdAt: now()
|
|
7328
7344
|
});
|
|
7329
7345
|
return { clientId: id, created: true, createdUser: true };
|
|
@@ -7786,6 +7802,9 @@ function createMockClient() {
|
|
|
7786
7802
|
async updateCourseLesson(_productId, lessonId, input) {
|
|
7787
7803
|
return mockBackend.updateCourseLesson(lessonId, input);
|
|
7788
7804
|
},
|
|
7805
|
+
async setCourseLessonThumbnail() {
|
|
7806
|
+
throw new ApiError(501, "lesson thumbnails are not available in mock mode");
|
|
7807
|
+
},
|
|
7789
7808
|
async addCourseModule(_productId, input) {
|
|
7790
7809
|
return mockBackend.addCourseModule(input);
|
|
7791
7810
|
},
|
|
@@ -8554,6 +8573,11 @@ function createHttpClient(config) {
|
|
|
8554
8573
|
`/products/${encodeURIComponent(productId)}/course/lessons/${encodeURIComponent(lessonId)}`,
|
|
8555
8574
|
input
|
|
8556
8575
|
),
|
|
8576
|
+
setCourseLessonThumbnail: (productId, lessonId, input) => request(
|
|
8577
|
+
"PUT",
|
|
8578
|
+
`/products/${encodeURIComponent(productId)}/course/lessons/${encodeURIComponent(lessonId)}/thumbnail`,
|
|
8579
|
+
input
|
|
8580
|
+
),
|
|
8557
8581
|
addCourseModule: (productId, input) => request("POST", `/products/${encodeURIComponent(productId)}/course/modules`, input),
|
|
8558
8582
|
updateCourseModule: (productId, moduleId, input) => request(
|
|
8559
8583
|
"PATCH",
|
|
@@ -9599,38 +9623,9 @@ function registerLogoutCommand(auth, clear) {
|
|
|
9599
9623
|
|
|
9600
9624
|
// src/commands/auth/register.ts
|
|
9601
9625
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
9626
|
+
|
|
9627
|
+
// src/utils/httpJson.ts
|
|
9602
9628
|
import { hostname as hostname2 } from "os";
|
|
9603
|
-
import { createInterface } from "readline/promises";
|
|
9604
|
-
var MAX_CODE_ATTEMPTS = 5;
|
|
9605
|
-
var MIN_PASSWORD_LENGTH = 8;
|
|
9606
|
-
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
9607
|
-
var CTRL_C = String.fromCharCode(3);
|
|
9608
|
-
var DEL = String.fromCharCode(127);
|
|
9609
|
-
function resolveRegisterUrls(explicitApiUrl) {
|
|
9610
|
-
const exchangeUrl = process.env.ZANFIA_EXCHANGE_URL ?? backendDefaults.exchangeUrl;
|
|
9611
|
-
let usersUrl = process.env.ZANFIA_USERS_URL ?? backendDefaults.usersUrl;
|
|
9612
|
-
if (!usersUrl && exchangeUrl?.includes("/cli/auth/exchange")) {
|
|
9613
|
-
usersUrl = exchangeUrl.replace(/\/cli\/auth\/exchange\/?$/, "");
|
|
9614
|
-
}
|
|
9615
|
-
if (!usersUrl) {
|
|
9616
|
-
throw new Error(
|
|
9617
|
-
"ZANFIA_USERS_URL is not set. Set it to the users-service base URL (e.g. https://europe-central2-<project>.cloudfunctions.net/users-v1)."
|
|
9618
|
-
);
|
|
9619
|
-
}
|
|
9620
|
-
usersUrl = usersUrl.replace(/\/$/, "");
|
|
9621
|
-
const firebaseApiKey = process.env.ZANFIA_FIREBASE_API_KEY ?? backendDefaults.firebaseApiKey;
|
|
9622
|
-
if (!firebaseApiKey) {
|
|
9623
|
-
throw new Error(
|
|
9624
|
-
"ZANFIA_FIREBASE_API_KEY is not set. Set it to the Firebase web API key of the target project (public client key, not a secret)."
|
|
9625
|
-
);
|
|
9626
|
-
}
|
|
9627
|
-
return {
|
|
9628
|
-
usersUrl,
|
|
9629
|
-
exchangeUrl: exchangeUrl ?? `${usersUrl}/cli/auth/exchange`,
|
|
9630
|
-
firebaseApiKey,
|
|
9631
|
-
apiUrl: explicitApiUrl ?? process.env.ZANFIA_API_URL ?? backendDefaults.apiUrl
|
|
9632
|
-
};
|
|
9633
|
-
}
|
|
9634
9629
|
async function postJson2(label, url, body, options = {}) {
|
|
9635
9630
|
if (options.verbose) process.stderr.write(`\u2192 POST ${url}
|
|
9636
9631
|
`);
|
|
@@ -9662,6 +9657,19 @@ function extractErrorMessage(text, fallback) {
|
|
|
9662
9657
|
}
|
|
9663
9658
|
return text || fallback;
|
|
9664
9659
|
}
|
|
9660
|
+
function safeHostname2() {
|
|
9661
|
+
try {
|
|
9662
|
+
const h = hostname2();
|
|
9663
|
+
return h && h.length > 0 ? h : "unknown";
|
|
9664
|
+
} catch {
|
|
9665
|
+
return "unknown";
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9668
|
+
|
|
9669
|
+
// src/utils/prompt.ts
|
|
9670
|
+
import { createInterface } from "readline/promises";
|
|
9671
|
+
var CTRL_C = String.fromCharCode(3);
|
|
9672
|
+
var DEL = String.fromCharCode(127);
|
|
9665
9673
|
async function promptLine(question) {
|
|
9666
9674
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9667
9675
|
try {
|
|
@@ -9703,13 +9711,35 @@ function promptHidden(question) {
|
|
|
9703
9711
|
stdin.on("data", onData);
|
|
9704
9712
|
});
|
|
9705
9713
|
}
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9714
|
+
|
|
9715
|
+
// src/commands/auth/register.ts
|
|
9716
|
+
var MAX_CODE_ATTEMPTS = 5;
|
|
9717
|
+
var MIN_PASSWORD_LENGTH = 8;
|
|
9718
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
9719
|
+
function resolveRegisterUrls(explicitApiUrl) {
|
|
9720
|
+
const exchangeUrl = process.env.ZANFIA_EXCHANGE_URL ?? backendDefaults.exchangeUrl;
|
|
9721
|
+
let usersUrl = process.env.ZANFIA_USERS_URL ?? backendDefaults.usersUrl;
|
|
9722
|
+
if (!usersUrl && exchangeUrl?.includes("/cli/auth/exchange")) {
|
|
9723
|
+
usersUrl = exchangeUrl.replace(/\/cli\/auth\/exchange\/?$/, "");
|
|
9724
|
+
}
|
|
9725
|
+
if (!usersUrl) {
|
|
9726
|
+
throw new Error(
|
|
9727
|
+
"ZANFIA_USERS_URL is not set. Set it to the users-service base URL (e.g. https://europe-central2-<project>.cloudfunctions.net/users-v1)."
|
|
9728
|
+
);
|
|
9729
|
+
}
|
|
9730
|
+
usersUrl = usersUrl.replace(/\/$/, "");
|
|
9731
|
+
const firebaseApiKey = process.env.ZANFIA_FIREBASE_API_KEY ?? backendDefaults.firebaseApiKey;
|
|
9732
|
+
if (!firebaseApiKey) {
|
|
9733
|
+
throw new Error(
|
|
9734
|
+
"ZANFIA_FIREBASE_API_KEY is not set. Set it to the Firebase web API key of the target project (public client key, not a secret)."
|
|
9735
|
+
);
|
|
9712
9736
|
}
|
|
9737
|
+
return {
|
|
9738
|
+
usersUrl,
|
|
9739
|
+
exchangeUrl: exchangeUrl ?? `${usersUrl}/cli/auth/exchange`,
|
|
9740
|
+
firebaseApiKey,
|
|
9741
|
+
apiUrl: explicitApiUrl ?? process.env.ZANFIA_API_URL ?? backendDefaults.apiUrl
|
|
9742
|
+
};
|
|
9713
9743
|
}
|
|
9714
9744
|
function resolveTimezone() {
|
|
9715
9745
|
try {
|
|
@@ -12459,6 +12489,31 @@ function registerCoursesCommands(program3) {
|
|
|
12459
12489
|
});
|
|
12460
12490
|
}
|
|
12461
12491
|
);
|
|
12492
|
+
lesson.command("set-thumbnail").description(
|
|
12493
|
+
"Set or clear the lesson thumbnail; Bunny video lessons also get it as the player thumbnail"
|
|
12494
|
+
).argument("<productId>", "course product id").argument("<lessonId>", "lesson id").option("--media <mediaId>", "media-library IMAGE id (from `media upload`)").option("--url <httpsUrl>", "already-hosted https image URL").option("--clear", "remove the thumbnail (video lessons fall back to the generated frame)").action(
|
|
12495
|
+
async (productId, lessonId, options, command) => {
|
|
12496
|
+
const flags = getGlobalFlags(command);
|
|
12497
|
+
const picked = [options.media, options.url, options.clear].filter(
|
|
12498
|
+
(value) => value !== void 0 && value !== false
|
|
12499
|
+
);
|
|
12500
|
+
if (picked.length !== 1) {
|
|
12501
|
+
throw new Error("Pass exactly one of --media <mediaId>, --url <httpsUrl>, --clear.");
|
|
12502
|
+
}
|
|
12503
|
+
const result = await clientFor2(flags).setCourseLessonThumbnail(productId, lessonId, {
|
|
12504
|
+
...options.media !== void 0 ? { mediaId: options.media } : {},
|
|
12505
|
+
...options.url !== void 0 ? { thumbnailUrl: options.url } : {},
|
|
12506
|
+
...options.clear ? { thumbnailUrl: null } : {}
|
|
12507
|
+
});
|
|
12508
|
+
emit(flags, result, () => {
|
|
12509
|
+
process.stdout.write(
|
|
12510
|
+
result.thumbnailUrl ? `Set thumbnail for lesson ${result.lessonId}: ${result.thumbnailUrl}
|
|
12511
|
+
` : `Cleared thumbnail for lesson ${result.lessonId}
|
|
12512
|
+
`
|
|
12513
|
+
);
|
|
12514
|
+
});
|
|
12515
|
+
}
|
|
12516
|
+
);
|
|
12462
12517
|
lesson.command("update").description("Update a lesson (title, description, status, Markdown content)").argument("<productId>", "course product id").argument("<lessonId>", "lesson id").option("--title <title>", "new lesson title").option("--description <text>", "new lesson description").option("--status <status>", `new status (${LESSON_STATUSES.join("|")})`).option("--content <markdown>", "new lesson body (Markdown)").option("--content-file <path>", "read the new lesson body (Markdown) from a file").action(
|
|
12463
12518
|
async (productId, lessonId, options, command) => {
|
|
12464
12519
|
const flags = getGlobalFlags(command);
|
|
@@ -13341,6 +13396,145 @@ function registerFunnelsCommands(program3) {
|
|
|
13341
13396
|
);
|
|
13342
13397
|
}
|
|
13343
13398
|
|
|
13399
|
+
// src/commands/join/index.ts
|
|
13400
|
+
var MAX_CODE_ATTEMPTS2 = 5;
|
|
13401
|
+
var HANDLE_REGEX = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
13402
|
+
var EMAIL_REGEX2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
13403
|
+
var AGENT_DOCS_URL = "https://zanfia.com/help/developer-cli/agents";
|
|
13404
|
+
function joinUrl(base, handle, step) {
|
|
13405
|
+
return `${base.replace(/\/$/, "")}/v1/join/${encodeURIComponent(handle)}/${step}`;
|
|
13406
|
+
}
|
|
13407
|
+
async function runJoin(handleInput, options) {
|
|
13408
|
+
const handle = handleInput.trim().toLowerCase();
|
|
13409
|
+
if (!HANDLE_REGEX.test(handle)) {
|
|
13410
|
+
throw new Error(
|
|
13411
|
+
"Community handle must be lowercase letters, digits and dashes (e.g. crew-of-one)."
|
|
13412
|
+
);
|
|
13413
|
+
}
|
|
13414
|
+
const communityApiUrl = resolveCommunityApiUrl();
|
|
13415
|
+
if (!communityApiUrl) {
|
|
13416
|
+
throw new Error(
|
|
13417
|
+
"No Community API URL configured. Set ZANFIA_COMMUNITY_API_URL (this build has no baked backend)."
|
|
13418
|
+
);
|
|
13419
|
+
}
|
|
13420
|
+
const verbose = options.verbose === true;
|
|
13421
|
+
const email = (options.email ?? await promptLine("Email: ")).trim().toLowerCase();
|
|
13422
|
+
if (!EMAIL_REGEX2.test(email)) {
|
|
13423
|
+
throw new Error("Please enter a valid email address.");
|
|
13424
|
+
}
|
|
13425
|
+
const started = await postJson2(
|
|
13426
|
+
"Join",
|
|
13427
|
+
joinUrl(communityApiUrl, handle, "start"),
|
|
13428
|
+
{ email, lang: "en" },
|
|
13429
|
+
{ verbose }
|
|
13430
|
+
);
|
|
13431
|
+
if (!options.json) {
|
|
13432
|
+
process.stdout.write(
|
|
13433
|
+
`Joining ${started.communityName} (${started.workspaceName}) \u2014 a 6-digit code was sent to ${email}.
|
|
13434
|
+
`
|
|
13435
|
+
);
|
|
13436
|
+
}
|
|
13437
|
+
const firstName = options.name ?? (options.json ? "" : await promptLine("Your name (optional): "));
|
|
13438
|
+
const hostname3 = safeHostname2();
|
|
13439
|
+
let completed = null;
|
|
13440
|
+
for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS2 && !completed; attempt += 1) {
|
|
13441
|
+
const code = options.code ?? (await promptLine("Code from the email (or 'r' to resend): ")).trim();
|
|
13442
|
+
if (code.toLowerCase() === "r") {
|
|
13443
|
+
await postJson2(
|
|
13444
|
+
"Resend",
|
|
13445
|
+
joinUrl(communityApiUrl, handle, "start"),
|
|
13446
|
+
{ email, lang: "en" },
|
|
13447
|
+
{ verbose }
|
|
13448
|
+
);
|
|
13449
|
+
process.stdout.write("Code re-sent.\n");
|
|
13450
|
+
attempt -= 1;
|
|
13451
|
+
continue;
|
|
13452
|
+
}
|
|
13453
|
+
try {
|
|
13454
|
+
completed = await postJson2(
|
|
13455
|
+
"Join",
|
|
13456
|
+
joinUrl(communityApiUrl, handle, "complete"),
|
|
13457
|
+
{ email, code, firstName, hostname: hostname3 },
|
|
13458
|
+
{ verbose }
|
|
13459
|
+
);
|
|
13460
|
+
} catch (err) {
|
|
13461
|
+
if (options.code || attempt === MAX_CODE_ATTEMPTS2) throw err;
|
|
13462
|
+
process.stdout.write(`${err instanceof Error ? err.message : String(err)}
|
|
13463
|
+
`);
|
|
13464
|
+
}
|
|
13465
|
+
}
|
|
13466
|
+
if (!completed) {
|
|
13467
|
+
throw new Error("Could not verify the code.");
|
|
13468
|
+
}
|
|
13469
|
+
const path2 = writeCredentials({
|
|
13470
|
+
apiKey: completed.token,
|
|
13471
|
+
apiUrl: communityApiUrl,
|
|
13472
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13473
|
+
source: "oauth",
|
|
13474
|
+
expiresAt: completed.expiresAt,
|
|
13475
|
+
userEmail: completed.userEmail,
|
|
13476
|
+
workspaceId: completed.workspaceId,
|
|
13477
|
+
workspaceName: completed.workspaceName,
|
|
13478
|
+
kind: "member"
|
|
13479
|
+
});
|
|
13480
|
+
if (options.json) {
|
|
13481
|
+
process.stdout.write(
|
|
13482
|
+
`${JSON.stringify(
|
|
13483
|
+
{
|
|
13484
|
+
joined: true,
|
|
13485
|
+
alreadyMember: completed.alreadyMember,
|
|
13486
|
+
communityId: completed.communityId,
|
|
13487
|
+
communityName: completed.communityName,
|
|
13488
|
+
workspaceId: completed.workspaceId,
|
|
13489
|
+
workspaceName: completed.workspaceName,
|
|
13490
|
+
introChannelId: completed.introChannelId,
|
|
13491
|
+
userEmail: completed.userEmail,
|
|
13492
|
+
expiresAt: completed.expiresAt,
|
|
13493
|
+
credentialsPath: path2
|
|
13494
|
+
},
|
|
13495
|
+
null,
|
|
13496
|
+
2
|
|
13497
|
+
)}
|
|
13498
|
+
`
|
|
13499
|
+
);
|
|
13500
|
+
return;
|
|
13501
|
+
}
|
|
13502
|
+
const intro = completed.introChannelId ? ` zanfia communities post create ${completed.communityId} ${completed.introChannelId} --content "Hi, I'm <your agent>. I ..."
|
|
13503
|
+
` : "";
|
|
13504
|
+
process.stdout.write(
|
|
13505
|
+
`
|
|
13506
|
+
${completed.alreadyMember ? "Welcome back to" : "You are in:"} ${completed.communityName}.
|
|
13507
|
+
Member key saved to ${path2} (expires ${completed.expiresAt.slice(0, 10)}).
|
|
13508
|
+
|
|
13509
|
+
Next:
|
|
13510
|
+
zanfia communities list
|
|
13511
|
+
zanfia communities channels ${completed.communityId}
|
|
13512
|
+
` + (intro ? `
|
|
13513
|
+
Introduce your agent:
|
|
13514
|
+
${intro}` : "") + `
|
|
13515
|
+
Plug in your agent: ${AGENT_DOCS_URL}
|
|
13516
|
+
`
|
|
13517
|
+
);
|
|
13518
|
+
}
|
|
13519
|
+
function registerJoinCommand(program3) {
|
|
13520
|
+
program3.command("join <handle>").description("Join a public community from the terminal (emailed code) and get a member key").option("--email <email>", "email to join with (prompted when omitted)").option("--name <name>", "your display name (prompted when omitted)").option("--code <code>", "the 6-digit code from the email (prompted when omitted)").addHelpText(
|
|
13521
|
+
"after",
|
|
13522
|
+
`
|
|
13523
|
+
Examples:
|
|
13524
|
+
zanfia join crew-of-one
|
|
13525
|
+
zanfia join crew-of-one --email me@example.com --name "Sam" --code 123456
|
|
13526
|
+
|
|
13527
|
+
The key is a MEMBER key: it reaches the Community API for that creator's
|
|
13528
|
+
communities only (read/post/comment/chat), never your own workspace. It is
|
|
13529
|
+
saved to ~/.config/zanfia/credentials.json and expires in 90 days \u2014 run
|
|
13530
|
+
join again to refresh it.
|
|
13531
|
+
`
|
|
13532
|
+
).action(async (handle, options, command) => {
|
|
13533
|
+
const globals = command.optsWithGlobals();
|
|
13534
|
+
await runJoin(handle, { ...options, json: globals.json, verbose: globals.verbose });
|
|
13535
|
+
});
|
|
13536
|
+
}
|
|
13537
|
+
|
|
13344
13538
|
// src/commands/media/index.ts
|
|
13345
13539
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
13346
13540
|
import { readFile, stat as stat3 } from "fs/promises";
|
|
@@ -17025,6 +17219,7 @@ function buildPriceInput(options) {
|
|
|
17025
17219
|
compareAtAmountCents: options.compareAt ? parsePositiveInt7(options.compareAt, "--compare-at") : void 0,
|
|
17026
17220
|
accessInterval: options.accessInterval ? parseChoice3(options.accessInterval, PRICE_INTERVALS, "--access-interval") : void 0,
|
|
17027
17221
|
accessIntervalCount: options.accessCount ? parsePositiveInt7(options.accessCount, "--access-count") : void 0,
|
|
17222
|
+
accessUntil: options.accessUntil || void 0,
|
|
17028
17223
|
planId: options.plan
|
|
17029
17224
|
};
|
|
17030
17225
|
}
|
|
@@ -17035,6 +17230,7 @@ var PRICE_PLACEMENT_CHOICES = [
|
|
|
17035
17230
|
"belowDescription",
|
|
17036
17231
|
"aboveDescription"
|
|
17037
17232
|
];
|
|
17233
|
+
var PLAN_INCLUDES_LAYOUT_CHOICES = ["inline", "list"];
|
|
17038
17234
|
var JSON_PATCH_REJECTED_KEYS2 = [
|
|
17039
17235
|
"active",
|
|
17040
17236
|
"archived",
|
|
@@ -17084,8 +17280,22 @@ function buildOfferUpdateInput(options) {
|
|
|
17084
17280
|
options.checkoutMediaWidth === void 0 ? void 0 : options.checkoutMediaWidth.trim().toLowerCase() === "none" ? null : parsePositiveInt7(options.checkoutMediaWidth, "--checkout-media-width")
|
|
17085
17281
|
);
|
|
17086
17282
|
set("showTestimonials", bool(options.showTestimonials, "--show-testimonials"));
|
|
17283
|
+
set("showPlanIncludes", bool(options.showPlanIncludes, "--show-plan-includes"));
|
|
17284
|
+
set(
|
|
17285
|
+
"planIncludesLayout",
|
|
17286
|
+
options.planIncludesLayout === void 0 ? void 0 : parseChoice3(
|
|
17287
|
+
options.planIncludesLayout,
|
|
17288
|
+
PLAN_INCLUDES_LAYOUT_CHOICES,
|
|
17289
|
+
"--plan-includes-layout"
|
|
17290
|
+
)
|
|
17291
|
+
);
|
|
17087
17292
|
set("invoiceAsOption", bool(options.invoiceAsOption, "--invoice-as-option"));
|
|
17088
17293
|
set("hideCountryField", bool(options.hideCountryField, "--hide-country-field"));
|
|
17294
|
+
set(
|
|
17295
|
+
"fixedCountry",
|
|
17296
|
+
options.fixedCountry === void 0 ? void 0 : options.fixedCountry === "none" ? null : options.fixedCountry.toUpperCase()
|
|
17297
|
+
);
|
|
17298
|
+
set("showFixedCountry", bool(options.showFixedCountry, "--show-fixed-country"));
|
|
17089
17299
|
set(
|
|
17090
17300
|
"showPhoneField",
|
|
17091
17301
|
options.showPhoneField === void 0 ? void 0 : parseVisibility(options.showPhoneField, "--show-phone-field")
|
|
@@ -17298,7 +17508,19 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
17298
17508
|
);
|
|
17299
17509
|
offers.command("update").description(
|
|
17300
17510
|
'Update any offer (checkout-builder) setting \u2014 display, form & UX, legal, invoicing, notifications, tracking. Boolean flags take on|off; "none" clears a nullable field. Deep structured fields (theme, deadline, afterPurchase, paymentMethodsDisplay, customInvoicesClient, multiplePurchaseType, checkoutImages, checkoutMedia, termsContent, privacyPolicyContent, oto) go through --json. Lifecycle stays with publish/unpublish/delete; order bumps with `bumps attach`/`bumps detach`; the reusable OTO offer documents themselves with `oto create`/`update`/`delete`.'
|
|
17301
|
-
).argument("<checkoutId>", "offer (checkout) id").option("--name <name>", "internal offer name").option("--slug <slug>", "public slug (changing it on a live offer breaks links)").option("--title <title>", "checkout page title").option("--description <description>", "checkout page description").option("--success-url <url|none>", "post-purchase redirect URL (none = clear)").option("--language <code|none>", "checkout page language, e.g. en, pl (none = clear)").option("--checkout-media-width <px|none>", "max media gallery width in px (none = full width)").option("--show-testimonials <on|off>", "show the product testimonials gallery").option(
|
|
17511
|
+
).argument("<checkoutId>", "offer (checkout) id").option("--name <name>", "internal offer name").option("--slug <slug>", "public slug (changing it on a live offer breaks links)").option("--title <title>", "checkout page title").option("--description <description>", "checkout page description").option("--success-url <url|none>", "post-purchase redirect URL (none = clear)").option("--language <code|none>", "checkout page language, e.g. en, pl (none = clear)").option("--checkout-media-width <px|none>", "max media gallery width in px (none = full width)").option("--show-testimonials <on|off>", "show the product testimonials gallery").option(
|
|
17512
|
+
"--show-plan-includes <on|off>",
|
|
17513
|
+
'show the "Includes" list of bundled products under each price (multi-plan products; default on)'
|
|
17514
|
+
).option(
|
|
17515
|
+
"--plan-includes-layout <inline|list>",
|
|
17516
|
+
'layout of that "Includes" list: inline = one line (default), list = one product per line'
|
|
17517
|
+
).option("--invoice-as-option <on|off>", "let the buyer opt into invoice data entry").option("--hide-country-field <on|off>", "hide the country field on the form").option(
|
|
17518
|
+
"--fixed-country <ISO|none>",
|
|
17519
|
+
"country stored on orders while the country field is hidden (none = Poland)"
|
|
17520
|
+
).option(
|
|
17521
|
+
"--show-fixed-country <on|off>",
|
|
17522
|
+
"with the country field hidden: keep it on the form as a locked, read-only field"
|
|
17523
|
+
).option("--show-phone-field <off|optional|required>", "phone field visibility").option("--show-additional-info <off|optional|required>", "additional-info field visibility").option("--collect-first-name <on|off>", "free checkouts: collect first name").option("--collect-last-name <on|off>", "free checkouts: collect last name").option(
|
|
17302
17524
|
"--allow-promotion-code <off|subscriptions|all>",
|
|
17303
17525
|
"promotion-code input: hidden, subscription prices only, or all prices"
|
|
17304
17526
|
).option("--force-b2b <on|off>", "force company (B2B) purchase data").option(
|
|
@@ -17454,7 +17676,10 @@ Order bumps (${checkout.orderBumps.length}):
|
|
|
17454
17676
|
price.command("add").description("Add a price variant to an offer").argument("<checkoutId>", "offer (checkout) id").requiredOption("--type <type>", "one_time | recurring | installments").requiredOption(
|
|
17455
17677
|
"--amount <minor-units>",
|
|
17456
17678
|
"unit price in minor units, e.g. 1999 = 19.99 (installments: each payment)"
|
|
17457
|
-
).requiredOption("--currency <code>", "pln | usd | eur | gbp").option("--interval <interval>", "billing interval: day | week | month | year").option("--interval-count <n>", "bill every n intervals").option("--cycles <n>", "installments: total number of payments (>= 2)").option("--allow-cancellation", "installments: let the buyer cancel the schedule").option("--trial-days <n>", "recurring: free-trial days before the first charge").option("--label <text>", "display label").option("--invoice-name <text>", "custom invoice line name").option("--hidden", "hide from the public price list (buyable via direct link)").option("--description <text>", "markdown description rendered under the price").option("--sale-ends-at <ISO>", "time-limited sale deadline (ISO 8601, in the future)").option("--sale-days <n>", "convenience: end the sale n days from now").option("--quantity-limit <n>", "seat limit").option("--show-available", "show the remaining quantity publicly").option("--compare-at <minor-units>", 'promo strike-through "was" price in minor units').option("--access-interval <interval>", "one-time: limited access window unit").option("--access-count <n>", "one-time: limited access window length").option(
|
|
17679
|
+
).requiredOption("--currency <code>", "pln | usd | eur | gbp").option("--interval <interval>", "billing interval: day | week | month | year").option("--interval-count <n>", "bill every n intervals").option("--cycles <n>", "installments: total number of payments (>= 2)").option("--allow-cancellation", "installments: let the buyer cancel the schedule").option("--trial-days <n>", "recurring: free-trial days before the first charge").option("--label <text>", "display label").option("--invoice-name <text>", "custom invoice line name").option("--hidden", "hide from the public price list (buyable via direct link)").option("--description <text>", "markdown description rendered under the price").option("--sale-ends-at <ISO>", "time-limited sale deadline (ISO 8601, in the future)").option("--sale-days <n>", "convenience: end the sale n days from now").option("--quantity-limit <n>", "seat limit").option("--show-available", "show the remaining quantity publicly").option("--compare-at <minor-units>", 'promo strike-through "was" price in minor units').option("--access-interval <interval>", "one-time: limited access window unit").option("--access-count <n>", "one-time: limited access window length").option(
|
|
17680
|
+
"--access-until <iso>",
|
|
17681
|
+
"one-time: fixed access end for every buyer (ISO 8601, future; instead of --access-interval)"
|
|
17682
|
+
).option("--plan <planId>", "link the price to a plan").action(async (checkoutId, options, command) => {
|
|
17458
17683
|
const flags = getGlobalFlags(command);
|
|
17459
17684
|
const input = buildPriceInput(options);
|
|
17460
17685
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -19932,7 +20157,7 @@ function formatSteps(steps) {
|
|
|
19932
20157
|
// src/index.ts
|
|
19933
20158
|
var PROGRAM_NAME = "zanfia".length > 0 ? "zanfia" : "zanfia";
|
|
19934
20159
|
var program2 = new Command();
|
|
19935
|
-
program2.name(PROGRAM_NAME).description("Zanfia CLI \u2014 manage products, clients, orders and more from your terminal").version("0.
|
|
20160
|
+
program2.name(PROGRAM_NAME).description("Zanfia CLI \u2014 manage products, clients, orders and more from your terminal").version("0.3.10").option("--api-key <key>", "override stored API key for this invocation").option("--json", "emit JSON output instead of human-readable tables").option("--verbose", "log request/response debug info").showHelpAfterError().addHelpText(
|
|
19936
20161
|
"after",
|
|
19937
20162
|
`
|
|
19938
20163
|
New to Zanfia?
|
|
@@ -19950,10 +20175,14 @@ Signing in \u2014 two roles, two consent pages:
|
|
|
19950
20175
|
${PROGRAM_NAME} auth set-key --api-url <api-community-url> <key>
|
|
19951
20176
|
Member alternative without a browser: paste the key generated on the
|
|
19952
20177
|
creator's platform under Settings \u2192 Developer.
|
|
20178
|
+
${PROGRAM_NAME} join <handle>
|
|
20179
|
+
Join a public community from the terminal: emailed 6-digit code, then a
|
|
20180
|
+
member key is saved \u2014 no account or browser needed. Try: join crew-of-one
|
|
19953
20181
|
`
|
|
19954
20182
|
);
|
|
19955
20183
|
registerLoginCommand(program2);
|
|
19956
20184
|
registerRegisterCommand(program2);
|
|
20185
|
+
registerJoinCommand(program2);
|
|
19957
20186
|
registerLogoutCommand(program2, clearCredentials);
|
|
19958
20187
|
registerAuthCommands(program2);
|
|
19959
20188
|
registerProductsCommands(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanfia/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Zanfia CLI — thin HTTP client over the Zanfia Public API",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"tus-js-client": "^4.3.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@mailingr/typescript-config": "^1.1.
|
|
31
|
+
"@mailingr/typescript-config": "^1.1.1",
|
|
32
32
|
"@types/node": "^22.19.17",
|
|
33
33
|
"tsup": "^8.3.0",
|
|
34
34
|
"tsx": "^4.23.1",
|