@mapled/cli 0.14.6 → 0.16.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/README.md CHANGED
@@ -16,6 +16,27 @@ Node 18 or newer. No dependencies. Prefer a global install? `npm install -g @map
16
16
 
17
17
  Connecting a site without an AI agent? [Integrate by hand](https://www.npmjs.com/package/@mapled/next#integrate-by-hand) in the `@mapled/next` README walks through the whole integration — modeling the content, reading it, the publish webhook, preview, forms — and uses these commands along the way.
18
18
 
19
+ ## For AI agents
20
+
21
+ Mapled is a hosted headless CMS for sites built with AI: the owner edits content in Mapled, the site reads published content through a small SDK, and you connect the two. Everything below runs from the shell of the session you are already in — no MCP server to register, no restart.
22
+
23
+ ```bash
24
+ npx @mapled/cli connect --project <id>
25
+ ```
26
+
27
+ The browser opens Mapled on that project and the person clicks Authorize; nothing is copied or pasted. The command writes `mapled.json` and prints Mapled's guide for agents (also at https://api.mapled.io/agents.md). Run again in a connected repository — or in a subdirectory of one — it opens no browser. The guide is read from Mapled, or from the origin you name with `--api` on that run; an origin that only `mapled.json` names never serves it, and a first sign-in there asks for `--api` too. Without `--project` the person picks the project in the browser.
28
+
29
+ ```bash
30
+ npx @mapled/cli tools
31
+ npx @mapled/cli tool get_schema
32
+ npx @mapled/cli tool propose_setup_plan --input plan.json
33
+ npx @mapled/cli tool get_setup_run '{"runId":"…"}' --wait
34
+ ```
35
+
36
+ `tools` lists every tool with its input (`--json` for the schemas). `tool <name>` calls one — the same tools the [MCP server](https://www.npmjs.com/package/@mapled/mcp) offers, through the same connection: the input is a JSON object, inline or in a file with `--input`; the answer is printed as Mapled worded it, and a refusal exits 1. `tool get_setup_run '{"runId":"…"}' --wait` waits while the plan is still waiting for approval and prints the run once the person has decided on the Mapled screen — the agent carries on without being asked; it waits 90 seconds by default (`--wait=<seconds>`, up to 600) and then prints the run as it stands, so the agent runs the same command again.
37
+
38
+ Do the work yourself: the person approves on Mapled screens (the connection, the plan, a destructive change) and is never sent to a terminal or to GitHub. Never ask for a token or a key in the conversation.
39
+
19
40
  ## Sign in
20
41
 
21
42
  ```bash
@@ -271,7 +292,10 @@ Prints a Preview link — the one the **Preview** button in Mapled opens: the si
271
292
 
272
293
  - `--api <origin>` — a Mapled API other than `https://api.mapled.io` (or set `MAPLED_API_URL`); `project link` remembers it in `mapled.json`
273
294
  - `--no-browser` — print the sign-in link instead of opening a browser
274
- - `--json` — machine-readable output (`doctor`, `integration report`, `schema diff`, `scan`, `manifest validate`, `forms list`, `preview`)
295
+ - `--project <id>` — the project `connect` and `auth login` open the consent on, and the one `project link` links
296
+ - `--input <file>` — `tool` only: the tool's input as a JSON file
297
+ - `--wait[=<seconds>]` — `tool get_setup_run` only: wait while the plan awaits approval (90 seconds by default, up to 600)
298
+ - `--json` — machine-readable output (`tools`, `doctor`, `integration report`, `schema diff`, `scan`, `manifest validate`, `forms list`, `preview`)
275
299
  - `--load-config` — `doctor` only: let the site's installed Next load a next.config doctor can't read; runs the site's code, as `next build` does
276
300
  - `MAPLED_CONFIG_DIR` — where credentials live (default: `$XDG_CONFIG_HOME/mapled` or `~/.config/mapled`)
277
301
 
package/dist/api.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare class ApiError extends Error {
7
7
  constructor(status: number, code: string | null, message: string);
8
8
  }
9
9
  export type Method = "GET" | "POST" | "PATCH" | "DELETE";
10
- export declare function apiRequest<T>(api: string, method: Method, path: string, token: string, body: unknown, f: Fetch): Promise<T>;
10
+ export declare function apiRequest<T>(api: string, method: Method, path: string, token: string, body: unknown, f: Fetch, accept?: string): Promise<T>;
11
11
  export declare function apiGet<T>(api: string, path: string, token: string, f: Fetch): Promise<T>;
12
12
  /** A signed-in connection with its store: reads refresh the access
13
13
  token when it is about to expire or when the API says it did, and
@@ -20,6 +20,10 @@ export declare class Session {
20
20
  constructor(store: CredentialsFile, file: string, conn: Connection, f: Fetch);
21
21
  get<T>(path: string): Promise<T>;
22
22
  send<T>(method: Method, path: string, body: unknown): Promise<T>;
23
+ /** One JSON-RPC call to the hosted MCP endpoint — the tools an agent
24
+ gets over MCP, from the shell (the endpoint is stateless: a call
25
+ needs no `initialize`). */
26
+ rpc<T>(method: string, params?: unknown): Promise<T>;
23
27
  private request;
24
28
  private refresh;
25
29
  }
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { upsertConnection, writeCredentials } from "./credentials.js";
2
- import { CliError } from "./errors.js";
2
+ import { CliError, SignInNeeded } from "./errors.js";
3
3
  import { refreshTokens } from "./oauth.js";
4
4
  /** Calls to the agent API (/v1/agent/*) with a connection's bearer token. */
5
5
  export class ApiError extends Error {
@@ -12,14 +12,14 @@ export class ApiError extends Error {
12
12
  this.code = code;
13
13
  }
14
14
  }
15
- export async function apiRequest(api, method, path, token, body, f) {
15
+ export async function apiRequest(api, method, path, token, body, f, accept = "application/json") {
16
16
  let res;
17
17
  try {
18
18
  res = await f(`${api}${path}`, {
19
19
  method,
20
20
  headers: {
21
21
  authorization: `Bearer ${token}`,
22
- accept: "application/json",
22
+ accept,
23
23
  ...(body !== undefined ? { "content-type": "application/json" } : {}),
24
24
  },
25
25
  ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
@@ -57,19 +57,29 @@ export class Session {
57
57
  send(method, path, body) {
58
58
  return this.request(method, path, body);
59
59
  }
60
- async request(method, path, body) {
60
+ /** One JSON-RPC call to the hosted MCP endpoint — the tools an agent
61
+ gets over MCP, from the shell (the endpoint is stateless: a call
62
+ needs no `initialize`). */
63
+ async rpc(method, params) {
64
+ const answer = await this.request("POST", "/mcp", { jsonrpc: "2.0", id: 1, method, ...(params !== undefined ? { params } : {}) }, "application/json, text/event-stream");
65
+ if (answer?.error || answer?.result === undefined) {
66
+ throw new CliError(answer?.error?.message ?? "Mapled gave no answer. Try again.");
67
+ }
68
+ return answer.result;
69
+ }
70
+ async request(method, path, body, accept) {
61
71
  if (new Date(this.conn.expiresAt).getTime() - Date.now() < 60_000)
62
72
  await this.refresh();
63
73
  try {
64
- return await apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
74
+ return await apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f, accept);
65
75
  }
66
76
  catch (err) {
67
77
  if (err instanceof ApiError && err.status === 401) {
68
78
  if (err.code === "TOKEN_EXPIRED") {
69
79
  await this.refresh();
70
- return apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
80
+ return apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f, accept);
71
81
  }
72
- throw new CliError(`The connection to ${this.conn.projectName} was revoked. Run \`mapled auth login\` to sign in again.`);
82
+ throw new SignInNeeded(`The connection to ${this.conn.projectName} was revoked. Run \`mapled auth login\` to sign in again.`);
73
83
  }
74
84
  throw err;
75
85
  }
@@ -80,7 +90,7 @@ export class Session {
80
90
  tokens = await refreshTokens(this.conn.api, this.conn.clientId, this.conn.refreshToken, this.f);
81
91
  }
82
92
  catch {
83
- throw new CliError(`Your sign-in to ${this.conn.projectName} expired. Run \`mapled auth login\` to sign in again.`);
93
+ throw new SignInNeeded(`Your sign-in to ${this.conn.projectName} expired. Run \`mapled auth login\` to sign in again.`);
84
94
  }
85
95
  this.conn = { ...this.conn, ...tokens };
86
96
  this.store = upsertConnection(this.store, this.conn);
package/dist/args.js CHANGED
@@ -2,7 +2,7 @@ import { CliError } from "./errors.js";
2
2
  /** Minimal argument parsing: the leading words name the command;
3
3
  `--flag`, `--flag=value` and `--flag value` (for the flags that take
4
4
  one) follow. Nothing here is user content beyond the command line. */
5
- const VALUE_FLAGS = new Set(["api", "project", "out", "key", "locale"]);
5
+ const VALUE_FLAGS = new Set(["api", "project", "out", "key", "locale", "input"]);
6
6
  export function parseArgs(argv) {
7
7
  const words = [];
8
8
  const flags = {};
@@ -17,11 +17,26 @@ export type Ctx = {
17
17
  credentialsFile: string;
18
18
  /** How long a sign-in waits for the browser (tests shorten it). */
19
19
  loginTimeoutMs?: number;
20
+ /** How often `tool get_setup_run --wait` asks again (tests shorten it). */
21
+ pollMs?: number;
20
22
  };
21
23
  type Flags = ParsedArgs["flags"];
22
24
  export declare function login(ctx: Ctx, flags: Flags): Promise<Connection>;
23
25
  export declare function logout(ctx: Ctx, flags: Flags): Promise<void>;
24
26
  export declare function link(ctx: Ctx, flags: Flags): Promise<FoundConfig>;
27
+ /** `mapled connect [--project <id>]` — everything an agent needs before
28
+ its first tool call, in one run: sign in through the browser (the
29
+ consent opens on the named project), write mapled.json, print
30
+ Mapled's guide for agents. A repository that is already connected
31
+ opens no browser. */
32
+ export declare function connect(ctx: Ctx, flags: Flags): Promise<void>;
33
+ /** `mapled tools [--json]` — every tool the hosted MCP endpoint offers,
34
+ with its description and input: what `mapled tool` can call. */
35
+ export declare function tools(ctx: Ctx, flags: Flags): Promise<void>;
36
+ /** `mapled tool <name> [<json> | --input <file>]` — one tool call, the
37
+ way an MCP client makes it: the answer is printed as Mapled worded
38
+ it, a refusal exits 1. */
39
+ export declare function tool(ctx: Ctx, flags: Flags, words: string[]): Promise<number>;
25
40
  export declare function generate(ctx: Ctx, flags: Flags): Promise<void>;
26
41
  export declare function schemaPull(ctx: Ctx, flags: Flags): Promise<void>;
27
42
  export declare function schemaDiff(ctx: Ctx, flags: Flags): Promise<number>;
package/dist/commands.js CHANGED
@@ -2,10 +2,10 @@ import { readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { ApiError, apiGet, Session } from "./api.js";
4
4
  import { stringFlag } from "./args.js";
5
- import { CONFIG_FILE, DEFAULT_TYPES_PATH, findConfig, resolveApi, writeConfig } from "./config.js";
5
+ import { CONFIG_FILE, DEFAULT_API, DEFAULT_TYPES_PATH, findConfig, resolveApi, writeConfig } from "./config.js";
6
6
  import { connectionsFor, findConnection, readCredentials, removeConnection, upsertConnection, writeCredentials, } from "./credentials.js";
7
7
  import * as dr from "./doctor.js";
8
- import { CliError } from "./errors.js";
8
+ import { CliError, SignInNeeded } from "./errors.js";
9
9
  import { checkManifest, compareManifests, fileExistsIn, MANIFEST_FILE, manifestSummary, orderManifest, parseManifest, plural, readManifestFile, writeManifestFile, } from "./manifest.js";
10
10
  import { authorize, clientKnown, exchangeCode, registerClient, revokeToken } from "./oauth.js";
11
11
  import { formatChecks, GLYPH, paint, summarize, useColor } from "./output.js";
@@ -28,6 +28,14 @@ async function ensureClient(store, api, f) {
28
28
  clientId,
29
29
  };
30
30
  }
31
+ /** `--project <id>`: a project id as Mapled writes it, or nothing. */
32
+ function projectFlag(flags) {
33
+ const value = stringFlag(flags, "project")?.toLowerCase();
34
+ if (value !== undefined && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value)) {
35
+ throw new CliError("--project takes a project id — the one in the prompt copied from Mapled.");
36
+ }
37
+ return value;
38
+ }
31
39
  export async function login(ctx, flags) {
32
40
  const found = await findConfig(ctx.cwd);
33
41
  const api = resolveApi(found?.config ?? null, stringFlag(flags, "api"), ctx.env);
@@ -38,6 +46,7 @@ export async function login(ctx, flags) {
38
46
  openBrowser: flags["no-browser"] ? async () => false : ctx.openBrowser,
39
47
  log: ctx.out,
40
48
  timeoutMs: ctx.loginTimeoutMs,
49
+ project: projectFlag(flags),
41
50
  });
42
51
  const tokens = await exchangeCode(api, client.clientId, grant, ctx.fetch);
43
52
  const status = await apiGet(api, "/v1/agent/integration", tokens.accessToken, ctx.fetch);
@@ -51,7 +60,7 @@ export async function login(ctx, flags) {
51
60
  };
52
61
  store = upsertConnection(store, conn);
53
62
  await writeCredentials(ctx.credentialsFile, store);
54
- ctx.out(`Signed in to ${conn.projectName}.`);
63
+ ctx.out(`Signed in to ${clean(conn.projectName, 80)}.`);
55
64
  if (!found)
56
65
  ctx.out("Next: run `mapled project link` in the site's repository.");
57
66
  else if (found.config.project !== conn.projectId) {
@@ -131,6 +140,160 @@ export async function link(ctx, flags) {
131
140
  ctx.out("Next: `mapled types generate`, then `mapled doctor`.");
132
141
  return { path: file, dir: path.resolve(ctx.cwd), config };
133
142
  }
143
+ /** `mapled connect [--project <id>]` — everything an agent needs before
144
+ its first tool call, in one run: sign in through the browser (the
145
+ consent opens on the named project), write mapled.json, print
146
+ Mapled's guide for agents. A repository that is already connected
147
+ opens no browser. */
148
+ export async function connect(ctx, flags) {
149
+ const wanted = projectFlag(flags);
150
+ // the nearest mapled.json, here or above: a subdirectory of a connected
151
+ // repository is connected
152
+ const linked = await findConfig(ctx.cwd);
153
+ const flagApi = stringFlag(flags, "api");
154
+ const api = resolveApi(linked?.config ?? null, flagApi, ctx.env);
155
+ // The guide is text an agent follows, so it comes from Mapled or from
156
+ // an origin the person named on this run (the flag, the environment) —
157
+ // never from one only a file of the repository names: mapled.json is
158
+ // committed, and whoever wrote it would be writing the agent's guide.
159
+ const guideApi = !flagApi && linked?.config.api && api !== DEFAULT_API ? DEFAULT_API : api;
160
+ const store = await readCredentials(ctx.credentialsFile);
161
+ const target = wanted ?? linked?.config.project;
162
+ let conn = target ? findConnection(store, api, target) : undefined;
163
+ if (conn) {
164
+ // a connection revoked in Integrations is of no use: ask the API once
165
+ try {
166
+ await new Session(store, ctx.credentialsFile, conn, ctx.fetch).get("/v1/agent/integration");
167
+ ctx.out(`Already connected to ${clean(conn.projectName, 80)}.`);
168
+ }
169
+ catch (err) {
170
+ // only a connection that is gone asks for a new sign-in: an API
171
+ // that can't be reached right now is said as it is
172
+ if (!(err instanceof SignInNeeded))
173
+ throw err;
174
+ conn = undefined;
175
+ }
176
+ }
177
+ if (!conn && api !== guideApi) {
178
+ // Signing in sends the person's browser to this origin, and what it
179
+ // answers is printed for the agent: an origin only the committed
180
+ // mapled.json names has to be named by the person too.
181
+ throw new CliError(`${CONFIG_FILE} here names another API, ${api}. To sign in there, run \`mapled connect --api ${api}\`.`);
182
+ }
183
+ if (!conn) {
184
+ // a repository linked before opens the consent on its own project
185
+ conn = await login(ctx, target ? { ...flags, project: target } : flags);
186
+ if (wanted && conn.projectId !== wanted) {
187
+ ctx.out(`Note: ${clean(conn.projectName, 80)} was picked in the browser — not the project the prompt named. Continuing with it.`);
188
+ }
189
+ }
190
+ if (!linked || linked.config.project !== conn.projectId) {
191
+ await link(ctx, { ...flags, project: conn.projectId });
192
+ }
193
+ ctx.out("");
194
+ try {
195
+ const res = await ctx.fetch(`${guideApi}/agents.md`);
196
+ if (!res.ok)
197
+ throw new Error();
198
+ for (const line of (await res.text()).split("\n"))
199
+ ctx.out(line);
200
+ }
201
+ catch {
202
+ ctx.out(`Connected. The guide for agents: ${guideApi}/agents.md`);
203
+ }
204
+ }
205
+ /** `mapled tools [--json]` — every tool the hosted MCP endpoint offers,
206
+ with its description and input: what `mapled tool` can call. */
207
+ export async function tools(ctx, flags) {
208
+ const { session } = await openSession(ctx, flags);
209
+ const { tools: list } = await session.rpc("tools/list");
210
+ if (flags.json) {
211
+ ctx.out(JSON.stringify(list, null, 2));
212
+ return;
213
+ }
214
+ for (const tool of list) {
215
+ const required = new Set(tool.inputSchema?.required ?? []);
216
+ const input = Object.keys(tool.inputSchema?.properties ?? {}).map((k) => (required.has(k) ? k : `${k}?`));
217
+ ctx.out(`${tool.name}${input.length > 0 ? ` { ${input.join(", ")} }` : ""}`);
218
+ if (tool.description)
219
+ ctx.out(` ${tool.description}`);
220
+ ctx.out("");
221
+ }
222
+ ctx.out("Call one: `mapled tool <name> '<json>'` or `mapled tool <name> --input <file.json>`; `mapled tools --json` shows each input's schema.");
223
+ }
224
+ /** `mapled tool <name> [<json> | --input <file>]` — one tool call, the
225
+ way an MCP client makes it: the answer is printed as Mapled worded
226
+ it, a refusal exits 1. */
227
+ export async function tool(ctx, flags, words) {
228
+ const [name, inline] = words;
229
+ if (!name)
230
+ throw new CliError("Which tool? `mapled tools` lists them.", 2);
231
+ const file = stringFlag(flags, "input");
232
+ if (file !== undefined && inline !== undefined)
233
+ throw new CliError("Pass the input as JSON or with --input, not both.", 2);
234
+ let raw = inline ?? "{}";
235
+ if (file !== undefined) {
236
+ try {
237
+ raw = await readFile(path.resolve(ctx.cwd, file), "utf8");
238
+ }
239
+ catch {
240
+ throw new CliError(`Couldn't read ${file}.`);
241
+ }
242
+ }
243
+ let args;
244
+ try {
245
+ args = JSON.parse(raw);
246
+ }
247
+ catch {
248
+ throw new CliError("The input isn't valid JSON.");
249
+ }
250
+ if (args === null || typeof args !== "object" || Array.isArray(args))
251
+ throw new CliError("The input must be a JSON object.");
252
+ const wait = waitSeconds(flags, name);
253
+ const { session } = await openSession(ctx, flags);
254
+ const deadline = Date.now() + wait * 1000;
255
+ let answer = await session.rpc("tools/call", { name, arguments: args });
256
+ // the person approves on a Mapled screen, not in the agent's chat: the
257
+ // agent blocks here instead of ending its turn to wait for a word that
258
+ // never comes
259
+ while (wait > 0 && !answer.isError && awaitingApproval(answer) && Date.now() < deadline) {
260
+ await new Promise((resolve) => setTimeout(resolve, Math.min(ctx.pollMs ?? WAIT_POLL_MS, Math.max(0, deadline - Date.now()))));
261
+ answer = await session.rpc("tools/call", { name, arguments: args });
262
+ }
263
+ for (const part of answer.content ?? [])
264
+ if (part.type === "text" && part.text !== undefined)
265
+ ctx.out(part.text);
266
+ return answer.isError ? 1 : 0;
267
+ }
268
+ /** `--wait` waits this long by default: under the two minutes an agent's
269
+ shell gives a command unless it asks for more. */
270
+ const WAIT_SECONDS = 90;
271
+ const WAIT_MAX_SECONDS = 600;
272
+ const WAIT_POLL_MS = 3000;
273
+ /** Seconds `tool <name> --wait[=<seconds>]` may wait; 0 without it. */
274
+ function waitSeconds(flags, name) {
275
+ const value = flags.wait;
276
+ if (value === undefined)
277
+ return 0;
278
+ if (name !== "get_setup_run")
279
+ throw new CliError("--wait works with get_setup_run only.", 2);
280
+ if (value === true)
281
+ return WAIT_SECONDS;
282
+ const seconds = Number(value);
283
+ if (!Number.isInteger(seconds) || seconds < 1 || seconds > WAIT_MAX_SECONDS) {
284
+ throw new CliError(`--wait takes whole seconds from 1 to ${WAIT_MAX_SECONDS}.`, 2);
285
+ }
286
+ return seconds;
287
+ }
288
+ function awaitingApproval(answer) {
289
+ const text = answer.content?.find((part) => part.type === "text")?.text;
290
+ try {
291
+ return JSON.parse(text ?? "").run?.status === "awaiting_approval";
292
+ }
293
+ catch {
294
+ return false;
295
+ }
296
+ }
134
297
  async function openSession(ctx, flags) {
135
298
  const found = await findConfig(ctx.cwd);
136
299
  if (!found)
package/dist/doctor.js CHANGED
@@ -1082,6 +1082,15 @@ export function checkWebhook(webhook, site) {
1082
1082
  detail: `${host} resolves to a private address, which Mapled can't reach. Point the webhook at the public deployment.`,
1083
1083
  };
1084
1084
  }
1085
+ if (last.responseStatus === 503) {
1086
+ // what the SDK's route answers before the site has the signing secret (deployed first, as the README says)
1087
+ return {
1088
+ key: "webhook",
1089
+ label,
1090
+ status: "failed",
1091
+ detail: `The last delivery to ${host} responded 503 — the route answers that while MAPLED_WEBHOOK_SECRET isn't set on the site. Set it there, redeploy, then send a test ping.`,
1092
+ };
1093
+ }
1085
1094
  const reason = last.responseStatus ? `responded ${last.responseStatus}` : (last.error ?? "failed");
1086
1095
  return {
1087
1096
  key: "webhook",
package/dist/errors.d.ts CHANGED
@@ -4,3 +4,9 @@ export declare class CliError extends Error {
4
4
  exitCode: number;
5
5
  constructor(message: string, exitCode?: number);
6
6
  }
7
+ /** The stored connection no longer works — revoked in Mapled, or its
8
+ refresh token spent: only a new sign-in helps. A network failure is
9
+ not this. */
10
+ export declare class SignInNeeded extends CliError {
11
+ constructor(message: string);
12
+ }
package/dist/errors.js CHANGED
@@ -8,3 +8,12 @@ export class CliError extends Error {
8
8
  this.exitCode = exitCode;
9
9
  }
10
10
  }
11
+ /** The stored connection no longer works — revoked in Mapled, or its
12
+ refresh token spent: only a new sign-in helps. A network failure is
13
+ not this. */
14
+ export class SignInNeeded extends CliError {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "SignInNeeded";
18
+ }
19
+ }
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { parseArgs } from "./args.js";
5
5
  import { openBrowser } from "./browser.js";
6
- import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, preview, scan, schemaDiff, schemaPull, } from "./commands.js";
6
+ import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, preview, scan, connect, tool, tools, schemaDiff, schemaPull, } from "./commands.js";
7
7
  import { credentialsPath } from "./credentials.js";
8
8
  import { CliError } from "./errors.js";
9
9
  /** `mapled` — sign in, link a repository to its project, generate types
@@ -13,6 +13,10 @@ import { CliError } from "./errors.js";
13
13
  const HELP = `mapled — the Mapled CLI
14
14
 
15
15
  Usage
16
+ mapled connect [--project <id>] Connect this repository: sign in, write mapled.json, print the guide for AI agents
17
+ mapled tools [--json] List the Mapled tools an agent can call
18
+ mapled tool <name> [<json> | --input <file>] Call one tool — the same ones the MCP server offers
19
+ get_setup_run --wait[=<s>] waits (90 s by default) for the plan's approval
16
20
  mapled auth login [--api <origin>] [--no-browser] Sign in to a project in your browser
17
21
  mapled auth logout [--all] Revoke this machine's access to the linked project
18
22
  mapled project link [--project <id>] Write mapled.json for this repository
@@ -36,6 +40,7 @@ Options
36
40
 
37
41
  Credentials stay in your user config directory, never in the repository.
38
42
  Run it as \`npx @mapled/cli <command>\`, or install it globally with \`npm install -g @mapled/cli\` to get \`mapled\` on your PATH.`;
43
+ const ROOT_COMMANDS = new Set(["preview", "connect", "tools", "tool"]);
39
44
  function version() {
40
45
  return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
41
46
  }
@@ -80,8 +85,17 @@ async function main(argv) {
80
85
  version: version(),
81
86
  credentialsFile: credentialsPath(),
82
87
  };
83
- // `preview` takes the record it opens as words of its own
84
- switch (group === "preview" ? group : `${group} ${sub ?? ""}`.trim()) {
88
+ // commands of one word: what follows them is theirs (`preview` takes the
89
+ // record it opens, `tool` the tool it calls)
90
+ switch (ROOT_COMMANDS.has(group) ? group : `${group} ${sub ?? ""}`.trim()) {
91
+ case "connect":
92
+ await connect(ctx, flags);
93
+ return 0;
94
+ case "tools":
95
+ await tools(ctx, flags);
96
+ return 0;
97
+ case "tool":
98
+ return tool(ctx, flags, words.slice(1));
85
99
  case "auth login":
86
100
  await login(ctx, flags);
87
101
  return 0;
package/dist/oauth.d.ts CHANGED
@@ -30,6 +30,8 @@ export declare function registerClient(api: string, f: Fetch): Promise<string>;
30
30
  creates nothing on the server. */
31
31
  export declare function clientKnown(api: string, clientId: string, f: Fetch): Promise<boolean>;
32
32
  export type AuthorizeDeps = {
33
+ /** The project the consent screen opens on — a hint; the person still picks. */
34
+ project?: string;
33
35
  openBrowser: (url: string) => Promise<boolean>;
34
36
  log: (line: string) => void;
35
37
  timeoutMs?: number;
package/dist/oauth.js CHANGED
@@ -149,6 +149,7 @@ export function authorize(api, clientId, deps) {
149
149
  state,
150
150
  scope: SCOPE,
151
151
  resource: `${api}/mcp`,
152
+ ...(deps.project ? { project: deps.project } : {}),
152
153
  }).toString();
153
154
  deps.log(`Opening your browser to sign in to Mapled. If it doesn't open, visit:\n ${url.toString()}`);
154
155
  deps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/cli",
3
- "version": "0.14.6",
3
+ "version": "0.16.0",
4
4
  "description": "Mapled CLI — sign in, link a project, generate TypeScript types for its content, create forms, preview drafts and check the site's integration.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",
@@ -9,6 +9,8 @@
9
9
  "cms",
10
10
  "headless-cms",
11
11
  "cli",
12
+ "ai-agents",
13
+ "mcp",
12
14
  "typescript",
13
15
  "codegen"
14
16
  ],