@anakonn/ankk 0.1.0-beta.1 → 0.1.2
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 +87 -19
- package/dist/index.js +501 -91
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,32 +24,77 @@ Global Bun installs can upgrade through the CLI:
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
ankk upgrade
|
|
27
|
-
ankk upgrade --tag beta
|
|
28
|
-
ankk upgrade --tag latest
|
|
29
27
|
ankk upgrade --dry-run
|
|
30
28
|
```
|
|
31
29
|
|
|
32
|
-
The
|
|
33
|
-
|
|
30
|
+
The CLI uses the `latest` npm dist-tag. Release versions use three-part numeric
|
|
31
|
+
SemVer such as `0.1.1`.
|
|
34
32
|
|
|
35
|
-
##
|
|
33
|
+
## Initial Setup
|
|
34
|
+
|
|
35
|
+
Create `~/.ankk/config.json` after installing the CLI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
ankk config setup
|
|
39
|
+
```
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
promoted to `main`, update `apps/cli/package.json` to the release version, merge
|
|
39
|
-
that version to `main`, then push a matching tag:
|
|
41
|
+
For CI or other non-interactive environments:
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
|
-
|
|
43
|
-
git switch main
|
|
44
|
-
git pull --ff-only origin main
|
|
45
|
-
git tag ankk-cli-v0.1.0-beta.1
|
|
46
|
-
git push origin ankk-cli-v0.1.0-beta.1
|
|
44
|
+
ANKK_API_KEY=spk_... ankk config setup --base-url https://api-public.ankk.app --api-key-env ANKK_API_KEY --yes
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
The
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
|
|
88
|
+
## Publish
|
|
89
|
+
|
|
90
|
+
Public npm publish runs automatically when CLI-related changes reach the
|
|
91
|
+
repository `main` branch. Before merging CLI changes to `main`, update
|
|
92
|
+
`apps/cli/package.json` to the release version.
|
|
93
|
+
|
|
94
|
+
The CI job `publish:cli:npm` validates that `apps/cli/package.json` uses
|
|
95
|
+
three-part numeric SemVer, that the version is not already published, and then
|
|
96
|
+
publishes with npm dist-tag `latest`. If the version was already published, the
|
|
97
|
+
job fails so the missing version bump is visible.
|
|
53
98
|
|
|
54
99
|
The current repository is hosted on self-managed GitLab. npm Trusted Publishing
|
|
55
100
|
is preferred, but npm currently documents GitLab support for GitLab.com shared
|
|
@@ -62,11 +107,11 @@ npm token with the smallest practical scope:
|
|
|
62
107
|
- Organization access: none unless npm requires it for the `@anakonn` scope.
|
|
63
108
|
- Expiration: finite.
|
|
64
109
|
- GitLab variable: `NPM_TOKEN`, masked and protected.
|
|
65
|
-
- GitLab protected
|
|
110
|
+
- GitLab protected branch: `main`.
|
|
66
111
|
|
|
67
112
|
If this is the first ever publish and npm cannot scope a token to an
|
|
68
113
|
unpublished package, publish `@anakonn/ankk` once manually with 2FA, then
|
|
69
|
-
replace CI with a package-scoped token for later
|
|
114
|
+
replace CI with a package-scoped token for later publishes.
|
|
70
115
|
|
|
71
116
|
When publishing moves to a supported Trusted Publishing runner, configure npm
|
|
72
117
|
Trusted Publisher with:
|
|
@@ -97,6 +142,29 @@ Use live production pull only for dogfooding or deployed-contract comparison:
|
|
|
97
142
|
(cd apps/cli && bun run src/entry/index.ts --json openapi pull --dry-run)
|
|
98
143
|
```
|
|
99
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
|
+
|
|
100
168
|
## Smoke
|
|
101
169
|
|
|
102
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.2",
|
|
2161
2161
|
description: "Bun-first CLI for the ankk public API.",
|
|
2162
2162
|
private: false,
|
|
2163
2163
|
license: "UNLICENSED",
|
|
@@ -2684,6 +2684,23 @@ 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/config-command.ts
|
|
2701
|
+
import { createInterface } from "readline/promises";
|
|
2702
|
+
import { Writable } from "stream";
|
|
2703
|
+
|
|
2687
2704
|
// src/config.ts
|
|
2688
2705
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2689
2706
|
import { dirname, join } from "path";
|
|
@@ -16990,6 +17007,23 @@ var loadConfig = async (path) => {
|
|
|
16990
17007
|
throw error51;
|
|
16991
17008
|
}
|
|
16992
17009
|
};
|
|
17010
|
+
var configFileExists = async (path) => {
|
|
17011
|
+
try {
|
|
17012
|
+
await readFile(path, "utf8");
|
|
17013
|
+
return true;
|
|
17014
|
+
} catch (error51) {
|
|
17015
|
+
if (isMissingFileError(error51))
|
|
17016
|
+
return false;
|
|
17017
|
+
throw error51;
|
|
17018
|
+
}
|
|
17019
|
+
};
|
|
17020
|
+
var writeConfig = async (path, config2) => {
|
|
17021
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
17022
|
+
await writeFile(path, `${JSON.stringify(config2, null, 2)}
|
|
17023
|
+
`, {
|
|
17024
|
+
mode: 384
|
|
17025
|
+
});
|
|
17026
|
+
};
|
|
16993
17027
|
var resolveRuntimeConfig = async ({
|
|
16994
17028
|
apiKey,
|
|
16995
17029
|
baseUrl,
|
|
@@ -17015,6 +17049,315 @@ var redactApiKey = (apiKey) => {
|
|
|
17015
17049
|
var normalizeBaseUrl = (value) => value.replace(/\/+$/, "");
|
|
17016
17050
|
var isMissingFileError = (error51) => typeof error51 === "object" && error51 !== null && ("code" in error51) && error51.code === "ENOENT";
|
|
17017
17051
|
|
|
17052
|
+
// src/output.ts
|
|
17053
|
+
var defaultIo = {
|
|
17054
|
+
stderr: process.stderr,
|
|
17055
|
+
stdout: process.stdout
|
|
17056
|
+
};
|
|
17057
|
+
var writeOutput = (io, mode, value, human) => {
|
|
17058
|
+
if (mode === "json") {
|
|
17059
|
+
io.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
17060
|
+
`);
|
|
17061
|
+
return;
|
|
17062
|
+
}
|
|
17063
|
+
io.stdout.write(`${human}
|
|
17064
|
+
`);
|
|
17065
|
+
};
|
|
17066
|
+
var writeError = (io, error51, mode = "human") => {
|
|
17067
|
+
if (mode === "json") {
|
|
17068
|
+
io.stderr.write(`${JSON.stringify(errorJson(error51), null, 2)}
|
|
17069
|
+
`);
|
|
17070
|
+
return;
|
|
17071
|
+
}
|
|
17072
|
+
if (error51 instanceof CliHttpError) {
|
|
17073
|
+
io.stderr.write(`${error51.message}
|
|
17074
|
+
`);
|
|
17075
|
+
return;
|
|
17076
|
+
}
|
|
17077
|
+
if (error51 instanceof Error) {
|
|
17078
|
+
io.stderr.write(`${error51.message}
|
|
17079
|
+
`);
|
|
17080
|
+
return;
|
|
17081
|
+
}
|
|
17082
|
+
io.stderr.write(`Unexpected CLI error.
|
|
17083
|
+
`);
|
|
17084
|
+
};
|
|
17085
|
+
var errorJson = (error51) => {
|
|
17086
|
+
if (error51 instanceof CliHttpError) {
|
|
17087
|
+
const body = error51.body && typeof error51.body === "object" && !Array.isArray(error51.body) ? error51.body : {};
|
|
17088
|
+
return {
|
|
17089
|
+
error: "http_error",
|
|
17090
|
+
message: error51.message,
|
|
17091
|
+
...body,
|
|
17092
|
+
status: error51.status
|
|
17093
|
+
};
|
|
17094
|
+
}
|
|
17095
|
+
if (error51 instanceof Error) {
|
|
17096
|
+
return {
|
|
17097
|
+
error: "cli_error",
|
|
17098
|
+
message: error51.message
|
|
17099
|
+
};
|
|
17100
|
+
}
|
|
17101
|
+
return {
|
|
17102
|
+
error: "unexpected_cli_error",
|
|
17103
|
+
message: "Unexpected CLI error."
|
|
17104
|
+
};
|
|
17105
|
+
};
|
|
17106
|
+
|
|
17107
|
+
// src/config-command.ts
|
|
17108
|
+
var registerConfigCommands = ({
|
|
17109
|
+
env,
|
|
17110
|
+
fetch,
|
|
17111
|
+
io,
|
|
17112
|
+
program: program2,
|
|
17113
|
+
prompt = createDefaultPrompt(io)
|
|
17114
|
+
}) => {
|
|
17115
|
+
const config2 = program2.command("config").description("Manage CLI config");
|
|
17116
|
+
config2.command("show").description("Show resolved CLI config").action(async () => {
|
|
17117
|
+
const options = program2.opts();
|
|
17118
|
+
const runtimeConfig = await resolveRuntimeConfig({
|
|
17119
|
+
apiKey: options.apiKey,
|
|
17120
|
+
baseUrl: options.baseUrl,
|
|
17121
|
+
configPath: options.config ?? defaultConfigPath({ env }),
|
|
17122
|
+
env
|
|
17123
|
+
});
|
|
17124
|
+
const value = {
|
|
17125
|
+
api_key: redactApiKey(runtimeConfig.apiKey),
|
|
17126
|
+
base_url: runtimeConfig.baseUrl,
|
|
17127
|
+
config_path: runtimeConfig.configPath
|
|
17128
|
+
};
|
|
17129
|
+
writeOutput(io, outputMode(options), value, `base_url: ${value.base_url}
|
|
17130
|
+
api_key: ${value.api_key ?? "(not set)"}
|
|
17131
|
+
config_path: ${value.config_path}`);
|
|
17132
|
+
});
|
|
17133
|
+
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) => {
|
|
17134
|
+
const globalOptions = program2.opts();
|
|
17135
|
+
const configPath = globalOptions.config ?? defaultConfigPath({ env });
|
|
17136
|
+
const existingConfig = await loadConfig(configPath);
|
|
17137
|
+
const exists = await configFileExists(configPath);
|
|
17138
|
+
const overwrite = exists ? Boolean(options.yes) || await prompt.confirm({
|
|
17139
|
+
defaultValue: false,
|
|
17140
|
+
message: `Overwrite existing config at ${configPath}?`
|
|
17141
|
+
}) : true;
|
|
17142
|
+
if (!overwrite) {
|
|
17143
|
+
const value2 = {
|
|
17144
|
+
config_path: configPath,
|
|
17145
|
+
wrote: false
|
|
17146
|
+
};
|
|
17147
|
+
writeOutput(io, outputMode(globalOptions), value2, `Config unchanged: ${configPath}`);
|
|
17148
|
+
return;
|
|
17149
|
+
}
|
|
17150
|
+
const baseUrl = await setupBaseUrl({
|
|
17151
|
+
defaultValue: globalOptions.baseUrl ?? env.ANKK_BASE_URL ?? existingConfig.base_url ?? defaultBaseUrl,
|
|
17152
|
+
optionValue: options.baseUrl,
|
|
17153
|
+
prompt,
|
|
17154
|
+
yes: Boolean(options.yes)
|
|
17155
|
+
});
|
|
17156
|
+
const apiKey = await setupApiKey({
|
|
17157
|
+
env,
|
|
17158
|
+
existingValue: existingConfig.api_key,
|
|
17159
|
+
globalValue: globalOptions.apiKey,
|
|
17160
|
+
optionEnvName: options.apiKeyEnv,
|
|
17161
|
+
optionValue: options.apiKey,
|
|
17162
|
+
prompt,
|
|
17163
|
+
yes: Boolean(options.yes)
|
|
17164
|
+
});
|
|
17165
|
+
await writeConfig(configPath, {
|
|
17166
|
+
api_key: apiKey,
|
|
17167
|
+
base_url: baseUrl
|
|
17168
|
+
});
|
|
17169
|
+
const health = options.skipHealth ? null : await fetchHealth({ baseUrl, fetch });
|
|
17170
|
+
const value = {
|
|
17171
|
+
api_key: redactApiKey(apiKey),
|
|
17172
|
+
base_url: baseUrl,
|
|
17173
|
+
config_path: configPath,
|
|
17174
|
+
health,
|
|
17175
|
+
next_commands: ["ankk brands list", "ankk config show"],
|
|
17176
|
+
wrote: true
|
|
17177
|
+
};
|
|
17178
|
+
writeOutput(io, outputMode(globalOptions), value, [
|
|
17179
|
+
`Config saved: ${configPath}`,
|
|
17180
|
+
`base_url: ${baseUrl}`,
|
|
17181
|
+
`api_key: ${redactApiKey(apiKey)}`,
|
|
17182
|
+
...health ? [`health: ${health.service} ${health.status}`] : [],
|
|
17183
|
+
"next: ankk brands list"
|
|
17184
|
+
].join(`
|
|
17185
|
+
`));
|
|
17186
|
+
});
|
|
17187
|
+
};
|
|
17188
|
+
var setupBaseUrl = async ({
|
|
17189
|
+
defaultValue,
|
|
17190
|
+
optionValue,
|
|
17191
|
+
prompt,
|
|
17192
|
+
yes
|
|
17193
|
+
}) => {
|
|
17194
|
+
const value = optionValue ?? (yes ? defaultValue : await prompt.text({ defaultValue, message: "API base URL" }));
|
|
17195
|
+
return normalizeBaseUrl2(value || defaultValue);
|
|
17196
|
+
};
|
|
17197
|
+
var setupApiKey = async ({
|
|
17198
|
+
env,
|
|
17199
|
+
existingValue,
|
|
17200
|
+
globalValue,
|
|
17201
|
+
optionEnvName,
|
|
17202
|
+
optionValue,
|
|
17203
|
+
prompt,
|
|
17204
|
+
yes
|
|
17205
|
+
}) => {
|
|
17206
|
+
const envValue = optionEnvName ? env[optionEnvName] : undefined;
|
|
17207
|
+
if (optionEnvName && !envValue) {
|
|
17208
|
+
throw new Error(`API key environment variable ${optionEnvName} is not set.`);
|
|
17209
|
+
}
|
|
17210
|
+
const value = optionValue ?? globalValue ?? envValue ?? env.ANKK_API_KEY ?? (yes ? existingValue : await prompt.secret({ message: "API key" }));
|
|
17211
|
+
if (!value) {
|
|
17212
|
+
throw new Error("API key is required. Pass --api-key-env ANKK_API_KEY, --api-key, or set ANKK_API_KEY.");
|
|
17213
|
+
}
|
|
17214
|
+
return value;
|
|
17215
|
+
};
|
|
17216
|
+
var fetchHealth = async ({ baseUrl, fetch }) => {
|
|
17217
|
+
const response = await fetch(`${baseUrl}/v1/health`);
|
|
17218
|
+
if (!response.ok)
|
|
17219
|
+
throw new Error(`Config saved, but health check failed with HTTP ${response.status}.`);
|
|
17220
|
+
const body = await response.json();
|
|
17221
|
+
if (!isHealthResponse(body))
|
|
17222
|
+
throw new Error("Config saved, but health check response was invalid.");
|
|
17223
|
+
return body;
|
|
17224
|
+
};
|
|
17225
|
+
var createDefaultPrompt = (io) => {
|
|
17226
|
+
const prompt = {
|
|
17227
|
+
async confirm({ defaultValue = false, message }) {
|
|
17228
|
+
const suffix = defaultValue ? " [Y/n]" : " [y/N]";
|
|
17229
|
+
const answer = await prompt.text({ message: `${message}${suffix}` });
|
|
17230
|
+
const normalized = answer.trim().toLowerCase();
|
|
17231
|
+
if (!normalized)
|
|
17232
|
+
return defaultValue;
|
|
17233
|
+
return normalized === "y" || normalized === "yes";
|
|
17234
|
+
},
|
|
17235
|
+
async secret({ message }) {
|
|
17236
|
+
io.stderr.write(`${message}: `);
|
|
17237
|
+
const reader = createInterface({
|
|
17238
|
+
input: process.stdin,
|
|
17239
|
+
output: new MuteWritable,
|
|
17240
|
+
terminal: true
|
|
17241
|
+
});
|
|
17242
|
+
try {
|
|
17243
|
+
return (await reader.question("")).trim();
|
|
17244
|
+
} finally {
|
|
17245
|
+
reader.close();
|
|
17246
|
+
io.stderr.write(`
|
|
17247
|
+
`);
|
|
17248
|
+
}
|
|
17249
|
+
},
|
|
17250
|
+
async text({ defaultValue, message }) {
|
|
17251
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
17252
|
+
const reader = createInterface({
|
|
17253
|
+
input: process.stdin,
|
|
17254
|
+
output: process.stderr
|
|
17255
|
+
});
|
|
17256
|
+
try {
|
|
17257
|
+
const value = (await reader.question(`${message}${suffix}: `)).trim();
|
|
17258
|
+
return value || defaultValue || "";
|
|
17259
|
+
} finally {
|
|
17260
|
+
reader.close();
|
|
17261
|
+
}
|
|
17262
|
+
}
|
|
17263
|
+
};
|
|
17264
|
+
return prompt;
|
|
17265
|
+
};
|
|
17266
|
+
|
|
17267
|
+
class MuteWritable extends Writable {
|
|
17268
|
+
_write(_chunk, _encoding, callback) {
|
|
17269
|
+
callback();
|
|
17270
|
+
}
|
|
17271
|
+
}
|
|
17272
|
+
var normalizeBaseUrl2 = (value) => {
|
|
17273
|
+
const normalized = value.replace(/\/+$/, "");
|
|
17274
|
+
new URL(normalized);
|
|
17275
|
+
return normalized;
|
|
17276
|
+
};
|
|
17277
|
+
var outputMode = (options) => options.json ? "json" : "human";
|
|
17278
|
+
var isHealthResponse = (value) => {
|
|
17279
|
+
if (!value || typeof value !== "object")
|
|
17280
|
+
return false;
|
|
17281
|
+
const record2 = value;
|
|
17282
|
+
return typeof record2.service === "string" && typeof record2.status === "string";
|
|
17283
|
+
};
|
|
17284
|
+
|
|
17285
|
+
// src/content-publish.ts
|
|
17286
|
+
var applyScheduledForOption = (body, scheduledFor) => {
|
|
17287
|
+
if (!scheduledFor)
|
|
17288
|
+
return body;
|
|
17289
|
+
const normalized = normalizeIsoTimestamp(scheduledFor, "--scheduled-for");
|
|
17290
|
+
const existing = body.scheduled_for;
|
|
17291
|
+
if (existing === undefined || existing === null) {
|
|
17292
|
+
return { ...body, scheduled_for: normalized };
|
|
17293
|
+
}
|
|
17294
|
+
if (typeof existing !== "string") {
|
|
17295
|
+
throw new Error("scheduled_for in SNS content publish body must be an ISO timestamp string or null.");
|
|
17296
|
+
}
|
|
17297
|
+
if (normalizeIsoTimestamp(existing, "scheduled_for") !== normalized) {
|
|
17298
|
+
throw new Error("scheduled_for in SNS content publish body conflicts with --scheduled-for.");
|
|
17299
|
+
}
|
|
17300
|
+
return { ...body, scheduled_for: normalized };
|
|
17301
|
+
};
|
|
17302
|
+
var formatContentPublishResult = (response, body) => {
|
|
17303
|
+
const lines = [`content_id: ${response.content_id}`, `publish_job_id: ${response.publish_job_id}`, `status: ${response.status}`];
|
|
17304
|
+
if (response.scheduled_for) {
|
|
17305
|
+
lines.push(`scheduled_for: ${response.scheduled_for}`);
|
|
17306
|
+
const brandRef = typeof body.brand_ref === "string" ? body.brand_ref : "<brand_ref>";
|
|
17307
|
+
lines.push(`next: ankk content cancel ${response.content_id} --brand-ref ${brandRef}`);
|
|
17308
|
+
}
|
|
17309
|
+
return lines.join(`
|
|
17310
|
+
`);
|
|
17311
|
+
};
|
|
17312
|
+
var normalizeIsoTimestamp = (value, name) => {
|
|
17313
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)) {
|
|
17314
|
+
throw new Error(`${name} must be an ISO timestamp, for example 2026-06-18T09:00:00Z.`);
|
|
17315
|
+
}
|
|
17316
|
+
const timestamp = new Date(value);
|
|
17317
|
+
if (Number.isNaN(timestamp.getTime())) {
|
|
17318
|
+
throw new Error(`${name} must be an ISO timestamp, for example 2026-06-18T09:00:00Z.`);
|
|
17319
|
+
}
|
|
17320
|
+
return timestamp.toISOString();
|
|
17321
|
+
};
|
|
17322
|
+
|
|
17323
|
+
// src/content-output.ts
|
|
17324
|
+
var formatSnsContent = (response) => {
|
|
17325
|
+
const lines = [
|
|
17326
|
+
`content_id: ${response.content_id}`,
|
|
17327
|
+
`status: ${response.status}`,
|
|
17328
|
+
`sns_type: ${response.sns_type}`,
|
|
17329
|
+
`connection_id: ${response.connection_id}`
|
|
17330
|
+
];
|
|
17331
|
+
if (response.title)
|
|
17332
|
+
lines.push(`title: ${response.title}`);
|
|
17333
|
+
if (response.provider_post_id)
|
|
17334
|
+
lines.push(`provider_post_id: ${response.provider_post_id}`);
|
|
17335
|
+
if (response.provider_post_status)
|
|
17336
|
+
lines.push(`provider_post_status: ${response.provider_post_status}`);
|
|
17337
|
+
if (response.permalink)
|
|
17338
|
+
lines.push(`permalink: ${response.permalink}`);
|
|
17339
|
+
if (response.latest_publish_job) {
|
|
17340
|
+
lines.push(`latest_publish_job: ${response.latest_publish_job.status} (${response.latest_publish_job.publish_job_id})`);
|
|
17341
|
+
if (response.latest_publish_job.scheduled_for)
|
|
17342
|
+
lines.push(`scheduled_for: ${response.latest_publish_job.scheduled_for}`);
|
|
17343
|
+
if (response.latest_publish_job.last_error_code)
|
|
17344
|
+
lines.push(`last_error_code: ${response.latest_publish_job.last_error_code}`);
|
|
17345
|
+
if (response.latest_publish_job.last_error_message)
|
|
17346
|
+
lines.push(`last_error_message: ${response.latest_publish_job.last_error_message}`);
|
|
17347
|
+
}
|
|
17348
|
+
if (response.latest_delete_job) {
|
|
17349
|
+
lines.push(`latest_delete_job: ${response.latest_delete_job.status} (${response.latest_delete_job.delete_job_id})`);
|
|
17350
|
+
if (response.latest_delete_job.scheduled_for)
|
|
17351
|
+
lines.push(`delete_scheduled_for: ${response.latest_delete_job.scheduled_for}`);
|
|
17352
|
+
if (response.latest_delete_job.last_error_code)
|
|
17353
|
+
lines.push(`delete_last_error_code: ${response.latest_delete_job.last_error_code}`);
|
|
17354
|
+
if (response.latest_delete_job.last_error_message)
|
|
17355
|
+
lines.push(`delete_last_error_message: ${response.latest_delete_job.last_error_message}`);
|
|
17356
|
+
}
|
|
17357
|
+
return lines.join(`
|
|
17358
|
+
`);
|
|
17359
|
+
};
|
|
17360
|
+
|
|
17018
17361
|
// src/json-input.ts
|
|
17019
17362
|
import { readFile as readFile2 } from "fs/promises";
|
|
17020
17363
|
var readJsonInput = async (path, { readStdin = readProcessStdin } = {}) => {
|
|
@@ -17030,6 +17373,98 @@ var readJsonInput = async (path, { readStdin = readProcessStdin } = {}) => {
|
|
|
17030
17373
|
};
|
|
17031
17374
|
var readProcessStdin = async () => Bun.stdin.text();
|
|
17032
17375
|
|
|
17376
|
+
// src/media-upload.ts
|
|
17377
|
+
import { readFile as readFile3, stat } from "fs/promises";
|
|
17378
|
+
import { basename } from "path";
|
|
17379
|
+
var runMediaUploadCommand = async ({
|
|
17380
|
+
brandRef,
|
|
17381
|
+
client,
|
|
17382
|
+
contentType,
|
|
17383
|
+
fetch,
|
|
17384
|
+
file: file2,
|
|
17385
|
+
io,
|
|
17386
|
+
outputMode: outputMode2
|
|
17387
|
+
}) => {
|
|
17388
|
+
const upload = await readMediaUploadFile(file2, contentType);
|
|
17389
|
+
const prepared = responseOrThrow(await client.POST("/v1/media/uploads", {
|
|
17390
|
+
body: {
|
|
17391
|
+
brand_ref: brandRef,
|
|
17392
|
+
content_type: upload.contentType,
|
|
17393
|
+
filename: upload.filename,
|
|
17394
|
+
size: upload.size
|
|
17395
|
+
}
|
|
17396
|
+
}));
|
|
17397
|
+
const uploaded = await fetch(prepared.upload_url, {
|
|
17398
|
+
body: upload.data,
|
|
17399
|
+
headers: {
|
|
17400
|
+
"content-type": prepared.content_type
|
|
17401
|
+
},
|
|
17402
|
+
method: prepared.method
|
|
17403
|
+
});
|
|
17404
|
+
if (!uploaded.ok) {
|
|
17405
|
+
throw new Error(`Media upload failed with HTTP ${uploaded.status}.`);
|
|
17406
|
+
}
|
|
17407
|
+
writeOutput(io, outputMode2, {
|
|
17408
|
+
...prepared,
|
|
17409
|
+
uploaded: true
|
|
17410
|
+
}, `asset_ref: ${prepared.asset_ref}`);
|
|
17411
|
+
};
|
|
17412
|
+
var mediaUploadPolicies = {
|
|
17413
|
+
".gif": { contentType: "image/gif", maxSize: 20 * 1024 * 1024 },
|
|
17414
|
+
".jpeg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
17415
|
+
".jpg": { contentType: "image/jpeg", maxSize: 20 * 1024 * 1024 },
|
|
17416
|
+
".mov": { contentType: "video/quicktime", maxSize: 500 * 1024 * 1024 },
|
|
17417
|
+
".mp4": { contentType: "video/mp4", maxSize: 500 * 1024 * 1024 },
|
|
17418
|
+
".png": { contentType: "image/png", maxSize: 20 * 1024 * 1024 },
|
|
17419
|
+
".webm": { contentType: "video/webm", maxSize: 500 * 1024 * 1024 },
|
|
17420
|
+
".webp": { contentType: "image/webp", maxSize: 20 * 1024 * 1024 }
|
|
17421
|
+
};
|
|
17422
|
+
var readMediaUploadFile = async (file2, contentTypeOverride) => {
|
|
17423
|
+
const filename = basename(file2);
|
|
17424
|
+
const extension = mediaExtension(filename);
|
|
17425
|
+
const extensionPolicy = mediaUploadPolicies[extension];
|
|
17426
|
+
const contentType = contentTypeOverride?.trim().toLowerCase() || extensionPolicy.contentType;
|
|
17427
|
+
const contentTypePolicy = mediaUploadPolicyForContentType(contentType);
|
|
17428
|
+
if (!contentTypePolicy) {
|
|
17429
|
+
throw new Error(`Unsupported media content type: ${contentType}`);
|
|
17430
|
+
}
|
|
17431
|
+
if (!contentTypePolicy.extensions.includes(extension)) {
|
|
17432
|
+
throw new Error(`Media file extension does not match content type: ${filename} (${contentType})`);
|
|
17433
|
+
}
|
|
17434
|
+
const info = await stat(file2);
|
|
17435
|
+
if (!info.isFile()) {
|
|
17436
|
+
throw new Error(`Media path is not a file: ${file2}`);
|
|
17437
|
+
}
|
|
17438
|
+
if (info.size <= 0) {
|
|
17439
|
+
throw new Error("Media file is empty.");
|
|
17440
|
+
}
|
|
17441
|
+
if (info.size > contentTypePolicy.maxSize) {
|
|
17442
|
+
throw new Error(`Media file exceeds ${contentTypePolicy.maxSize} bytes.`);
|
|
17443
|
+
}
|
|
17444
|
+
return {
|
|
17445
|
+
contentType,
|
|
17446
|
+
data: await readFile3(file2),
|
|
17447
|
+
filename,
|
|
17448
|
+
size: info.size
|
|
17449
|
+
};
|
|
17450
|
+
};
|
|
17451
|
+
var mediaExtension = (filename) => {
|
|
17452
|
+
const index = filename.lastIndexOf(".");
|
|
17453
|
+
const extension = index > 0 ? filename.slice(index).toLowerCase() : "";
|
|
17454
|
+
if (extension in mediaUploadPolicies)
|
|
17455
|
+
return extension;
|
|
17456
|
+
throw new Error(`Unsupported media file extension: ${filename}`);
|
|
17457
|
+
};
|
|
17458
|
+
var mediaUploadPolicyForContentType = (contentType) => {
|
|
17459
|
+
const extensions = Object.entries(mediaUploadPolicies).filter(([, policy]) => policy.contentType === contentType).map(([extension]) => extension);
|
|
17460
|
+
if (extensions.length === 0)
|
|
17461
|
+
return null;
|
|
17462
|
+
return {
|
|
17463
|
+
extensions,
|
|
17464
|
+
maxSize: mediaUploadPolicies[extensions[0] ?? ".jpg"].maxSize
|
|
17465
|
+
};
|
|
17466
|
+
};
|
|
17467
|
+
|
|
17033
17468
|
// src/openapi.ts
|
|
17034
17469
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
17035
17470
|
import { dirname as dirname2 } from "path";
|
|
@@ -17052,49 +17487,24 @@ var writeOpenApiSnapshot = async (path, spec) => {
|
|
|
17052
17487
|
`);
|
|
17053
17488
|
};
|
|
17054
17489
|
|
|
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
17490
|
// src/program.ts
|
|
17085
17491
|
var runCli = async ({
|
|
17086
17492
|
argv,
|
|
17087
17493
|
commandRunner = runProcess,
|
|
17088
17494
|
env = process.env,
|
|
17089
17495
|
fetch = globalThis.fetch,
|
|
17090
|
-
io = defaultIo
|
|
17496
|
+
io = defaultIo,
|
|
17497
|
+
prompt
|
|
17091
17498
|
}) => {
|
|
17092
|
-
const program2 = createProgram({ commandRunner, env, fetch, io });
|
|
17499
|
+
const program2 = createProgram({ commandRunner, env, fetch, io, prompt });
|
|
17093
17500
|
try {
|
|
17094
17501
|
await program2.parseAsync(argv, { from: "user" });
|
|
17095
17502
|
return 0;
|
|
17096
17503
|
} catch (error51) {
|
|
17097
|
-
|
|
17504
|
+
if (isSuccessfulCommanderExit(error51)) {
|
|
17505
|
+
return 0;
|
|
17506
|
+
}
|
|
17507
|
+
writeError(io, error51, program2.opts().json ? "json" : "human");
|
|
17098
17508
|
return 1;
|
|
17099
17509
|
}
|
|
17100
17510
|
};
|
|
@@ -17102,26 +17512,27 @@ var createProgram = ({
|
|
|
17102
17512
|
commandRunner = runProcess,
|
|
17103
17513
|
env = process.env,
|
|
17104
17514
|
fetch = globalThis.fetch,
|
|
17105
|
-
io = defaultIo
|
|
17515
|
+
io = defaultIo,
|
|
17516
|
+
prompt
|
|
17106
17517
|
} = {}) => {
|
|
17107
17518
|
const program2 = new Command;
|
|
17108
|
-
program2
|
|
17519
|
+
configureCommanderOutput(program2, io);
|
|
17520
|
+
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
17521
|
program2.command("health").description("Check api-public health").action(async () => {
|
|
17110
17522
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17111
|
-
const result = await
|
|
17523
|
+
const result = await fetchHealth2({
|
|
17112
17524
|
baseUrl: context.config.baseUrl,
|
|
17113
17525
|
fetch
|
|
17114
17526
|
});
|
|
17115
17527
|
writeOutput(context.io, context.outputMode, result, `${result.service}: ${result.status}`);
|
|
17116
17528
|
});
|
|
17117
|
-
program2.command("upgrade").description("Upgrade the ankk CLI global installation").option("--
|
|
17118
|
-
const
|
|
17529
|
+
program2.command("upgrade").description("Upgrade the ankk CLI global installation").option("--dry-run", "Print the upgrade command without running it").action(async (options) => {
|
|
17530
|
+
const outputMode2 = program2.opts().json ? "json" : "human";
|
|
17119
17531
|
const result = await upgradeCli({
|
|
17120
17532
|
commandRunner,
|
|
17121
|
-
dryRun: Boolean(options.dryRun)
|
|
17122
|
-
tag: options.tag
|
|
17533
|
+
dryRun: Boolean(options.dryRun)
|
|
17123
17534
|
});
|
|
17124
|
-
writeOutput(io,
|
|
17535
|
+
writeOutput(io, outputMode2, result, options.dryRun ? `Would run: ${result.command.join(" ")}` : `Upgraded ${result.package} via ${result.tag}`);
|
|
17125
17536
|
});
|
|
17126
17537
|
const brands = program2.command("brands").description("Manage brands available to the API key");
|
|
17127
17538
|
brands.command("list").description("List brands available to the API key").action(async () => {
|
|
@@ -17179,8 +17590,8 @@ var createProgram = ({
|
|
|
17179
17590
|
}));
|
|
17180
17591
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17181
17592
|
});
|
|
17182
|
-
const content = program2.command("content").description("
|
|
17183
|
-
withBrandRef(content.command("list").description("List SNS
|
|
17593
|
+
const content = program2.command("content").description("Create, schedule, and manage Ankk SNS content work items");
|
|
17594
|
+
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) => {
|
|
17184
17595
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17185
17596
|
const result = responseOrThrow(await context.client.GET("/v1/content", {
|
|
17186
17597
|
params: queryParams({
|
|
@@ -17191,7 +17602,7 @@ var createProgram = ({
|
|
|
17191
17602
|
}));
|
|
17192
17603
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17193
17604
|
});
|
|
17194
|
-
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) => {
|
|
17605
|
+
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) => {
|
|
17195
17606
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17196
17607
|
const result = responseOrThrow(await context.client.GET("/v1/content/{content_id}", {
|
|
17197
17608
|
params: {
|
|
@@ -17205,15 +17616,28 @@ var createProgram = ({
|
|
|
17205
17616
|
})
|
|
17206
17617
|
}
|
|
17207
17618
|
}));
|
|
17208
|
-
writeOutput(context.io, context.outputMode, result,
|
|
17619
|
+
writeOutput(context.io, context.outputMode, result, formatSnsContent(result));
|
|
17209
17620
|
});
|
|
17210
|
-
content.command("publish").description("Publish or schedule SNS content").requiredOption("--file <path>", "JSON request body file, or - for stdin").action(async (options) => {
|
|
17621
|
+
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) => {
|
|
17211
17622
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17212
|
-
const body = readObjectBody(await readJsonInput(options.file), "SNS content publish body");
|
|
17623
|
+
const body = applyScheduledForOption(readObjectBody(await readJsonInput(options.file), "SNS content publish body"), options.scheduledFor);
|
|
17213
17624
|
const result = responseOrThrow(await context.client.POST("/v1/content", {
|
|
17214
17625
|
body: apiBody(body)
|
|
17215
17626
|
}));
|
|
17216
|
-
writeOutput(context.io, context.outputMode, result,
|
|
17627
|
+
writeOutput(context.io, context.outputMode, result, formatContentPublishResult(result, body));
|
|
17628
|
+
});
|
|
17629
|
+
const media = program2.command("media").description("Upload media for SNS publishing");
|
|
17630
|
+
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) => {
|
|
17631
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17632
|
+
await runMediaUploadCommand({
|
|
17633
|
+
brandRef: options.brandRef,
|
|
17634
|
+
client: context.client,
|
|
17635
|
+
contentType: options.contentType,
|
|
17636
|
+
fetch,
|
|
17637
|
+
file: file2,
|
|
17638
|
+
io: context.io,
|
|
17639
|
+
outputMode: context.outputMode
|
|
17640
|
+
});
|
|
17217
17641
|
});
|
|
17218
17642
|
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) => {
|
|
17219
17643
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
@@ -17242,7 +17666,7 @@ var createProgram = ({
|
|
|
17242
17666
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17243
17667
|
});
|
|
17244
17668
|
const references = program2.command("references").description("Create SNS references");
|
|
17245
|
-
references.command("create").description("Create or get a public
|
|
17669
|
+
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) => {
|
|
17246
17670
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17247
17671
|
const body = readObjectBody(await readJsonInput(options.file), "SNS reference body");
|
|
17248
17672
|
const result = responseOrThrow(await context.client.POST("/v1/references", {
|
|
@@ -17250,8 +17674,8 @@ var createProgram = ({
|
|
|
17250
17674
|
}));
|
|
17251
17675
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17252
17676
|
});
|
|
17253
|
-
const posts = program2.command("posts").description("Read posts");
|
|
17254
|
-
withBrandRef(posts.command("list").description("List posts")).action(async (options) => {
|
|
17677
|
+
const posts = program2.command("posts").description("Read or delete provider posts that exist on SNS providers");
|
|
17678
|
+
withBrandRef(posts.command("list").description("List provider posts")).action(async (options) => {
|
|
17255
17679
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17256
17680
|
const result = responseOrThrow(await context.client.GET("/v1/posts", {
|
|
17257
17681
|
params: queryParams({
|
|
@@ -17260,7 +17684,7 @@ var createProgram = ({
|
|
|
17260
17684
|
}));
|
|
17261
17685
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17262
17686
|
});
|
|
17263
|
-
withBrandRef(posts.command("get").description("Get post").argument("<post_id>", "Post id")).action(async (postId, options) => {
|
|
17687
|
+
withBrandRef(posts.command("get").description("Get a provider post").argument("<post_id>", "Post id returned by posts list")).action(async (postId, options) => {
|
|
17264
17688
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17265
17689
|
const result = responseOrThrow(await context.client.GET("/v1/posts/{post_id}", {
|
|
17266
17690
|
params: {
|
|
@@ -17274,7 +17698,7 @@ var createProgram = ({
|
|
|
17274
17698
|
}));
|
|
17275
17699
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17276
17700
|
});
|
|
17277
|
-
withBrandRef(posts.command("delete").description("Delete post").argument("<post_id>", "Post id")).action(async (postId, options) => {
|
|
17701
|
+
withBrandRef(posts.command("delete").description("Delete a provider post").argument("<post_id>", "Post id returned by posts list")).action(async (postId, options) => {
|
|
17278
17702
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17279
17703
|
const result = responseOrThrow(await context.client.DELETE("/v1/posts/{post_id}", {
|
|
17280
17704
|
params: {
|
|
@@ -17288,7 +17712,7 @@ var createProgram = ({
|
|
|
17288
17712
|
}));
|
|
17289
17713
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17290
17714
|
});
|
|
17291
|
-
const comments = program2.command("comments").description("Read comments");
|
|
17715
|
+
const comments = program2.command("comments").description("Read and create comments");
|
|
17292
17716
|
withBrandRef(comments.command("list").description("List comments")).action(async (options) => {
|
|
17293
17717
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17294
17718
|
const result = responseOrThrow(await context.client.GET("/v1/comments", {
|
|
@@ -17312,6 +17736,14 @@ var createProgram = ({
|
|
|
17312
17736
|
}));
|
|
17313
17737
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17314
17738
|
});
|
|
17739
|
+
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) => {
|
|
17740
|
+
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17741
|
+
const body = readObjectBody(await readJsonInput(options.file), "Comment create body");
|
|
17742
|
+
const result = responseOrThrow(await context.client.POST("/v1/comments", {
|
|
17743
|
+
body: apiBody(body)
|
|
17744
|
+
}));
|
|
17745
|
+
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17746
|
+
});
|
|
17315
17747
|
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) => {
|
|
17316
17748
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17317
17749
|
const body = readObjectBody(await readJsonInput(options.file), "Comment moderation body");
|
|
@@ -17372,7 +17804,7 @@ var createProgram = ({
|
|
|
17372
17804
|
}));
|
|
17373
17805
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17374
17806
|
});
|
|
17375
|
-
withBrandRef(analytics.command("overview").description("Get
|
|
17807
|
+
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) => {
|
|
17376
17808
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17377
17809
|
const result = responseOrThrow(await context.client.GET("/v1/analytics/overview", {
|
|
17378
17810
|
params: queryParams({
|
|
@@ -17415,7 +17847,7 @@ var createProgram = ({
|
|
|
17415
17847
|
}));
|
|
17416
17848
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17417
17849
|
});
|
|
17418
|
-
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) => {
|
|
17850
|
+
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) => {
|
|
17419
17851
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17420
17852
|
const body = readObjectBody(await readJsonInput(options.file), "Webhook update body");
|
|
17421
17853
|
const result = responseOrThrow(await context.client.PATCH("/v1/webhooks/{webhook_id}", {
|
|
@@ -17423,7 +17855,10 @@ var createProgram = ({
|
|
|
17423
17855
|
params: {
|
|
17424
17856
|
path: {
|
|
17425
17857
|
webhook_id: webhookId
|
|
17426
|
-
}
|
|
17858
|
+
},
|
|
17859
|
+
...queryParams({
|
|
17860
|
+
brand_ref: options.brandRef
|
|
17861
|
+
})
|
|
17427
17862
|
}
|
|
17428
17863
|
}));
|
|
17429
17864
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
@@ -17442,18 +17877,7 @@ var createProgram = ({
|
|
|
17442
17877
|
}));
|
|
17443
17878
|
writeOutput(context.io, context.outputMode, result, formatGenericRead(result));
|
|
17444
17879
|
});
|
|
17445
|
-
|
|
17446
|
-
config2.command("show").description("Show resolved CLI config").action(async () => {
|
|
17447
|
-
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
17448
|
-
const value = {
|
|
17449
|
-
api_key: redactApiKey(context.config.apiKey),
|
|
17450
|
-
base_url: context.config.baseUrl,
|
|
17451
|
-
config_path: context.config.configPath
|
|
17452
|
-
};
|
|
17453
|
-
writeOutput(context.io, context.outputMode, value, `base_url: ${value.base_url}
|
|
17454
|
-
api_key: ${value.api_key ?? "(not set)"}
|
|
17455
|
-
config_path: ${value.config_path}`);
|
|
17456
|
-
});
|
|
17880
|
+
registerConfigCommands({ env, fetch, io, program: program2, prompt });
|
|
17457
17881
|
const openapi = program2.command("openapi").description("Manage api-public OpenAPI schema");
|
|
17458
17882
|
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) => {
|
|
17459
17883
|
const context = await resolveCommandContext({ env, fetch, io, program: program2 });
|
|
@@ -17486,7 +17910,7 @@ var resolveCommandContext = async ({
|
|
|
17486
17910
|
configPath,
|
|
17487
17911
|
env
|
|
17488
17912
|
});
|
|
17489
|
-
const
|
|
17913
|
+
const outputMode2 = options.json ? "json" : "human";
|
|
17490
17914
|
return {
|
|
17491
17915
|
client: createApiPublicClient({
|
|
17492
17916
|
apiKey: config2.apiKey,
|
|
@@ -17495,25 +17919,20 @@ var resolveCommandContext = async ({
|
|
|
17495
17919
|
}),
|
|
17496
17920
|
config: config2,
|
|
17497
17921
|
io,
|
|
17498
|
-
outputMode
|
|
17922
|
+
outputMode: outputMode2
|
|
17499
17923
|
};
|
|
17500
17924
|
};
|
|
17501
17925
|
var safeDefaultConfigPath = (env) => defaultConfigPath({ env });
|
|
17502
17926
|
var packageName = package_default.name;
|
|
17503
17927
|
var packageVersion = package_default.version;
|
|
17504
|
-
var upgradeCli = async ({
|
|
17505
|
-
|
|
17506
|
-
dryRun,
|
|
17507
|
-
tag
|
|
17508
|
-
}) => {
|
|
17509
|
-
const resolvedTag = resolveUpgradeTag(tag, packageVersion);
|
|
17510
|
-
const command = ["bun", "install", "-g", `${packageName}@${resolvedTag}`];
|
|
17928
|
+
var upgradeCli = async ({ commandRunner, dryRun }) => {
|
|
17929
|
+
const command = ["bun", "install", "-g", `${packageName}@latest`];
|
|
17511
17930
|
const result = {
|
|
17512
17931
|
command,
|
|
17513
17932
|
current_version: packageVersion,
|
|
17514
17933
|
dry_run: dryRun,
|
|
17515
17934
|
package: packageName,
|
|
17516
|
-
tag:
|
|
17935
|
+
tag: "latest"
|
|
17517
17936
|
};
|
|
17518
17937
|
if (dryRun)
|
|
17519
17938
|
return result;
|
|
@@ -17523,15 +17942,6 @@ var upgradeCli = async ({
|
|
|
17523
17942
|
}
|
|
17524
17943
|
return result;
|
|
17525
17944
|
};
|
|
17526
|
-
var resolveUpgradeTag = (tag, currentVersion) => {
|
|
17527
|
-
if (tag === undefined) {
|
|
17528
|
-
return currentVersion.includes("-") ? "beta" : "latest";
|
|
17529
|
-
}
|
|
17530
|
-
if (tag === "beta" || tag === "latest") {
|
|
17531
|
-
return tag;
|
|
17532
|
-
}
|
|
17533
|
-
throw new Error('Upgrade tag must be either "beta" or "latest".');
|
|
17534
|
-
};
|
|
17535
17945
|
var runProcess = async (command, args) => {
|
|
17536
17946
|
const process3 = Bun.spawn([command, ...args], {
|
|
17537
17947
|
stderr: "inherit",
|
|
@@ -17596,17 +18006,17 @@ var readBodyOrBrandRef = async (options, name) => {
|
|
|
17596
18006
|
brand_ref: options.brandRef
|
|
17597
18007
|
};
|
|
17598
18008
|
};
|
|
17599
|
-
var
|
|
18009
|
+
var fetchHealth2 = async ({ baseUrl, fetch }) => {
|
|
17600
18010
|
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/v1/health`);
|
|
17601
18011
|
if (!response.ok)
|
|
17602
18012
|
throw new Error(`Health check failed with HTTP ${response.status}.`);
|
|
17603
18013
|
const body = await response.json();
|
|
17604
|
-
if (!
|
|
18014
|
+
if (!isHealthResponse2(body)) {
|
|
17605
18015
|
throw new Error("Health check response was invalid.");
|
|
17606
18016
|
}
|
|
17607
18017
|
return body;
|
|
17608
18018
|
};
|
|
17609
|
-
var
|
|
18019
|
+
var isHealthResponse2 = (value) => {
|
|
17610
18020
|
if (!value || typeof value !== "object")
|
|
17611
18021
|
return false;
|
|
17612
18022
|
const record2 = value;
|