@mirage-cli/contentdynamite-cli 0.0.0-stage → 1.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @mirage-cli/contentdynamite-cli
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - The product's own `ve-dynamite` CLI against the Content Dynamite Workers API, replacing the 0.1.0 port of the Python CLI. First npm release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Brufsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,73 @@
1
- # Temporary Holding Version
1
+ # @mirage-cli/contentdynamite-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `ve-dynamite`, the Content Dynamite CLI: SEO articles, Google Business Profile posts, BOFU landing pages, company profiles, ICP, design systems, part-by-part revisions and featured images, over the Content Dynamite `/api/v1` JSON API. It talks to `https://content-dynamite.alexthecomputer1.workers.dev` unless `--url` or `VE_DYNAMITE_API_URL` names another host.
4
+
5
+ Stdout is always indented JSON, so pipe it to `jq`. Warnings, progress and hints go to stderr. The exit code is 0 on success and 1 on any error.
6
+
7
+ > **Money warning:** several commands spend real money (LLM, SERP, crawling, image credits) on every call, and nothing rate limits them. Their help says so and they print a warning when they run. Never script them in a loop.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm i -g @mirage-cli/contentdynamite-cli # or: bun add -g @mirage-cli/contentdynamite-cli
13
+ ve-dynamite --help
14
+ ```
15
+
16
+ Runs on Node 18+ or bun. The only dependencies are `commander` and `zod`.
17
+
18
+ ## Sign in
19
+
20
+ ```bash
21
+ ve-dynamite login --email you@example.com --password <pw> # stores a 24h JWT in ~/.config/ve-dynamite/
22
+ ve-dynamite whoami
23
+ ve-dynamite tokens create --name ci-runner [--expires-days 30] # a long lived ved_ token for unattended use
24
+ ve-dynamite tokens use ved_xxxxxxxx
25
+ ```
26
+
27
+ Or pass a token per call: `--token ved_xxx` or `VE_DYNAMITE_TOKEN=ved_xxx`. Precedence is `--token`, then `VE_DYNAMITE_TOKEN`, then the stored login. `VE_DYNAMITE_CONFIG_DIR` moves the stored login.
28
+
29
+ ## Commands
30
+
31
+ | Group | Commands |
32
+ |---|---|
33
+ | root | `login`, `logout`, `whoami`, `upload <file> --type image\|csv` |
34
+ | `tokens` | `create`, `list`, `revoke <id>`, `use <token>` |
35
+ | `users` (admins) | `list`, `set-role <id> --role admin\|normal` |
36
+ | `profiles` | `create`, `list`, `get`, `update`, `delete` |
37
+ | `icp` | `show`, `export`, `regenerate`, `update` |
38
+ | `categories` | `list`, `add` |
39
+ | `design-system` | `show`, `set <profile-id> <zip>`, `rescan`, `clear` |
40
+ | `articles` | `write`, `research-prompt`, `get`, `list`, `watch`, `update`, `delete`, `download`, `export-docs`, `export`, `parts`, `reject`, `revisions`, `provenance-audit`, `clean-provenance`, `embed-audit`, `strip-embeds` |
41
+ | `revisions` | `list`, `get`, `watch`, `verdict` |
42
+ | `batches` | `create`, `list`, `get`, `watch`, `delete` |
43
+ | `gbp` | `write`, `batch`, `list`, `get`, `watch`, `update`, `regenerate`, `delete`, `image-options` |
44
+ | `landing-pages` | `write`, `get`, `list`, `watch`, `update`, `delete`, `export`, `deck`, `versions`, `research`, `audit`, `reiterate`, `new-version`, `criteria`, `fix-placeholders`, `fix-images`, `rebrand` |
45
+ | `evals` | `run` |
46
+ | `images` | `edit`, `status`, `commit`, `watch` |
47
+
48
+ `ve-dynamite <group> <command> --help` documents every flag.
49
+
50
+ Paid, real money per item: `articles write|reject`, `articles clean-provenance --rewrite`, `batches create`, `gbp write|batch|regenerate`, `landing-pages write|reiterate|new-version|fix-images`, `profiles create`, `icp regenerate`, `design-system set|rescan`, `images edit`, `evals run --judge`.
51
+
52
+ Generation is asynchronous. A write returns an id at once; `watch` polls until it finishes, or poll `get` yourself about every 10 seconds. A completed article or landing page is `success`, a GBP post is `done`, a revision is `ready`.
53
+
54
+ ## Running inside a host
55
+
56
+ `buildProgram(options)` returns an unparsed Commander program and `run(argv, options)` runs one command and returns its exit code:
57
+
58
+ ```ts
59
+ import { buildProgram, run, type CostReport } from "@mirage-cli/contentdynamite-cli";
60
+
61
+ const program = buildProgram({ mount: true, reportCost: (report: CostReport) => record(report) });
62
+ const code = await run(["articles", "get", "41"], { mount: true });
63
+ ```
64
+
65
+ `mount: true` is for agent hosts that inject the credential and the API host. It drops `login`, `logout`, `tokens use`, every `watch` and the `--url` flag, never reads or writes the stored login, takes the token only from `--token` or `VE_DYNAMITE_TOKEN`, requires `-o` on downloads, and words its hints for an agent that polls.
66
+
67
+ `reportCost` is called once the API accepts paid work, with `{ provider: "contentdynamite", units }`, where `units` is the number of paid jobs started. It is never called for a request the API refused.
68
+
69
+ File arguments and `-o` targets go through the Mirage VFS bridge (`globalThis.__MIRAGE_CLI_FILE_IO__`) when one is installed and claims the path, and otherwise through `node:fs`. For a Mirage mount, use [`@mirage-cli/contentdynamite`](../contentdynamite), which does all of this.
70
+
71
+ ## Where the source lives
72
+
73
+ The CLI is developed next to the API in the Content Dynamite backend repository, which is private, and imports the API's own request schemas and response types so it cannot drift from it. `src/` here is generated from that repository by `bun run cli:mirage --out <this folder>`: the CLI files are copied as they are, the schemas and status helpers it uses are bundled into `src/wire.js`, and their types are written out flat to `src/wire.d.ts`. `src/source.json` names the commit it came from. Change the CLI there and regenerate, rather than editing `src/` by hand.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ run2
4
+ } from "./index-3v4effdk.js";
5
+
6
+ // src/bin.ts
7
+ process.exitCode = await run2(process.argv.slice(2));
@@ -0,0 +1,24 @@
1
+ export type Params = Record<string, string | number | boolean | null | undefined>;
2
+ export declare class ApiError extends Error {
3
+ readonly status: number;
4
+ constructor(status: number, message: string);
5
+ }
6
+ export declare class ApiClient {
7
+ readonly baseUrl: string;
8
+ readonly token: string;
9
+ constructor(baseUrl: string, token: string);
10
+ get<T = unknown>(path: string, params?: Params): Promise<T>;
11
+ getText(path: string, params?: Params): Promise<string>;
12
+ getDownload(path: string, params?: Params): Promise<{
13
+ content: Uint8Array;
14
+ filename: string | null;
15
+ }>;
16
+ post<T = unknown>(path: string, body?: unknown, params?: Params, timeoutMs?: number): Promise<T>;
17
+ postForm<T = unknown>(path: string, file: Blob, filename: string, params?: Params): Promise<T>;
18
+ put<T = unknown>(path: string, body?: unknown): Promise<T>;
19
+ patch<T = unknown>(path: string, body?: unknown): Promise<T>;
20
+ delete<T = unknown>(path: string): Promise<T>;
21
+ private json;
22
+ private request;
23
+ private url;
24
+ }
@@ -0,0 +1,7 @@
1
+ import type { Command } from 'commander';
2
+ import type { ApiClient, Params } from '../client.js';
3
+ import type { Cli } from '../context.js';
4
+ import { type ArticleDoneRes, type ArticlePendingRes } from '../wire.js';
5
+ export type ArticleRes = ArticleDoneRes | ArticlePendingRes;
6
+ export declare function everyArticle(client: ApiClient, filter: Params): Promise<ArticleRes[]>;
7
+ export declare function registerArticles(program: Command, cli: Cli): Command;
@@ -0,0 +1,6 @@
1
+ import type { Command } from 'commander';
2
+ import { ApiClient } from '../client.js';
3
+ import type { Cli } from '../context.js';
4
+ import type { UserRes } from '../wire.js';
5
+ export declare function signedIn(client: ApiClient, rejected: string): Promise<UserRes>;
6
+ export declare function registerAuth(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerBatches(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerEvals(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerGbp(program: Command, cli: Cli): void;
@@ -0,0 +1,9 @@
1
+ import type { Command } from 'commander';
2
+ import type { ApiClient } from '../client.js';
3
+ import type { Cli } from '../context.js';
4
+ import type { EditFeatImageJobRes } from '../wire.js';
5
+ export declare function loadJobCache(): Record<string, EditFeatImageJobRes>;
6
+ export declare function cacheJob(key: string, job: EditFeatImageJobRes): void;
7
+ export declare function fetchStatus(client: ApiClient, articleId: number, jobId: string, cached?: boolean): Promise<EditFeatImageJobRes>;
8
+ export declare function registerImages(program: Command, cli: Cli): void;
9
+ export declare function registerUpload(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import { type Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerLandingPages(program: Command, cli: Cli): void;
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function parseIcp(icp: string): Record<string, unknown> | null;
4
+ export declare function registerProfiles(program: Command, cli: Cli): void;
@@ -0,0 +1,4 @@
1
+ import { type Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerArticleRevisions(articles: Command, cli: Cli): void;
4
+ export declare function registerRevisions(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerTokens(program: Command, cli: Cli): void;
@@ -0,0 +1,3 @@
1
+ import { type Command } from 'commander';
2
+ import type { Cli } from '../context.js';
3
+ export declare function registerUsers(program: Command, cli: Cli): void;
@@ -0,0 +1,6 @@
1
+ export declare const DEFAULT_API_URL = "https://content-dynamite.alexthecomputer1.workers.dev";
2
+ export declare function configDir(): string;
3
+ export declare const credentialsPath: () => string;
4
+ export declare const imageJobsPath: () => string;
5
+ export declare function defaultApiUrl(): string;
6
+ export declare function envToken(): string | undefined;
@@ -0,0 +1,13 @@
1
+ import type { Command } from 'commander';
2
+ import { ApiClient } from './client.js';
3
+ import { type ResolvedToken } from './credentials.js';
4
+ import type { ProgramOptions } from './options.js';
5
+ export interface Cli {
6
+ mount: boolean;
7
+ baseUrl(): string;
8
+ token(): ResolvedToken;
9
+ client(): ApiClient;
10
+ follow(watch: string, get: string): string;
11
+ billed(units: number): void;
12
+ }
13
+ export declare function cliFor(program: Command, { mount, reportCost }: ProgramOptions): Cli;
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export declare const PAT_PREFIX = "ved_";
3
+ declare const storedCredentials: z.ZodObject<{
4
+ authToken: z.ZodString;
5
+ email: z.ZodString;
6
+ username: z.ZodString;
7
+ profile: z.ZodNullable<z.ZodString>;
8
+ expiresAt: z.ZodNullable<z.ZodNumber>;
9
+ }, z.core.$strip>;
10
+ export type StoredCredentials = z.output<typeof storedCredentials>;
11
+ export interface ResolvedToken {
12
+ token: string;
13
+ source: 'flag' | 'env' | 'stored';
14
+ credentials: StoredCredentials | null;
15
+ }
16
+ export declare const isPat: (token: string) => boolean;
17
+ export declare function loadCredentials(): StoredCredentials | null;
18
+ export declare function saveCredentials({ authToken, email, username, profile, expiresAt }: StoredCredentials): void;
19
+ export declare function clearCredentials(): void;
20
+ export declare function decodeJwtPayload(token: string): Record<string, unknown> | null;
21
+ export declare function decodeJwtExp(token: string): number | null;
22
+ export declare function resolveToken(flagToken?: string, stored?: boolean): ResolvedToken;
23
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function readBytes(path: string): Uint8Array<ArrayBuffer>;
2
+ export declare const readText: (path: string) => string;
3
+ export declare function writeFile(path: string, data: string | Uint8Array): void;
@@ -0,0 +1,27 @@
1
+ import { type Command, CommanderError } from 'commander';
2
+ import type { Cli } from './context.js';
3
+ export declare class Exit extends CommanderError {
4
+ constructor(code: number);
5
+ }
6
+ export declare const clock: {
7
+ sleep: (ms: number) => Promise<void>;
8
+ now: () => number;
9
+ };
10
+ export declare const fmtNum: (value: number) => string;
11
+ export declare const splitCsv: (value: string) => string[];
12
+ export declare function integer(value: string): number;
13
+ export declare function decimal(value: string): number;
14
+ export declare function between(low: number, high: number): (value: string) => number;
15
+ export declare function either(value: string | undefined, file: string | undefined, flags: string): string | undefined;
16
+ export declare function documentOutput(command: Command, cli: Cli): Command;
17
+ export declare function downloadDocument(cli: Cli, path: string, format: string, output: string | undefined, fallback: string): Promise<string>;
18
+ export declare function interactiveFields(value: string | boolean | undefined): Record<string, string | boolean>;
19
+ export interface Watch<T> {
20
+ interval: number;
21
+ timeout?: number;
22
+ poll: () => Promise<T>;
23
+ progress: (item: T) => string;
24
+ done: (item: T) => boolean;
25
+ timedOut: (item: T) => string;
26
+ }
27
+ export declare function watchLoop<T>({ interval, timeout, poll, progress, done, timedOut }: Watch<T>): Promise<T>;