@ronana/mcp 0.1.1 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +11 -3
  2. package/dist/cli.js +64 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,9 @@ ronana generate cost google/nano-banana-2-lite/edit --prompt "make it dusk" --
38
38
  ronana generate create google/nano-banana-2-lite/edit --prompt "make it dusk" --image ./photo.png --out ./renders
39
39
  ronana generate get <id> | wait <id> [--out dir]
40
40
  ronana generate list
41
+ ronana upscale <id | file | url> --resolution 4k --out ./renders
42
+ ronana improve google/nano-banana-2-lite/edit --prompt "make it dusk"
43
+ ronana folder list | add <folder> <id…>
41
44
  ```
42
45
 
43
46
  Every field from `model get` is a flag (`--aspect_ratio 16:9`, `--seed 7`).
@@ -95,18 +98,23 @@ library.
95
98
  | `get_model` | Get one model's contract: its input schema. |
96
99
  | `estimate_cost` | Estimate the credit cost of a generation. Takes the same `model` and `input` as `generate`. |
97
100
  | `generate` | Start an image or video generation. |
98
- | `get_generation` | Get one generation and its download link. |
101
+ | `get_generation` | Get one generation and its download link. Pass `wait_seconds` (up to 45) to wait for the result in one call. |
102
+ | `upscale` | Upscale a video to a higher resolution. |
103
+ | `improve_prompt` | Rewrite a prompt so it works better on one model. |
99
104
  | `list_generations` | List recent generations. |
100
105
  | `list_references` | List library references. |
101
106
  | `upload_reference` | Add a file to the library from a URL (up to 25 MB) or from base64 (under 3 MB). |
102
107
  | `create_upload` | Get a private upload link for a larger local file (up to 200 MB). |
103
108
  | `finish_upload` | Add a file uploaded with `create_upload` to the library. |
109
+ | `list_folders` | List the folders in the library. |
110
+ | `add_to_folder` | File library items into a folder. |
104
111
  | `get_account` | Get the plan and credit balance. |
105
112
 
106
113
  A generate call takes a `model` and an `input`, exactly as `get_model` shows it
107
114
  in `input_schema`. A media field takes a public https URL or a Ronana id
108
- (`ref_…`, `img_…`, `gen_…`). Generation tools return an id immediately. Poll
109
- `get_generation` until the status is `completed` or `failed`.
115
+ (`ref_…`, `img_…`, `gen_…`). Generation tools return an id immediately. Call
116
+ `get_generation` with `wait_seconds` until the status is `completed` or
117
+ `failed`. Apps that support MCP Apps show the image or video right in the chat.
110
118
 
111
119
  ```json
112
120
  { "model": "google/nano-banana-2/edit", "input": { "prompt": "a lantern shop at dusk", "images": ["https://example.com/ref.png"] } }
package/dist/cli.js CHANGED
@@ -12,6 +12,9 @@
12
12
  // ronana generate cost <model> [same flags as create]
13
13
  // ronana generate get | wait <id> [--out dir]
14
14
  // ronana generate list [--limit n]
15
+ // ronana upscale <id | file | https-url> [--resolution r] [--model m] [--wait] [--out dir]
16
+ // ronana improve <model> --prompt "…"
17
+ // ronana folder list | add <folder> <id…>
15
18
  import { randomUUID } from "node:crypto";
16
19
  import { createWriteStream } from "node:fs";
17
20
  import { mkdir } from "node:fs/promises";
@@ -20,7 +23,7 @@ import { Readable } from "node:stream";
20
23
  import { pipeline } from "node:stream/promises";
21
24
  import { login, logout, readCredentials } from "./oauth.js";
22
25
  import { callTool, remoteClient, ToolError, uploadFile } from "./remote.js";
23
- import { buildInput, flag, has, parseArgs, replaceLocalMedia, typeOf } from "./input.js";
26
+ import { buildInput, flag, has, isRemoteValue, parseArgs, replaceLocalMedia, typeOf } from "./input.js";
24
27
  const HELP = `Usage: ronana <command>
25
28
 
26
29
  auth login Sign in with your browser (--base-url, --no-browser)
@@ -35,6 +38,10 @@ const HELP = `Usage: ronana <command>
35
38
  generate get <id> Status and download links
36
39
  generate wait <id> Wait for a generation to finish
37
40
  generate list Recent generations (--limit n)
41
+ upscale <id|file|url> Upscale a video (costs credits; --resolution, --model)
42
+ improve <model> --prompt "…" Rewrite a prompt for a model (costs a little)
43
+ folder list Your library folders
44
+ folder add <folder> <id…> File items into a folder (made if missing)
38
45
 
39
46
  Generate flags:
40
47
  --<field> <value> Any field from \`model get\`. Repeat for lists.
@@ -80,8 +87,12 @@ async function waitFor(remote, id, args) {
80
87
  let last = "";
81
88
  for (;;) {
82
89
  let gen;
90
+ const asked = Date.now();
83
91
  try {
84
- gen = await callTool(remote, "get_generation", { id });
92
+ // The server holds the call until the job settles (up to 25s), so this
93
+ // loop makes one request per 25s rather than one every few seconds.
94
+ const wait_seconds = Math.max(0, Math.min(25, Math.floor((deadline - asked) / 1000)));
95
+ gen = await callTool(remote, "get_generation", { id, wait_seconds });
85
96
  }
86
97
  catch (error) {
87
98
  // A server answer (not found, no access) is final; anything else is the
@@ -99,7 +110,11 @@ ${id} is still running. Resume with \`ronana generate wait ${id}\`.`);
99
110
  return gen;
100
111
  if (Date.now() > deadline)
101
112
  throw new Error(`Still ${gen.status} after ${timeoutMs / 1000}s. Check later with \`ronana generate wait ${id}\`.`);
102
- await new Promise((r) => setTimeout(r, intervalMs));
113
+ // A server that answered at once (an older one, or a short wait) still gets
114
+ // the local pause, so the loop never spins.
115
+ const left = intervalMs - (Date.now() - asked);
116
+ if (left > 0)
117
+ await new Promise((r) => setTimeout(r, left));
103
118
  }
104
119
  }
105
120
  const EXT_BY_TYPE = {
@@ -202,7 +217,7 @@ async function modelCommand(sub, args) {
202
217
  const filter = args.positional[0]?.toLowerCase();
203
218
  const models = (await withRemote((r) => callTool(r, "list_models", kind ? { kind } : {})))
204
219
  .filter((m) => !filter || m.id.toLowerCase().includes(filter) || m.name.toLowerCase().includes(filter));
205
- return emit(models, () => models.forEach((m) => print(`${m.id.padEnd(52)} ${m.kind.padEnd(6)} ${m.name}`)));
220
+ return emit(models, () => models.forEach((m) => print(`${m.id.padEnd(52)} ${m.kind.padEnd(6)} ${m.name}${m.best_for ? ` ${m.best_for}` : ""}`)));
206
221
  }
207
222
  if (sub !== "get")
208
223
  throw new Error("Use model list | get.");
@@ -225,9 +240,42 @@ async function modelCommand(sub, args) {
225
240
  print(`Priced by: ${contract.priced_fields.join(", ")}`);
226
241
  });
227
242
  }
243
+ async function upscaleCommand(args) {
244
+ const [source] = args.positional;
245
+ if (!source)
246
+ throw new Error("Usage: ronana upscale <gen_…|ref_… id | file | https-url> [--resolution 4k] [--out dir]");
247
+ await withRemote(async (remote) => {
248
+ // A local file uploads first; an id or a URL goes as-is.
249
+ const resolved = isRemoteValue(source) ? source : (note(`Uploading ${basename(source)}…`), (await uploadFile(remote, resolve(source))).id);
250
+ const queued = await callTool(remote, "upscale", {
251
+ source: resolved,
252
+ ...(flag(args, "model") ? { model: flag(args, "model") } : {}),
253
+ ...(flag(args, "resolution") ? { resolution: flag(args, "resolution") } : {}),
254
+ idempotency_key: flag(args, "idempotency_key") ?? randomUUID(),
255
+ });
256
+ if (!has(args, "wait") && !has(args, "out")) {
257
+ return emit(queued, () => { print(queued.id); note(`Check it with \`ronana generate wait ${queued.id}\`.`); });
258
+ }
259
+ note(`Queued ${queued.id}.`);
260
+ await finish(await waitFor(remote, queued.id, args), args);
261
+ });
262
+ }
263
+ async function folderCommand(sub, args) {
264
+ if (sub === "list" || sub === undefined) {
265
+ const folders = await withRemote((r) => callTool(r, "list_folders"));
266
+ return emit(folders, () => folders.forEach((f) => print(`${f.id.padEnd(28)} ${String(f.count).padStart(5)} ${f.name}`)));
267
+ }
268
+ if (sub !== "add")
269
+ throw new Error("Use folder list | add.");
270
+ const [folder, ...ids] = args.positional;
271
+ if (!folder || ids.length === 0)
272
+ throw new Error("Usage: ronana folder add <folder> <id…>");
273
+ const filed = await withRemote((r) => callTool(r, "add_to_folder", { ids, folder, create: true }));
274
+ return emit(filed, () => print(`Filed ${filed.filed.length} in "${filed.folder.name}".`));
275
+ }
228
276
  async function main() {
229
277
  const [command, sub, ...rest] = process.argv.slice(2);
230
- const takesSub = command === "auth" || command === "model" || command === "generate";
278
+ const takesSub = command === "auth" || command === "model" || command === "generate" || command === "folder";
231
279
  const args = parseArgs(takesSub ? rest : [sub, ...rest].filter((v) => v !== undefined));
232
280
  JSON_OUT = has(args, "json");
233
281
  if (!command || ["help", "--help", "-h"].includes(command) || has(args, "help"))
@@ -268,6 +316,17 @@ async function main() {
268
316
  return emit(ref, () => print(ref.id));
269
317
  }
270
318
  case "generate": return generateCommand(sub, args);
319
+ case "upscale": return upscaleCommand(args);
320
+ case "improve": {
321
+ const model = args.positional[0];
322
+ const prompt = flag(args, "prompt");
323
+ if (!model || !prompt)
324
+ throw new Error("Usage: ronana improve <model> --prompt \"…\"");
325
+ const improved = await withRemote((r) => callTool(r, "improve_prompt", { model, prompt }));
326
+ return emit(improved, () => print(improved.prompt));
327
+ }
328
+ case "folder":
329
+ case "folders": return folderCommand(command === "folders" ? "list" : sub, args);
271
330
  default: throw new Error(`Unknown command "${command}". Run \`ronana --help\`.`);
272
331
  }
273
332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronana/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Ronana command line and local MCP bridge.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://ronana.ai",