@anakonn/ankk 0.1.1 → 0.1.3
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 +78 -0
- package/dist/index.js +671 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,61 @@ ankk upgrade --dry-run
|
|
|
30
30
|
The CLI uses the `latest` npm dist-tag. Release versions use three-part numeric
|
|
31
31
|
SemVer such as `0.1.1`.
|
|
32
32
|
|
|
33
|
+
## Initial Setup
|
|
34
|
+
|
|
35
|
+
Create `~/.ankk/config.json` after installing the CLI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ankk config setup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For CI or other non-interactive environments:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ANKK_API_KEY=spk_... ankk config setup --base-url https://api-public.ankk.app --api-key-env ANKK_API_KEY --yes
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The setup command creates `~/.ankk/` when needed and writes the config file with
|
|
48
|
+
owner-only permissions. It never prints the full API key.
|
|
49
|
+
|
|
50
|
+
Runtime config precedence is:
|
|
51
|
+
|
|
52
|
+
1. CLI flags: `--api-key`, `--base-url`, `--config`
|
|
53
|
+
2. Environment variables: `ANKK_API_KEY`, `ANKK_BASE_URL`
|
|
54
|
+
3. Config file: `~/.ankk/config.json`
|
|
55
|
+
4. Built-in default base URL: `https://api-public.ankk.app`
|
|
56
|
+
|
|
57
|
+
Inspect the resolved config without exposing the full key:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ankk config show
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## SNS Content
|
|
64
|
+
|
|
65
|
+
Use `content` for Ankk-managed work items: create, schedule, inspect publish
|
|
66
|
+
state, cancel scheduled content, and delete mutable content.
|
|
67
|
+
|
|
68
|
+
Use `posts` for provider posts that already exist on SNS providers. Scheduled
|
|
69
|
+
content appears in `content`, not `posts`. Provider-origin posts may appear in
|
|
70
|
+
`posts` even when Ankk did not create them. An Ankk content item appears in
|
|
71
|
+
`posts` only after it has provider post identity such as `provider_post_id`.
|
|
72
|
+
|
|
73
|
+
Publish immediately from a JSON request body:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
ankk content publish --file payload.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Schedule the same payload with an ISO timestamp:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ankk content publish --file payload.json --scheduled-for 2026-06-18T09:00:00Z
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If `payload.json` also contains `scheduled_for`, it must match `--scheduled-for`.
|
|
86
|
+
Relative inputs such as `10m` are not supported yet.
|
|
87
|
+
|
|
33
88
|
## Publish
|
|
34
89
|
|
|
35
90
|
Public npm publish runs automatically when CLI-related changes reach the
|
|
@@ -87,6 +142,29 @@ Use live production pull only for dogfooding or deployed-contract comparison:
|
|
|
87
142
|
(cd apps/cli && bun run src/entry/index.ts --json openapi pull --dry-run)
|
|
88
143
|
```
|
|
89
144
|
|
|
145
|
+
## Content Payload Examples
|
|
146
|
+
|
|
147
|
+
TikTok photo publishing uses `provider_settings.tiktok.postMode` because TikTok
|
|
148
|
+
separates Direct Post from upload-to-inbox. Photo URLs must be publicly
|
|
149
|
+
reachable and allowed by the app's TikTok verified URL prefix/domain.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"brand_ref": "<brand_ref>",
|
|
154
|
+
"connection_id": "<tiktok_connection_id>",
|
|
155
|
+
"idempotency_key": "tiktok-photo-upload-to-inbox-2026-06-17",
|
|
156
|
+
"sns_type": "tiktok",
|
|
157
|
+
"text": "Photo caption",
|
|
158
|
+
"media": [{ "asset_ref": "https://cdn.example.com/photo.jpg" }],
|
|
159
|
+
"provider_settings": {
|
|
160
|
+
"content_type": "photo",
|
|
161
|
+
"tiktok": {
|
|
162
|
+
"postMode": "upload_to_inbox"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
90
168
|
## Smoke
|
|
91
169
|
|
|
92
170
|
Use environment variables or `~/.ankk/config.json` for credentials. Do not paste
|
package/dist/index.js
CHANGED
|
@@ -2157,7 +2157,7 @@ var {
|
|
|
2157
2157
|
// package.json
|
|
2158
2158
|
var package_default = {
|
|
2159
2159
|
name: "@anakonn/ankk",
|
|
2160
|
-
version: "0.1.
|
|
2160
|
+
version: "0.1.3",
|
|
2161
2161
|
description: "Bun-first CLI for the ankk public API.",
|
|
2162
2162
|
private: false,
|
|
2163
2163
|
license: "UNLICENSED",
|
|
@@ -2684,8 +2684,184 @@ var messageFromBody = (body) => {
|
|
|
2684
2684
|
return typeof error === "string" && error.trim() ? error : undefined;
|
|
2685
2685
|
};
|
|
2686
2686
|
|
|
2687
|
+
// src/commander-output.ts
|
|
2688
|
+
var configureCommanderOutput = (program2, io) => {
|
|
2689
|
+
program2.configureOutput({
|
|
2690
|
+
writeErr: (chunk) => {
|
|
2691
|
+
io.stderr.write(chunk);
|
|
2692
|
+
},
|
|
2693
|
+
writeOut: (chunk) => {
|
|
2694
|
+
io.stdout.write(chunk);
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
};
|
|
2698
|
+
var isSuccessfulCommanderExit = (error) => typeof error === "object" && error !== null && ("exitCode" in error) && error.exitCode === 0;
|
|
2699
|
+
|
|
2700
|
+
// src/brand-image-upload.ts
|
|
2701
|
+
import { readFile, stat } from "fs/promises";
|
|
2702
|
+
import { basename } from "path";
|
|
2703
|
+
|
|
2704
|
+
// src/output.ts
|
|
2705
|
+
var defaultIo = {
|
|
2706
|
+
stderr: process.stderr,
|
|
2707
|
+
stdout: process.stdout
|
|
2708
|
+
};
|
|
2709
|
+
var writeOutput = (io, mode, value, human) => {
|
|
2710
|
+
if (mode === "json") {
|
|
2711
|
+
io.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
2712
|
+
`);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
io.stdout.write(`${human}
|
|
2716
|
+
`);
|
|
2717
|
+
};
|
|
2718
|
+
var writeError = (io, error, mode = "human") => {
|
|
2719
|
+
if (mode === "json") {
|
|
2720
|
+
io.stderr.write(`${JSON.stringify(errorJson(error), null, 2)}
|
|
2721
|
+
`);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
if (error instanceof CliHttpError) {
|
|
2725
|
+
io.stderr.write(`${error.message}
|
|
2726
|
+
`);
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
if (error instanceof Error) {
|
|
2730
|
+
io.stderr.write(`${error.message}
|
|
2731
|
+
`);
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
io.stderr.write(`Unexpected CLI error.
|
|
2735
|
+
`);
|
|
2736
|
+
};
|
|
2737
|
+
var errorJson = (error) => {
|
|
2738
|
+
if (error instanceof CliHttpError) {
|
|
2739
|
+
const body = error.body && typeof error.body === "object" && !Array.isArray(error.body) ? error.body : {};
|
|
2740
|
+
return {
|
|
2741
|
+
error: "http_error",
|
|
2742
|
+
message: error.message,
|
|
2743
|
+
...body,
|
|
2744
|
+
status: error.status
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
if (error instanceof Error) {
|
|
2748
|
+
return {
|
|
2749
|
+
error: "cli_error",
|
|
2750
|
+
message: error.message
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
return {
|
|
2754
|
+
error: "unexpected_cli_error",
|
|
2755
|
+
message: "Unexpected CLI error."
|
|
2756
|
+
};
|
|
2757
|
+
};
|
|
2758
|
+
|
|
2759
|
+
// src/brand-image-upload.ts
|
|
2760
|
+
var runBrandImageUploadCommand = async ({
|
|
2761
|
+
alt,
|
|
2762
|
+
brandRef,
|
|
2763
|
+
client,
|
|
2764
|
+
contentType,
|
|
2765
|
+
description,
|
|
2766
|
+
fetch,
|
|
2767
|
+
file,
|
|
2768
|
+
io,
|
|
2769
|
+
outputMode,
|
|
2770
|
+
type
|
|
2771
|
+
}) => {
|
|
2772
|
+
const upload = await readBrandImageUploadFile(file, contentType);
|
|
2773
|
+
const prepared = responseOrThrow(await client.POST("/v1/brand-images/uploads", {
|
|
2774
|
+
body: {
|
|
2775
|
+
brand_ref: brandRef,
|
|
2776
|
+
content_type: upload.contentType,
|
|
2777
|
+
filename: upload.filename,
|
|
2778
|
+
size: upload.size
|
|
2779
|
+
}
|
|
2780
|
+
}));
|
|
2781
|
+
const uploaded = await fetch(prepared.upload_url, {
|
|
2782
|
+
body: upload.data,
|
|
2783
|
+
headers: {
|
|
2784
|
+
"content-type": prepared.content_type
|
|
2785
|
+
},
|
|
2786
|
+
method: prepared.method
|
|
2787
|
+
});
|
|
2788
|
+
if (!uploaded.ok) {
|
|
2789
|
+
throw new Error(`Brand image upload failed with HTTP ${uploaded.status}.`);
|
|
2790
|
+
}
|
|
2791
|
+
const image = responseOrThrow(await client.POST("/v1/brand-images", {
|
|
2792
|
+
body: {
|
|
2793
|
+
brand_ref: brandRef,
|
|
2794
|
+
origin_url: prepared.origin_url,
|
|
2795
|
+
...type ? { type } : {},
|
|
2796
|
+
...alt !== undefined ? { alt } : {},
|
|
2797
|
+
...description !== undefined ? { description } : {}
|
|
2798
|
+
}
|
|
2799
|
+
}));
|
|
2800
|
+
writeOutput(io, outputMode, {
|
|
2801
|
+
...image,
|
|
2802
|
+
uploaded: true
|
|
2803
|
+
}, `image_ref: ${image.image_ref}
|
|
2804
|
+
url: ${image.url}`);
|
|
2805
|
+
};
|
|
2806
|
+
var brandImageUploadPolicies = {
|
|
2807
|
+
".gif": { contentType: "image/gif", maxSize: 20 * 1024 * 1024 },
|
|
2808
|
+
".jpeg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
2809
|
+
".jpg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
2810
|
+
".png": { contentType: "image/png", maxSize: 20 * 1024 * 1024 },
|
|
2811
|
+
".webp": { contentType: "image/webp", maxSize: 20 * 1024 * 1024 }
|
|
2812
|
+
};
|
|
2813
|
+
var readBrandImageUploadFile = async (file, contentTypeOverride) => {
|
|
2814
|
+
const filename = basename(file);
|
|
2815
|
+
const extension = brandImageExtension(filename);
|
|
2816
|
+
const extensionPolicy = brandImageUploadPolicies[extension];
|
|
2817
|
+
const contentType = contentTypeOverride?.trim().toLowerCase() || extensionPolicy.contentType;
|
|
2818
|
+
const contentTypePolicy = brandImageUploadPolicyForContentType(contentType);
|
|
2819
|
+
if (!contentTypePolicy) {
|
|
2820
|
+
throw new Error(`Unsupported brand image content type: ${contentType}`);
|
|
2821
|
+
}
|
|
2822
|
+
if (!contentTypePolicy.extensions.includes(extension)) {
|
|
2823
|
+
throw new Error(`Brand image file extension does not match content type: ${filename} (${contentType})`);
|
|
2824
|
+
}
|
|
2825
|
+
const info = await stat(file);
|
|
2826
|
+
if (!info.isFile()) {
|
|
2827
|
+
throw new Error(`Brand image path is not a file: ${file}`);
|
|
2828
|
+
}
|
|
2829
|
+
if (info.size <= 0) {
|
|
2830
|
+
throw new Error("Brand image file is empty.");
|
|
2831
|
+
}
|
|
2832
|
+
if (info.size > contentTypePolicy.maxSize) {
|
|
2833
|
+
throw new Error(`Brand image file exceeds ${contentTypePolicy.maxSize} bytes.`);
|
|
2834
|
+
}
|
|
2835
|
+
return {
|
|
2836
|
+
contentType,
|
|
2837
|
+
data: await readFile(file),
|
|
2838
|
+
filename,
|
|
2839
|
+
size: info.size
|
|
2840
|
+
};
|
|
2841
|
+
};
|
|
2842
|
+
var brandImageExtension = (filename) => {
|
|
2843
|
+
const index = filename.lastIndexOf(".");
|
|
2844
|
+
const extension = index > 0 ? filename.slice(index).toLowerCase() : "";
|
|
2845
|
+
if (extension in brandImageUploadPolicies)
|
|
2846
|
+
return extension;
|
|
2847
|
+
throw new Error(`Unsupported brand image file extension: ${filename}`);
|
|
2848
|
+
};
|
|
2849
|
+
var brandImageUploadPolicyForContentType = (contentType) => {
|
|
2850
|
+
const extensions = Object.entries(brandImageUploadPolicies).filter(([, policy]) => policy.contentType === contentType).map(([extension]) => extension);
|
|
2851
|
+
if (extensions.length === 0)
|
|
2852
|
+
return null;
|
|
2853
|
+
return {
|
|
2854
|
+
extensions,
|
|
2855
|
+
maxSize: brandImageUploadPolicies[extensions[0] ?? ".jpg"].maxSize
|
|
2856
|
+
};
|
|
2857
|
+
};
|
|
2858
|
+
|
|
2859
|
+
// src/config-command.ts
|
|
2860
|
+
import { createInterface } from "readline/promises";
|
|
2861
|
+
import { Writable } from "stream";
|
|
2862
|
+
|
|
2687
2863
|
// src/config.ts
|
|
2688
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2864
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
2689
2865
|
import { dirname, join } from "path";
|
|
2690
2866
|
|
|
2691
2867
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
@@ -16982,7 +17158,7 @@ var defaultConfigPath = ({
|
|
|
16982
17158
|
};
|
|
16983
17159
|
var loadConfig = async (path) => {
|
|
16984
17160
|
try {
|
|
16985
|
-
const raw = await
|
|
17161
|
+
const raw = await readFile2(path, "utf8");
|
|
16986
17162
|
return configSchema.parse(JSON.parse(raw));
|
|
16987
17163
|
} catch (error51) {
|
|
16988
17164
|
if (isMissingFileError(error51))
|
|
@@ -16990,6 +17166,23 @@ var loadConfig = async (path) => {
|
|
|
16990
17166
|
throw error51;
|
|
16991
17167
|
}
|
|
16992
17168
|
};
|
|
17169
|
+
var configFileExists = async (path) => {
|
|
17170
|
+
try {
|
|
17171
|
+
await readFile2(path, "utf8");
|
|
17172
|
+
return true;
|
|
17173
|
+
} catch (error51) {
|
|
17174
|
+
if (isMissingFileError(error51))
|
|
17175
|
+
return false;
|
|
17176
|
+
throw error51;
|
|
17177
|
+
}
|
|
17178
|
+
};
|
|
17179
|
+
var writeConfig = async (path, config2) => {
|
|
17180
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
17181
|
+
await writeFile(path, `${JSON.stringify(config2, null, 2)}
|
|
17182
|
+
`, {
|
|
17183
|
+
mode: 384
|
|
17184
|
+
});
|
|
17185
|
+
};
|
|
16993
17186
|
var resolveRuntimeConfig = async ({
|
|
16994
17187
|
apiKey,
|
|
16995
17188
|
baseUrl,
|
|
@@ -17015,10 +17208,264 @@ var redactApiKey = (apiKey) => {
|
|
|
17015
17208
|
var normalizeBaseUrl = (value) => value.replace(/\/+$/, "");
|
|
17016
17209
|
var isMissingFileError = (error51) => typeof error51 === "object" && error51 !== null && ("code" in error51) && error51.code === "ENOENT";
|
|
17017
17210
|
|
|
17211
|
+
// src/config-command.ts
|
|
17212
|
+
var registerConfigCommands = ({
|
|
17213
|
+
env,
|
|
17214
|
+
fetch,
|
|
17215
|
+
io,
|
|
17216
|
+
program: program2,
|
|
17217
|
+
prompt = createDefaultPrompt(io)
|
|
17218
|
+
}) => {
|
|
17219
|
+
const config2 = program2.command("config").description("Manage CLI config");
|
|
17220
|
+
config2.command("show").description("Show resolved CLI config").action(async () => {
|
|
17221
|
+
const options = program2.opts();
|
|
17222
|
+
const runtimeConfig = await resolveRuntimeConfig({
|
|
17223
|
+
apiKey: options.apiKey,
|
|
17224
|
+
baseUrl: options.baseUrl,
|
|
17225
|
+
configPath: options.config ?? defaultConfigPath({ env }),
|
|
17226
|
+
env
|
|
17227
|
+
});
|
|
17228
|
+
const value = {
|
|
17229
|
+
api_key: redactApiKey(runtimeConfig.apiKey),
|
|
17230
|
+
base_url: runtimeConfig.baseUrl,
|
|
17231
|
+
config_path: runtimeConfig.configPath
|
|
17232
|
+
};
|
|
17233
|
+
writeOutput(io, outputMode(options), value, `base_url: ${value.base_url}
|
|
17234
|
+
api_key: ${value.api_key ?? "(not set)"}
|
|
17235
|
+
config_path: ${value.config_path}`);
|
|
17236
|
+
});
|
|
17237
|
+
config2.command("setup").description("Create or update the CLI config file").option("--api-key <key>", "Public API key to store").option("--api-key-env <name>", "Read the public API key from an environment variable").option("--base-url <url>", "API base URL to store").option("--skip-health", "Do not run the health check after writing config").option("--yes", "Overwrite existing config and use defaults without prompting").action(async (options) => {
|
|
17238
|
+
const globalOptions = program2.opts();
|
|
17239
|
+
const configPath = globalOptions.config ?? defaultConfigPath({ env });
|
|
17240
|
+
const existingConfig = await loadConfig(configPath);
|
|
17241
|
+
const exists = await configFileExists(configPath);
|
|
17242
|
+
const overwrite = exists ? Boolean(options.yes) || await prompt.confirm({
|
|
17243
|
+
defaultValue: false,
|
|
17244
|
+
message: `Overwrite existing config at ${configPath}?`
|
|
17245
|
+
}) : true;
|
|
17246
|
+
if (!overwrite) {
|
|
17247
|
+
const value2 = {
|
|
17248
|
+
config_path: configPath,
|
|
17249
|
+
wrote: false
|
|
17250
|
+
};
|
|
17251
|
+
writeOutput(io, outputMode(globalOptions), value2, `Config unchanged: ${configPath}`);
|
|
17252
|
+
return;
|
|
17253
|
+
}
|
|
17254
|
+
const baseUrl = await setupBaseUrl({
|
|
17255
|
+
defaultValue: globalOptions.baseUrl ?? env.ANKK_BASE_URL ?? existingConfig.base_url ?? defaultBaseUrl,
|
|
17256
|
+
optionValue: options.baseUrl,
|
|
17257
|
+
prompt,
|
|
17258
|
+
yes: Boolean(options.yes)
|
|
17259
|
+
});
|
|
17260
|
+
const apiKey = await setupApiKey({
|
|
17261
|
+
env,
|
|
17262
|
+
existingValue: existingConfig.api_key,
|
|
17263
|
+
globalValue: globalOptions.apiKey,
|
|
17264
|
+
optionEnvName: options.apiKeyEnv,
|
|
17265
|
+
optionValue: options.apiKey,
|
|
17266
|
+
prompt,
|
|
17267
|
+
yes: Boolean(options.yes)
|
|
17268
|
+
});
|
|
17269
|
+
await writeConfig(configPath, {
|
|
17270
|
+
api_key: apiKey,
|
|
17271
|
+
base_url: baseUrl
|
|
17272
|
+
});
|
|
17273
|
+
const health = options.skipHealth ? null : await fetchHealth({ baseUrl, fetch });
|
|
17274
|
+
const value = {
|
|
17275
|
+
api_key: redactApiKey(apiKey),
|
|
17276
|
+
base_url: baseUrl,
|
|
17277
|
+
config_path: configPath,
|
|
17278
|
+
health,
|
|
17279
|
+
next_commands: ["ankk brands list", "ankk config show"],
|
|
17280
|
+
wrote: true
|
|
17281
|
+
};
|
|
17282
|
+
writeOutput(io, outputMode(globalOptions), value, [
|
|
17283
|
+
`Config saved: ${configPath}`,
|
|
17284
|
+
`base_url: ${baseUrl}`,
|
|
17285
|
+
`api_key: ${redactApiKey(apiKey)}`,
|
|
17286
|
+
...health ? [`health: ${health.service} ${health.status}`] : [],
|
|
17287
|
+
"next: ankk brands list"
|
|
17288
|
+
].join(`
|
|
17289
|
+
`));
|
|
17290
|
+
});
|
|
17291
|
+
};
|
|
17292
|
+
var setupBaseUrl = async ({
|
|
17293
|
+
defaultValue,
|
|
17294
|
+
optionValue,
|
|
17295
|
+
prompt,
|
|
17296
|
+
yes
|
|
17297
|
+
}) => {
|
|
17298
|
+
const value = optionValue ?? (yes ? defaultValue : await prompt.text({ defaultValue, message: "API base URL" }));
|
|
17299
|
+
return normalizeBaseUrl2(value || defaultValue);
|
|
17300
|
+
};
|
|
17301
|
+
var setupApiKey = async ({
|
|
17302
|
+
env,
|
|
17303
|
+
existingValue,
|
|
17304
|
+
globalValue,
|
|
17305
|
+
optionEnvName,
|
|
17306
|
+
optionValue,
|
|
17307
|
+
prompt,
|
|
17308
|
+
yes
|
|
17309
|
+
}) => {
|
|
17310
|
+
const envValue = optionEnvName ? env[optionEnvName] : undefined;
|
|
17311
|
+
if (optionEnvName && !envValue) {
|
|
17312
|
+
throw new Error(`API key environment variable ${optionEnvName} is not set.`);
|
|
17313
|
+
}
|
|
17314
|
+
const value = optionValue ?? globalValue ?? envValue ?? env.ANKK_API_KEY ?? (yes ? existingValue : await prompt.secret({ message: "API key" }));
|
|
17315
|
+
if (!value) {
|
|
17316
|
+
throw new Error("API key is required. Pass --api-key-env ANKK_API_KEY, --api-key, or set ANKK_API_KEY.");
|
|
17317
|
+
}
|
|
17318
|
+
return value;
|
|
17319
|
+
};
|
|
17320
|
+
var fetchHealth = async ({ baseUrl, fetch }) => {
|
|
17321
|
+
const response = await fetch(`${baseUrl}/v1/health`);
|
|
17322
|
+
if (!response.ok)
|
|
17323
|
+
throw new Error(`Config saved, but health check failed with HTTP ${response.status}.`);
|
|
17324
|
+
const body = await response.json();
|
|
17325
|
+
if (!isHealthResponse(body))
|
|
17326
|
+
throw new Error("Config saved, but health check response was invalid.");
|
|
17327
|
+
return body;
|
|
17328
|
+
};
|
|
17329
|
+
var createDefaultPrompt = (io) => {
|
|
17330
|
+
const prompt = {
|
|
17331
|
+
async confirm({ defaultValue = false, message }) {
|
|
17332
|
+
const suffix = defaultValue ? " [Y/n]" : " [y/N]";
|
|
17333
|
+
const answer = await prompt.text({ message: `${message}${suffix}` });
|
|
17334
|
+
const normalized = answer.trim().toLowerCase();
|
|
17335
|
+
if (!normalized)
|
|
17336
|
+
return defaultValue;
|
|
17337
|
+
return normalized === "y" || normalized === "yes";
|
|
17338
|
+
},
|
|
17339
|
+
async secret({ message }) {
|
|
17340
|
+
io.stderr.write(`${message}: `);
|
|
17341
|
+
const reader = createInterface({
|
|
17342
|
+
input: process.stdin,
|
|
17343
|
+
output: new MuteWritable,
|
|
17344
|
+
terminal: true
|
|
17345
|
+
});
|
|
17346
|
+
try {
|
|
17347
|
+
return (await reader.question("")).trim();
|
|
17348
|
+
} finally {
|
|
17349
|
+
reader.close();
|
|
17350
|
+
io.stderr.write(`
|
|
17351
|
+
`);
|
|
17352
|
+
}
|
|
17353
|
+
},
|
|
17354
|
+
async text({ defaultValue, message }) {
|
|
17355
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
17356
|
+
const reader = createInterface({
|
|
17357
|
+
input: process.stdin,
|
|
17358
|
+
output: process.stderr
|
|
17359
|
+
});
|
|
17360
|
+
try {
|
|
17361
|
+
const value = (await reader.question(`${message}${suffix}: `)).trim();
|
|
17362
|
+
return value || defaultValue || "";
|
|
17363
|
+
} finally {
|
|
17364
|
+
reader.close();
|
|
17365
|
+
}
|
|
17366
|
+
}
|
|
17367
|
+
};
|
|
17368
|
+
return prompt;
|
|
17369
|
+
};
|
|
17370
|
+
|
|
17371
|
+
class MuteWritable extends Writable {
|
|
17372
|
+
_write(_chunk, _encoding, callback) {
|
|
17373
|
+
callback();
|
|
17374
|
+
}
|
|
17375
|
+
}
|
|
17376
|
+
var normalizeBaseUrl2 = (value) => {
|
|
17377
|
+
const normalized = value.replace(/\/+$/, "");
|
|
17378
|
+
new URL(normalized);
|
|
17379
|
+
return normalized;
|
|
17380
|
+
};
|
|
17381
|
+
var outputMode = (options) => options.json ? "json" : "human";
|
|
17382
|
+
var isHealthResponse = (value) => {
|
|
17383
|
+
if (!value || typeof value !== "object")
|
|
17384
|
+
return false;
|
|
17385
|
+
const record2 = value;
|
|
17386
|
+
return typeof record2.service === "string" && typeof record2.status === "string";
|
|
17387
|
+
};
|
|
17388
|
+
|
|
17389
|
+
// src/content-publish.ts
|
|
17390
|
+
var applyScheduledForOption = (body, scheduledFor) => {
|
|
17391
|
+
if (!scheduledFor)
|
|
17392
|
+
return body;
|
|
17393
|
+
const normalized = normalizeIsoTimestamp(scheduledFor, "--scheduled-for");
|
|
17394
|
+
const existing = body.scheduled_for;
|
|
17395
|
+
if (existing === undefined || existing === null) {
|
|
17396
|
+
return { ...body, scheduled_for: normalized };
|
|
17397
|
+
}
|
|
17398
|
+
if (typeof existing !== "string") {
|
|
17399
|
+
throw new Error("scheduled_for in SNS content publish body must be an ISO timestamp string or null.");
|
|
17400
|
+
}
|
|
17401
|
+
if (normalizeIsoTimestamp(existing, "scheduled_for") !== normalized) {
|
|
17402
|
+
throw new Error("scheduled_for in SNS content publish body conflicts with --scheduled-for.");
|
|
17403
|
+
}
|
|
17404
|
+
return { ...body, scheduled_for: normalized };
|
|
17405
|
+
};
|
|
17406
|
+
var formatContentPublishResult = (response, body) => {
|
|
17407
|
+
const lines = [`content_id: ${response.content_id}`, `publish_job_id: ${response.publish_job_id}`, `status: ${response.status}`];
|
|
17408
|
+
if (response.scheduled_for) {
|
|
17409
|
+
lines.push(`scheduled_for: ${response.scheduled_for}`);
|
|
17410
|
+
const brandRef = typeof body.brand_ref === "string" ? body.brand_ref : "<brand_ref>";
|
|
17411
|
+
lines.push(`next: ankk content cancel ${response.content_id} --brand-ref ${brandRef}`);
|
|
17412
|
+
}
|
|
17413
|
+
return lines.join(`
|
|
17414
|
+
`);
|
|
17415
|
+
};
|
|
17416
|
+
var normalizeIsoTimestamp = (value, name) => {
|
|
17417
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)) {
|
|
17418
|
+
throw new Error(`${name} must be an ISO timestamp, for example 2026-06-18T09:00:00Z.`);
|
|
17419
|
+
}
|
|
17420
|
+
const timestamp = new Date(value);
|
|
17421
|
+
if (Number.isNaN(timestamp.getTime())) {
|
|
17422
|
+
throw new Error(`${name} must be an ISO timestamp, for example 2026-06-18T09:00:00Z.`);
|
|
17423
|
+
}
|
|
17424
|
+
return timestamp.toISOString();
|
|
17425
|
+
};
|
|
17426
|
+
|
|
17427
|
+
// src/content-output.ts
|
|
17428
|
+
var formatSnsContent = (response) => {
|
|
17429
|
+
const lines = [
|
|
17430
|
+
`content_id: ${response.content_id}`,
|
|
17431
|
+
`status: ${response.status}`,
|
|
17432
|
+
`sns_type: ${response.sns_type}`,
|
|
17433
|
+
`connection_id: ${response.connection_id}`
|
|
17434
|
+
];
|
|
17435
|
+
if (response.title)
|
|
17436
|
+
lines.push(`title: ${response.title}`);
|
|
17437
|
+
if (response.provider_post_id)
|
|
17438
|
+
lines.push(`provider_post_id: ${response.provider_post_id}`);
|
|
17439
|
+
if (response.provider_post_status)
|
|
17440
|
+
lines.push(`provider_post_status: ${response.provider_post_status}`);
|
|
17441
|
+
if (response.permalink)
|
|
17442
|
+
lines.push(`permalink: ${response.permalink}`);
|
|
17443
|
+
if (response.latest_publish_job) {
|
|
17444
|
+
lines.push(`latest_publish_job: ${response.latest_publish_job.status} (${response.latest_publish_job.publish_job_id})`);
|
|
17445
|
+
if (response.latest_publish_job.scheduled_for)
|
|
17446
|
+
lines.push(`scheduled_for: ${response.latest_publish_job.scheduled_for}`);
|
|
17447
|
+
if (response.latest_publish_job.last_error_code)
|
|
17448
|
+
lines.push(`last_error_code: ${response.latest_publish_job.last_error_code}`);
|
|
17449
|
+
if (response.latest_publish_job.last_error_message)
|
|
17450
|
+
lines.push(`last_error_message: ${response.latest_publish_job.last_error_message}`);
|
|
17451
|
+
}
|
|
17452
|
+
if (response.latest_delete_job) {
|
|
17453
|
+
lines.push(`latest_delete_job: ${response.latest_delete_job.status} (${response.latest_delete_job.delete_job_id})`);
|
|
17454
|
+
if (response.latest_delete_job.scheduled_for)
|
|
17455
|
+
lines.push(`delete_scheduled_for: ${response.latest_delete_job.scheduled_for}`);
|
|
17456
|
+
if (response.latest_delete_job.last_error_code)
|
|
17457
|
+
lines.push(`delete_last_error_code: ${response.latest_delete_job.last_error_code}`);
|
|
17458
|
+
if (response.latest_delete_job.last_error_message)
|
|
17459
|
+
lines.push(`delete_last_error_message: ${response.latest_delete_job.last_error_message}`);
|
|
17460
|
+
}
|
|
17461
|
+
return lines.join(`
|
|
17462
|
+
`);
|
|
17463
|
+
};
|
|
17464
|
+
|
|
17018
17465
|
// src/json-input.ts
|
|
17019
|
-
import { readFile as
|
|
17466
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
17020
17467
|
var readJsonInput = async (path, { readStdin = readProcessStdin } = {}) => {
|
|
17021
|
-
const raw = path === "-" ? await readStdin() : await
|
|
17468
|
+
const raw = path === "-" ? await readStdin() : await readFile3(path, "utf8");
|
|
17022
17469
|
try {
|
|
17023
17470
|
return JSON.parse(raw);
|
|
17024
17471
|
} catch (error51) {
|
|
@@ -17030,6 +17477,98 @@ var readJsonInput = async (path, { readStdin = readProcessStdin } = {}) => {
|
|
|
17030
17477
|
};
|
|
17031
17478
|
var readProcessStdin = async () => Bun.stdin.text();
|
|
17032
17479
|
|
|
17480
|
+
// src/media-upload.ts
|
|
17481
|
+
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
17482
|
+
import { basename as basename2 } from "path";
|
|
17483
|
+
var runMediaUploadCommand = async ({
|
|
17484
|
+
brandRef,
|
|
17485
|
+
client,
|
|
17486
|
+
contentType,
|
|
17487
|
+
fetch,
|
|
17488
|
+
file: file2,
|
|
17489
|
+
io,
|
|
17490
|
+
outputMode: outputMode2
|
|
17491
|
+
}) => {
|
|
17492
|
+
const upload = await readMediaUploadFile(file2, contentType);
|
|
17493
|
+
const prepared = responseOrThrow(await client.POST("/v1/media/uploads", {
|
|
17494
|
+
body: {
|
|
17495
|
+
brand_ref: brandRef,
|
|
17496
|
+
content_type: upload.contentType,
|
|
17497
|
+
filename: upload.filename,
|
|
17498
|
+
size: upload.size
|
|
17499
|
+
}
|
|
17500
|
+
}));
|
|
17501
|
+
const uploaded = await fetch(prepared.upload_url, {
|
|
17502
|
+
body: upload.data,
|
|
17503
|
+
headers: {
|
|
17504
|
+
"content-type": prepared.content_type
|
|
17505
|
+
},
|
|
17506
|
+
method: prepared.method
|
|
17507
|
+
});
|
|
17508
|
+
if (!uploaded.ok) {
|
|
17509
|
+
throw new Error(`Media upload failed with HTTP ${uploaded.status}.`);
|
|
17510
|
+
}
|
|
17511
|
+
writeOutput(io, outputMode2, {
|
|
17512
|
+
...prepared,
|
|
17513
|
+
uploaded: true
|
|
17514
|
+
}, `asset_ref: ${prepared.asset_ref}`);
|
|
17515
|
+
};
|
|
17516
|
+
var mediaUploadPolicies = {
|
|
17517
|
+
".gif": { contentType: "image/gif", maxSize: 20 * 1024 * 1024 },
|
|
17518
|
+
".jpeg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
17519
|
+
".jpg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
17520
|
+
".mov": { contentType: "video/quicktime", maxSize: 500 * 1024 * 1024 },
|
|
17521
|
+
".mp4": { contentType: "video/mp4", maxSize: 500 * 1024 * 1024 },
|
|
17522
|
+
".png": { contentType: "image/png", maxSize: 20 * 1024 * 1024 },
|
|
17523
|
+
".webm": { contentType: "video/webm", maxSize: 500 * 1024 * 1024 },
|
|
17524
|
+
".webp": { contentType: "image/webp", maxSize: 20 * 1024 * 1024 }
|
|
17525
|
+
};
|
|
17526
|
+
var readMediaUploadFile = async (file2, contentTypeOverride) => {
|
|
17527
|
+
const filename = basename2(file2);
|
|
17528
|
+
const extension = mediaExtension(filename);
|
|
17529
|
+
const extensionPolicy = mediaUploadPolicies[extension];
|
|
17530
|
+
const contentType = contentTypeOverride?.trim().toLowerCase() || extensionPolicy.contentType;
|
|
17531
|
+
const contentTypePolicy = mediaUploadPolicyForContentType(contentType);
|
|
17532
|
+
if (!contentTypePolicy) {
|
|
17533
|
+
throw new Error(`Unsupported media content type: ${contentType}`);
|
|
17534
|
+
}
|
|
17535
|
+
if (!contentTypePolicy.extensions.includes(extension)) {
|
|
17536
|
+
throw new Error(`Media file extension does not match content type: ${filename} (${contentType})`);
|
|
17537
|
+
}
|
|
17538
|
+
const info = await stat2(file2);
|
|
17539
|
+
if (!info.isFile()) {
|
|
17540
|
+
throw new Error(`Media path is not a file: ${file2}`);
|
|
17541
|
+
}
|
|
17542
|
+
if (info.size <= 0) {
|
|
17543
|
+
throw new Error("Media file is empty.");
|
|
17544
|
+
}
|
|
17545
|
+
if (info.size > contentTypePolicy.maxSize) {
|
|
17546
|
+
throw new Error(`Media file exceeds ${contentTypePolicy.maxSize} bytes.`);
|
|
17547
|
+
}
|
|
17548
|
+
return {
|
|
17549
|
+
contentType,
|
|
17550
|
+
data: await readFile4(file2),
|
|
17551
|
+
filename,
|
|
17552
|
+
size: info.size
|
|
17553
|
+
};
|
|
17554
|
+
};
|
|
17555
|
+
var mediaExtension = (filename) => {
|
|
17556
|
+
const index = filename.lastIndexOf(".");
|
|
17557
|
+
const extension = index > 0 ? filename.slice(index).toLowerCase() : "";
|
|
17558
|
+
if (extension in mediaUploadPolicies)
|
|
17559
|
+
return extension;
|
|
17560
|
+
throw new Error(`Unsupported media file extension: ${filename}`);
|
|
17561
|
+
};
|
|
17562
|
+
var mediaUploadPolicyForContentType = (contentType) => {
|
|
17563
|
+
const extensions = Object.entries(mediaUploadPolicies).filter(([, policy]) => policy.contentType === contentType).map(([extension]) => extension);
|
|
17564
|
+
if (extensions.length === 0)
|
|
17565
|
+
return null;
|
|
17566
|
+
return {
|
|
17567
|
+
extensions,
|
|
17568
|
+
maxSize: mediaUploadPolicies[extensions[0] ?? ".jpg"].maxSize
|
|
17569
|
+
};
|
|
17570
|
+
};
|
|
17571
|
+
|
|
17033
17572
|
// src/openapi.ts
|
|
17034
17573
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
17035
17574
|
import { dirname as dirname2 } from "path";
|
|
@@ -17052,49 +17591,24 @@ var writeOpenApiSnapshot = async (path, spec) => {
|
|
|
17052
17591
|
`);
|
|
17053
17592
|
};
|
|
17054
17593
|
|
|
17055
|
-
// src/output.ts
|
|
17056
|
-
var defaultIo = {
|
|
17057
|
-
stderr: process.stderr,
|
|
17058
|
-
stdout: process.stdout
|
|
17059
|
-
};
|
|
17060
|
-
var writeOutput = (io, mode, value, human) => {
|
|
17061
|
-
if (mode === "json") {
|
|
17062
|
-
io.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
17063
|
-
`);
|
|
17064
|
-
return;
|
|
17065
|
-
}
|
|
17066
|
-
io.stdout.write(`${human}
|
|
17067
|
-
`);
|
|
17068
|
-
};
|
|
17069
|
-
var writeError = (io, error51) => {
|
|
17070
|
-
if (error51 instanceof CliHttpError) {
|
|
17071
|
-
io.stderr.write(`${error51.message}
|
|
17072
|
-
`);
|
|
17073
|
-
return;
|
|
17074
|
-
}
|
|
17075
|
-
if (error51 instanceof Error) {
|
|
17076
|
-
io.stderr.write(`${error51.message}
|
|
17077
|
-
`);
|
|
17078
|
-
return;
|
|
17079
|
-
}
|
|
17080
|
-
io.stderr.write(`Unexpected CLI error.
|
|
17081
|
-
`);
|
|
17082
|
-
};
|
|
17083
|
-
|
|
17084
17594
|
// src/program.ts
|
|
17085
17595
|
var runCli = async ({
|
|
17086
17596
|
argv,
|
|
17087
17597
|
commandRunner = runProcess,
|
|
17088
17598
|
env = process.env,
|
|
17089
17599
|
fetch = globalThis.fetch,
|
|
17090
|
-
io = defaultIo
|
|
17600
|
+
io = defaultIo,
|
|
17601
|
+
prompt
|
|
17091
17602
|
}) => {
|
|
17092
|
-
const program2 = createProgram({ commandRunner, env, fetch, io });
|
|
17603
|
+
const program2 = createProgram({ commandRunner, env, fetch, io, prompt });
|
|
17093
17604
|
try {
|
|
17094
17605
|
await program2.parseAsync(argv, { from: "user" });
|
|
17095
17606
|
return 0;
|
|
17096
17607
|
} catch (error51) {
|
|
17097
|
-
|
|
17608
|
+
if (isSuccessfulCommanderExit(error51)) {
|
|
17609
|
+
return 0;
|
|
17610
|
+
}
|
|
17611
|
+
writeError(io, error51, program2.opts().json ? "json" : "human");
|
|
17098
17612
|
return 1;
|
|
17099
17613
|
}
|
|
17100
17614
|
};
|
|
@@ -17102,25 +17616,27 @@ var createProgram = ({
|
|
|
17102
17616
|
commandRunner = runProcess,
|
|
17103
17617
|
env = process.env,
|
|
17104
17618
|
fetch = globalThis.fetch,
|
|
17105
|
-
io = defaultIo
|
|
17619
|
+
io = defaultIo,
|
|
17620
|
+
prompt
|
|
17106
17621
|
} = {}) => {
|
|
17107
17622
|
const program2 = new Command;
|
|
17108
|
-
program2
|
|
17623
|
+
configureCommanderOutput(program2, io);
|
|
17624
|
+
program2.name("ankk").description("CLI for the ankk public API").version(packageVersion, "-V, --version", "Print version").option("--base-url <url>", "API base URL").option("--api-key <key>", "Public API key").option("--config <path>", "Config file path").option("--json", "Print JSON output").exitOverride();
|
|
17109
17625
|
program2.command("health").description("Check api-public health").action(async () => {
|
|
17110
17626
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17111
|
-
const result = await
|
|
17627
|
+
const result = await fetchHealth2({
|
|
17112
17628
|
baseUrl: context.config.baseUrl,
|
|
17113
17629
|
fetch
|
|
17114
17630
|
});
|
|
17115
17631
|
writeOutput(context.io, context.outputMode, result, `${result.service}: ${result.status}`);
|
|
17116
17632
|
});
|
|
17117
17633
|
program2.command("upgrade").description("Upgrade the ankk CLI global installation").option("--dry-run", "Print the upgrade command without running it").action(async (options) => {
|
|
17118
|
-
const
|
|
17634
|
+
const outputMode2 = program2.opts().json ? "json" : "human";
|
|
17119
17635
|
const result = await upgradeCli({
|
|
17120
17636
|
commandRunner,
|
|
17121
17637
|
dryRun: Boolean(options.dryRun)
|
|
17122
17638
|
});
|
|
17123
|
-
writeOutput(io,
|
|
17639
|
+
writeOutput(io, outputMode2, result, options.dryRun ? `Would run: ${result.command.join(" ")}` : `Upgraded ${result.package} via ${result.tag}`);
|
|
17124
17640
|
});
|
|
17125
17641
|
const brands = program2.command("brands").description("Manage brands available to the API key");
|
|
17126
17642
|
brands.command("list").description("List brands available to the API key").action(async () => {
|
|
@@ -17178,8 +17694,8 @@ var createProgram = ({
|
|
|
17178
17694
|
}));
|
|
17179
17695
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17180
17696
|
});
|
|
17181
|
-
const content = program2.command("content").description("
|
|
17182
|
-
withBrandRef(content.command("list").description("List SNS
|
|
17697
|
+
const content = program2.command("content").description("Create, schedule, and manage Ankk SNS content work items");
|
|
17698
|
+
withBrandRef(content.command("list").description("List Ankk SNS content work items")).option("--connection-id <connection_id>", "Connection id filter").option("--status <status>", "Content status filter").action(async (options) => {
|
|
17183
17699
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17184
17700
|
const result = responseOrThrow(await context.client.GET("/v1/content", {
|
|
17185
17701
|
params: queryParams({
|
|
@@ -17190,7 +17706,7 @@ var createProgram = ({
|
|
|
17190
17706
|
}));
|
|
17191
17707
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17192
17708
|
});
|
|
17193
|
-
withBrandRef(content.command("get").description("Get SNS content").argument("<content_id>", "Content id")).option("--connection-id <connection_id>", "Connection id filter").option("--status <status>", "Content status filter").action(async (contentId, options) => {
|
|
17709
|
+
withBrandRef(content.command("get").description("Get an Ankk SNS content work item").argument("<content_id>", "Content id")).option("--connection-id <connection_id>", "Connection id filter").option("--status <status>", "Content status filter").action(async (contentId, options) => {
|
|
17194
17710
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17195
17711
|
const result = responseOrThrow(await context.client.GET("/v1/content/{content_id}", {
|
|
17196
17712
|
params: {
|
|
@@ -17204,16 +17720,91 @@ var createProgram = ({
|
|
|
17204
17720
|
})
|
|
17205
17721
|
}
|
|
17206
17722
|
}));
|
|
17207
|
-
writeOutput(context.io, context.outputMode, result,
|
|
17723
|
+
writeOutput(context.io, context.outputMode, result, formatSnsContent(result));
|
|
17208
17724
|
});
|
|
17209
|
-
content.command("publish").description("Publish or schedule SNS content").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (options) => {
|
|
17725
|
+
content.command("publish").description("Publish or schedule SNS content").requiredOption("--file <path>", "JSON request body file, or - for stdin").option("--scheduled-for <timestamp>", "Schedule publish at an ISO timestamp, for example 2026-06-18T09:00:00Z").action(async (options) => {
|
|
17210
17726
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17211
|
-
const body = readObjectBody(await readJsonInput(options.file), "SNS content publish body");
|
|
17727
|
+
const body = applyScheduledForOption(readObjectBody(await readJsonInput(options.file), "SNS content publish body"), options.scheduledFor);
|
|
17212
17728
|
const result = responseOrThrow(await context.client.POST("/v1/content", {
|
|
17213
17729
|
body: apiBody(body)
|
|
17214
17730
|
}));
|
|
17731
|
+
writeOutput(context.io, context.outputMode, result, formatContentPublishResult(result, body));
|
|
17732
|
+
});
|
|
17733
|
+
const media = program2.command("media").description("Upload media for SNS publishing");
|
|
17734
|
+
withBrandRef(media.command("upload").description("Upload an image or video file").argument("<file>", "Media file path")).option("--content-type <content_type>", "Override detected MIME type").action(async (file2, options) => {
|
|
17735
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17736
|
+
await runMediaUploadCommand({
|
|
17737
|
+
brandRef: options.brandRef,
|
|
17738
|
+
client: context.client,
|
|
17739
|
+
contentType: options.contentType,
|
|
17740
|
+
fetch,
|
|
17741
|
+
file: file2,
|
|
17742
|
+
io: context.io,
|
|
17743
|
+
outputMode: context.outputMode
|
|
17744
|
+
});
|
|
17745
|
+
});
|
|
17746
|
+
const brandImages = program2.command("brand-images").description("Manage brand images shown in the web brand image screen");
|
|
17747
|
+
withBrandRef(brandImages.command("list").description("List brand images")).option("--type <type>", "Image type filter: logo, banner, product, gallery, other").action(async (options) => {
|
|
17748
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17749
|
+
const type = options.type ? parseBrandImageType(options.type) : undefined;
|
|
17750
|
+
const result = responseOrThrow(await context.client.GET("/v1/brand-images", {
|
|
17751
|
+
params: {
|
|
17752
|
+
query: {
|
|
17753
|
+
brand_ref: options.brandRef,
|
|
17754
|
+
...type ? { type } : {}
|
|
17755
|
+
}
|
|
17756
|
+
}
|
|
17757
|
+
}));
|
|
17215
17758
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17216
17759
|
});
|
|
17760
|
+
withBrandRef(brandImages.command("upload").description("Upload and register a brand image").argument("<file>", "Image file path")).option("--type <type>", "Image type: logo, banner, product, gallery, other").option("--alt <text>", "Alternative text").option("--description <text>", "Image description").option("--content-type <content_type>", "Override detected MIME type").action(async (file2, options) => {
|
|
17761
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17762
|
+
await runBrandImageUploadCommand({
|
|
17763
|
+
alt: options.alt,
|
|
17764
|
+
brandRef: options.brandRef,
|
|
17765
|
+
client: context.client,
|
|
17766
|
+
contentType: options.contentType,
|
|
17767
|
+
description: options.description,
|
|
17768
|
+
fetch,
|
|
17769
|
+
file: file2,
|
|
17770
|
+
io: context.io,
|
|
17771
|
+
outputMode: context.outputMode,
|
|
17772
|
+
type: options.type ? parseBrandImageType(options.type) : undefined
|
|
17773
|
+
});
|
|
17774
|
+
});
|
|
17775
|
+
withBrandRef(brandImages.command("update").description("Update brand image metadata").argument("<image_ref>", "Brand image reference")).option("--type <type>", "Image type: logo, banner, product, gallery, other").option("--alt <text>", "Alternative text").option("--description <text>", "Image description").action(async (imageRef, options) => {
|
|
17776
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17777
|
+
const result = responseOrThrow(await context.client.PATCH("/v1/brand-images/{image_ref}", {
|
|
17778
|
+
body: apiBody({
|
|
17779
|
+
...options.type ? { type: parseBrandImageType(options.type) } : {},
|
|
17780
|
+
...options.alt !== undefined ? { alt: options.alt } : {},
|
|
17781
|
+
...options.description !== undefined ? { description: options.description } : {}
|
|
17782
|
+
}),
|
|
17783
|
+
params: {
|
|
17784
|
+
path: {
|
|
17785
|
+
image_ref: imageRef
|
|
17786
|
+
},
|
|
17787
|
+
query: {
|
|
17788
|
+
brand_ref: options.brandRef
|
|
17789
|
+
}
|
|
17790
|
+
}
|
|
17791
|
+
}));
|
|
17792
|
+
writeOutput(context.io, context.outputMode, result, `Brand image updated: ${result.image_ref}`);
|
|
17793
|
+
});
|
|
17794
|
+
withBrandRef(brandImages.command("delete").description("Delete brand image metadata").argument("<image_ref>", "Brand image reference")).action(async (imageRef, options) => {
|
|
17795
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17796
|
+
const result = responseOrThrow(await context.client.DELETE("/v1/brand-images/{image_ref}", {
|
|
17797
|
+
params: {
|
|
17798
|
+
path: {
|
|
17799
|
+
image_ref: imageRef
|
|
17800
|
+
},
|
|
17801
|
+
query: {
|
|
17802
|
+
brand_ref: options.brandRef
|
|
17803
|
+
}
|
|
17804
|
+
}
|
|
17805
|
+
}));
|
|
17806
|
+
writeOutput(context.io, context.outputMode, result, `Brand image deleted: ${result.image_ref}`);
|
|
17807
|
+
});
|
|
17217
17808
|
withBrandRef(content.command("cancel").description("Cancel scheduled SNS content").argument("<content_id>", "Content id")).option("--file <path>", "JSON request body file, or - for stdin").action(async (contentId, options) => {
|
|
17218
17809
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17219
17810
|
const body = await readBodyOrBrandRef(options, "SNS content cancel body");
|
|
@@ -17241,7 +17832,7 @@ var createProgram = ({
|
|
|
17241
17832
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17242
17833
|
});
|
|
17243
17834
|
const references = program2.command("references").description("Create SNS references");
|
|
17244
|
-
references.command("create").description("Create or get a public
|
|
17835
|
+
references.command("create").description("Create or get a public post reference where direct provider lookup is supported").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (options) => {
|
|
17245
17836
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17246
17837
|
const body = readObjectBody(await readJsonInput(options.file), "SNS reference body");
|
|
17247
17838
|
const result = responseOrThrow(await context.client.POST("/v1/references", {
|
|
@@ -17249,8 +17840,8 @@ var createProgram = ({
|
|
|
17249
17840
|
}));
|
|
17250
17841
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17251
17842
|
});
|
|
17252
|
-
const posts = program2.command("posts").description("Read posts");
|
|
17253
|
-
withBrandRef(posts.command("list").description("List posts")).action(async (options) => {
|
|
17843
|
+
const posts = program2.command("posts").description("Read or delete provider posts that exist on SNS providers");
|
|
17844
|
+
withBrandRef(posts.command("list").description("List provider posts")).action(async (options) => {
|
|
17254
17845
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17255
17846
|
const result = responseOrThrow(await context.client.GET("/v1/posts", {
|
|
17256
17847
|
params: queryParams({
|
|
@@ -17259,7 +17850,7 @@ var createProgram = ({
|
|
|
17259
17850
|
}));
|
|
17260
17851
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17261
17852
|
});
|
|
17262
|
-
withBrandRef(posts.command("get").description("Get post").argument("<post_id>", "Post id")).action(async (postId, options) => {
|
|
17853
|
+
withBrandRef(posts.command("get").description("Get a provider post").argument("<post_id>", "Post id returned by posts list")).action(async (postId, options) => {
|
|
17263
17854
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17264
17855
|
const result = responseOrThrow(await context.client.GET("/v1/posts/{post_id}", {
|
|
17265
17856
|
params: {
|
|
@@ -17273,7 +17864,7 @@ var createProgram = ({
|
|
|
17273
17864
|
}));
|
|
17274
17865
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17275
17866
|
});
|
|
17276
|
-
withBrandRef(posts.command("delete").description("Delete post").argument("<post_id>", "Post id")).action(async (postId, options) => {
|
|
17867
|
+
withBrandRef(posts.command("delete").description("Delete a provider post").argument("<post_id>", "Post id returned by posts list")).action(async (postId, options) => {
|
|
17277
17868
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17278
17869
|
const result = responseOrThrow(await context.client.DELETE("/v1/posts/{post_id}", {
|
|
17279
17870
|
params: {
|
|
@@ -17287,7 +17878,7 @@ var createProgram = ({
|
|
|
17287
17878
|
}));
|
|
17288
17879
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17289
17880
|
});
|
|
17290
|
-
const comments = program2.command("comments").description("Read comments");
|
|
17881
|
+
const comments = program2.command("comments").description("Read and create comments");
|
|
17291
17882
|
withBrandRef(comments.command("list").description("List comments")).action(async (options) => {
|
|
17292
17883
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17293
17884
|
const result = responseOrThrow(await context.client.GET("/v1/comments", {
|
|
@@ -17311,6 +17902,14 @@ var createProgram = ({
|
|
|
17311
17902
|
}));
|
|
17312
17903
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17313
17904
|
});
|
|
17905
|
+
comments.command("create").description("Create a top-level comment on a provider post").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (options) => {
|
|
17906
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17907
|
+
const body = readObjectBody(await readJsonInput(options.file), "Comment create body");
|
|
17908
|
+
const result = responseOrThrow(await context.client.POST("/v1/comments", {
|
|
17909
|
+
body: apiBody(body)
|
|
17910
|
+
}));
|
|
17911
|
+
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17912
|
+
});
|
|
17314
17913
|
comments.command("moderate").description("Create comment moderation action").argument("<comment_id>", "Comment id").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (commentId, options) => {
|
|
17315
17914
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17316
17915
|
const body = readObjectBody(await readJsonInput(options.file), "Comment moderation body");
|
|
@@ -17371,7 +17970,7 @@ var createProgram = ({
|
|
|
17371
17970
|
}));
|
|
17372
17971
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17373
17972
|
});
|
|
17374
|
-
withBrandRef(analytics.command("overview").description("Get
|
|
17973
|
+
withBrandRef(analytics.command("overview").description("Get compact brand-level analytics counters using the same default 30-day range as analytics get")).option("--from <date>", "Start date").option("--to <date>", "End date").action(async (options) => {
|
|
17375
17974
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17376
17975
|
const result = responseOrThrow(await context.client.GET("/v1/analytics/overview", {
|
|
17377
17976
|
params: queryParams({
|
|
@@ -17414,7 +18013,7 @@ var createProgram = ({
|
|
|
17414
18013
|
}));
|
|
17415
18014
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17416
18015
|
});
|
|
17417
|
-
webhooks.command("update").description("Update webhook subscription").argument("<webhook_id>", "Webhook id").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (webhookId, options) => {
|
|
18016
|
+
withBrandRef(webhooks.command("update").description("Update webhook subscription").argument("<webhook_id>", "Webhook id").requiredOption("--file <path>", "JSON request body file, or - for stdin")).action(async (webhookId, options) => {
|
|
17418
18017
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17419
18018
|
const body = readObjectBody(await readJsonInput(options.file), "Webhook update body");
|
|
17420
18019
|
const result = responseOrThrow(await context.client.PATCH("/v1/webhooks/{webhook_id}", {
|
|
@@ -17422,7 +18021,10 @@ var createProgram = ({
|
|
|
17422
18021
|
params: {
|
|
17423
18022
|
path: {
|
|
17424
18023
|
webhook_id: webhookId
|
|
17425
|
-
}
|
|
18024
|
+
},
|
|
18025
|
+
...queryParams({
|
|
18026
|
+
brand_ref: options.brandRef
|
|
18027
|
+
})
|
|
17426
18028
|
}
|
|
17427
18029
|
}));
|
|
17428
18030
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
@@ -17441,18 +18043,7 @@ var createProgram = ({
|
|
|
17441
18043
|
}));
|
|
17442
18044
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17443
18045
|
});
|
|
17444
|
-
|
|
17445
|
-
config2.command("show").description("Show resolved CLI config").action(async () => {
|
|
17446
|
-
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17447
|
-
const value = {
|
|
17448
|
-
api_key: redactApiKey(context.config.apiKey),
|
|
17449
|
-
base_url: context.config.baseUrl,
|
|
17450
|
-
config_path: context.config.configPath
|
|
17451
|
-
};
|
|
17452
|
-
writeOutput(context.io, context.outputMode, value, `base_url: ${value.base_url}
|
|
17453
|
-
api_key: ${value.api_key ?? "(not set)"}
|
|
17454
|
-
config_path: ${value.config_path}`);
|
|
17455
|
-
});
|
|
18046
|
+
registerConfigCommands({ env, fetch, io, program: program2, prompt });
|
|
17456
18047
|
const openapi = program2.command("openapi").description("Manage api-public OpenAPI schema");
|
|
17457
18048
|
openapi.command("pull").description("Pull the api-public OpenAPI schema").option("--dry-run", "Fetch and report without writing").option("--output <path>", "Snapshot output path").action(async (options) => {
|
|
17458
18049
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
@@ -17485,7 +18076,7 @@ var resolveCommandContext = async ({
|
|
|
17485
18076
|
configPath,
|
|
17486
18077
|
env
|
|
17487
18078
|
});
|
|
17488
|
-
const
|
|
18079
|
+
const outputMode2 = options.json ? "json" : "human";
|
|
17489
18080
|
return {
|
|
17490
18081
|
client: createApiPublicClient({
|
|
17491
18082
|
apiKey: config2.apiKey,
|
|
@@ -17494,7 +18085,7 @@ var resolveCommandContext = async ({
|
|
|
17494
18085
|
}),
|
|
17495
18086
|
config: config2,
|
|
17496
18087
|
io,
|
|
17497
|
-
outputMode
|
|
18088
|
+
outputMode: outputMode2
|
|
17498
18089
|
};
|
|
17499
18090
|
};
|
|
17500
18091
|
var safeDefaultConfigPath = (env) => defaultConfigPath({ env });
|
|
@@ -17573,6 +18164,12 @@ var readObjectBody = (value, name) => {
|
|
|
17573
18164
|
return value;
|
|
17574
18165
|
};
|
|
17575
18166
|
var apiBody = (body) => body;
|
|
18167
|
+
var brandImageTypes = ["logo", "banner", "product", "gallery", "other"];
|
|
18168
|
+
var parseBrandImageType = (value) => {
|
|
18169
|
+
if (brandImageTypes.includes(value))
|
|
18170
|
+
return value;
|
|
18171
|
+
throw new Error(`Unsupported brand image type: ${value}`);
|
|
18172
|
+
};
|
|
17576
18173
|
var readBodyOrBrandRef = async (options, name) => {
|
|
17577
18174
|
if (options.file) {
|
|
17578
18175
|
return readObjectBody(await readJsonInput(options.file), name);
|
|
@@ -17581,17 +18178,17 @@ var readBodyOrBrandRef = async (options, name) => {
|
|
|
17581
18178
|
brand_ref: options.brandRef
|
|
17582
18179
|
};
|
|
17583
18180
|
};
|
|
17584
|
-
var
|
|
18181
|
+
var fetchHealth2 = async ({ baseUrl, fetch }) => {
|
|
17585
18182
|
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/v1/health`);
|
|
17586
18183
|
if (!response.ok)
|
|
17587
18184
|
throw new Error(`Health check failed with HTTP ${response.status}.`);
|
|
17588
18185
|
const body = await response.json();
|
|
17589
|
-
if (!
|
|
18186
|
+
if (!isHealthResponse2(body)) {
|
|
17590
18187
|
throw new Error("Health check response was invalid.");
|
|
17591
18188
|
}
|
|
17592
18189
|
return body;
|
|
17593
18190
|
};
|
|
17594
|
-
var
|
|
18191
|
+
var isHealthResponse2 = (value) => {
|
|
17595
18192
|
if (!value || typeof value !== "object")
|
|
17596
18193
|
return false;
|
|
17597
18194
|
const record2 = value;
|