Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## Overview

This document provides instructions for AI agents on how to work with and extend the Make CLI repository. The CLI is a standalone command-line tool that interacts with the Make automation platform. It depends on `@makehq/sdk` for all API access, types, and MCP tool definitions.
This document provides instructions for AI agents on how to work with and extend the Make CLI repository. The CLI is a standalone command-line tool that interacts with the Make automation platform. It depends on `@makehq/sdk` for all API access, types, and tool definitions.

## Repository Structure

```
make-cli/
├── src/
│ ├── index.ts # Executable entry point: sets up Commander, registers all commands
│ ├── commands.ts # Builds CLI commands from @makehq/sdk MCP tool definitions
│ ├── commands.ts # Builds CLI commands from @makehq/sdk tool definitions
│ ├── auth.ts # Resolves API key and zone from flags, env vars, or config file
│ ├── config.ts # Reads/writes local credentials file (~/.config/make-cli/config.json)
│ ├── login.ts # Hand-crafted login, logout, whoami commands
Expand All @@ -31,29 +31,53 @@ make-cli/

All API functionality comes from the `@makehq/sdk` package. The CLI imports:

| Import | Source | Purpose |
| -------------- | ----------------- | ------------------------------------------------ |
| `Make` | `@makehq/sdk` | API client — instantiated per command invocation |
| `MakeError` | `@makehq/sdk` | Typed API error with `statusCode` and `message` |
| `JSONValue` | `@makehq/sdk` | Generic JSON value type |
| `MakeMCPTools` | `@makehq/sdk/mcp` | Array of all MCP tool definitions |
| `MakeMCPTool` | `@makehq/sdk/mcp` | Type describing a single MCP tool |
| `JSONSchema` | `@makehq/sdk/mcp` | JSON Schema type for tool input parameters |
| Import | Source | Purpose |
| ------------- | ------------------- | --------------------------------------------------- |
| `Make` | `@makehq/sdk` | API client — instantiated per command invocation |
| `MakeError` | `@makehq/sdk` | Typed API error with `statusCode` and `message` |
| `JSONValue` | `@makehq/sdk` | Generic JSON value type |
| `MakeTools` | `@makehq/sdk/tools` | Array of all Make SDK tool definitions |
| `MakeTool` | `@makehq/sdk/tools` | Type describing a single tool |
| `JSONSchema` | `@makehq/sdk/tools` | JSON Schema type for tool input parameters |

## How the CLI Works

The CLI uses an **auto-discovery pattern**: it reads the `MakeMCPTools` array from `@makehq/sdk/mcp` and dynamically registers each tool as a CLI subcommand. No command wiring is done by hand.
The CLI uses an **auto-discovery pattern**: it reads the `MakeTools` array from `@makehq/sdk/tools` and dynamically registers each tool as a CLI subcommand. No command wiring is done by hand.

### Command registration flow

1. `src/index.ts` creates a Commander program with global flags (`--api-key`, `--zone`, `--output`)
2. It calls `buildCommands(program, MakeMCPTools)` from `src/commands.ts`
2. It calls `buildCommands(program, MakeTools)` from `src/commands.ts`
3. `buildCommands` groups tools by `tool.category` and creates nested subcommands:
- Category → top-level command (e.g. `scenarios`, `data-stores`, `sdk-apps`)
- Tool action → subcommand (e.g. `list`, `get`, `create`)
4. Each subcommand's options are derived from `tool.inputSchema.properties`
5. On execution, the tool's `execute(make, args)` function is called

### Positional argument for resource-level actions

Tools that operate on a single resource (typically `get` / `update` / `delete` / action-style tools) declare the owning input property via `tool.resourceId` (e.g. `dataStructureId` for `data-structures_get`, `executionId` for `executions_get`, `key` for `data-store-records_update`). For these tools the CLI exposes that value as a positional argument. The original descriptive long-form flag is kept as an alternative for scripted / explicit use, so both of these work and map to the same SDK input:

```
make-cli data-structures get 178
make-cli data-structures get --data-structure-id=178
```

When the tool also declares a parent scope (`tool.scopeId`), that stays a named flag — only the resource's own id becomes positional:

```
make-cli executions get abc --scenario-id=925
```

Behavior details:

- The positional argument is registered as optional at the Commander level (`[resource-id]`) so either invocation style parses cleanly. Presence is enforced in the action handler based on the JSON Schema's `required` list.
- Supplying the value both positionally and via the flag is rejected with an explicit error.
- The positional is not registered when `tool.resourceId` is unset (collection-level `list` / `create`) or when it points at a property that isn't part of the schema.
- Generated help text shows the positional in the Usage line and in an `Arguments:` section; built-in examples are rendered using the positional form.

See `deriveSelfIdentifier` and `registerToolAsCommand` in `src/commands.ts`.

### Tool name → CLI command mapping

Tool names follow `{category}_{action}` where category dots become hyphens:
Expand Down Expand Up @@ -94,7 +118,7 @@ The file is written with mode `0o600` (owner-read/write only on Unix) and uses a
| `make-cli logout` | Removes the local credentials file |
| `make-cli whoami` | Calls `make.users.me()` and prints `name`, `email`, and `zone` |

These are intentionally separate from `buildCommands` — they are not auto-discovered from MCP tools.
These are intentionally separate from `buildCommands` — they are not auto-discovered from SDK tools.

### Output formatting

Expand All @@ -106,9 +130,9 @@ Controlled by the global `--output` flag (default: `json`):

## Adding New Commands

New CLI commands come automatically from new MCP tools added in `@makehq/sdk`. To add a command:
New CLI commands come automatically from new SDK tools added in `@makehq/sdk`. To add a command:

1. Add or update a `.mcp.ts` file in the `@makehq/sdk` repository following its conventions
1. Add or update a `.tool.ts` file in the `@makehq/sdk` repository following its conventions (setting `resourceId` on resource-level tools so the CLI can register the positional argument)
2. Bump and publish a new version of `@makehq/sdk`
3. Update `@makehq/sdk` version in this repo's `package.json` and run `npm install`
4. No code changes needed in this repo — the new tool is auto-discovered
Expand Down Expand Up @@ -151,7 +175,7 @@ The build produces a single file: `dist/index.js` — an ESM executable with `#!
## TypeScript Guidelines

- Use `type` imports for type-only imports
- All imports from `@makehq/sdk` and `@makehq/sdk/mcp` use the package name (never relative paths into node_modules)
- All imports from `@makehq/sdk` and `@makehq/sdk/tools` use the package name (never relative paths into node_modules)
- Use `.js` extensions in relative imports (e.g. `import { run } from './index.js'`)

## Quality Checklist
Expand Down
43 changes: 33 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,29 +94,35 @@ Flags take priority over environment variables, which take priority over saved c
## Usage

```
make-cli [options] <category> <action> [options]
make-cli [options] <category> <action> [resource-id] [options]
```

### Global Options

| Option | Description |
| ----------------- | ------------------------------------------------------------------ |
| `-V, --version` | Output the version number |
| `--api-key <key>` | Make API key (or set `MAKE_API_KEY`) |
| `--zone <zone>` | Make zone (e.g. `eu2.make.com`) (or set `MAKE_ZONE`) |
| `--output` | Output format: `json` (default), `compact`, `table` |
| `-h, --help` | Display help for the command |
| Option | Description |
| ----------------- | ---------------------------------------------------- |
| `-V, --version` | Output the version number |
| `--api-key <key>` | Make API key (or set `MAKE_API_KEY`) |
| `--zone <zone>` | Make zone (e.g. `eu2.make.com`) (or set `MAKE_ZONE`) |
| `--output` | Output format: `json` (default), `compact`, `table` |
| `-h, --help` | Display help for the command |

### Examples

```bash
# Listing — scope is always a named flag
make-cli scenarios list --team-id=123
make-cli scenarios get --scenario-id=456
make-cli connections list --team-id=123
make-cli data-stores list --team-id=123
make-cli data-store-records list --data-store-id=1
make-cli teams list --organization-id=1
make-cli users me

# Resource-level actions — resource id as positional argument
make-cli scenarios get 456
make-cli data-structures get 178
make-cli data-store-records update ecc4819b2260 \
--data-store-id=137 \
--data='{"status":"inactive"}'

# Creating a scenario
make-cli scenarios create \
Expand All @@ -128,6 +134,23 @@ make-cli scenarios create \
make-cli scenarios list --team-id=123 --output=table
```

### Resource IDs

Resource-level actions (`get`, `update`, `delete`, and similar) accept the resource's own ID as a **positional argument**. The long-form flag is still available for scripting or when you prefer being explicit — both forms are equivalent:

```bash
make-cli scenarios get 456
make-cli scenarios get --scenario-id=456
```

Parent scopes (e.g. `--team-id`, `--organization-id`, or `--scenario-id` on nested resources) stay as named flags, so only the resource's own id is positional:

```bash
make-cli executions get a07e16f2ad134bf49cf83a00aa95c0a5 --scenario-id=925
```

Collection-level actions (`list`, `create`, ...) have no positional ID — every input is a named flag.

### Commands

Commands are organized by category:
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@makehq/cli",
"version": "1.3.1",
"version": "1.4.0",
"description": "A command-line tool for Make automation platform",
"license": "MIT",
"author": "Make",
Expand Down Expand Up @@ -28,7 +28,7 @@
],
"dependencies": {
"@inquirer/prompts": "^8.3.2",
"@makehq/sdk": "^1.2.1",
"@makehq/sdk": "^1.3.0",
"commander": "^14.0.3",
"open": "^11.0.0"
},
Expand Down
63 changes: 45 additions & 18 deletions scripts/build-docs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { MakeMCPTools } from '@makehq/sdk/mcp';
import type { MakeMCPTool, JSONSchema } from '@makehq/sdk/mcp';
import { MakeTools } from '@makehq/sdk/tools';
import type { MakeTool, JSONSchema } from '@makehq/sdk/tools';
import { CATEGORY_TITLES, CATEGORY_GROUPS } from '../src/categories.js';
import { camelToKebab, formatExampleCommand } from '../src/examples.js';
import { deriveActionName } from '../src/commands.js';
import { deriveActionName, deriveSelfIdentifier } from '../src/commands.js';

const DOCS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'docs');

Expand All @@ -18,7 +18,7 @@ function schemaTypeLabel(schema: JSONSchema): string {
return type ?? 'string';
}

function buildToolSection(tool: MakeMCPTool, categorySlug: string): string {
function buildToolSection(tool: MakeTool, categorySlug: string): string {
const action = deriveActionName(tool.name, tool.category);
const lines: string[] = [];

Expand All @@ -30,27 +30,54 @@ function buildToolSection(tool: MakeMCPTool, categorySlug: string): string {
const properties = tool.inputSchema.properties ?? {};
const required = new Set(tool.inputSchema.required ?? []);
const propEntries = Object.entries(properties);
const selfIdProperty = deriveSelfIdentifier(tool);
const selfIdSchema = selfIdProperty ? properties[selfIdProperty] : undefined;
const selfIdFlag = selfIdProperty ? `--${camelToKebab(selfIdProperty)}` : undefined;

if (selfIdProperty && selfIdSchema && selfIdFlag) {
const argName = camelToKebab(selfIdProperty);
const isRequired = required.has(selfIdProperty);
// Keep positional argument notation aligned with the CLI `--help` Usage
// lines, which render this self identifier as `[arg]` regardless of
// logical requiredness — the positional is always registered as
// optional at the Commander level so the `--<arg>` flag alternative
// stays valid. Requiredness is conveyed separately via the Required
// column and the description.
const argToken = `[${argName}]`;
const rawDesc = selfIdSchema.description?.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim() ?? '';
// SDK descriptions generally omit trailing punctuation; normalize to a
// single '.' so the cross-reference clause reads as a second sentence.
const baseDesc = rawDesc ? rawDesc.replace(/[.!?]+$/, '') + '.' : '';
const desc = baseDesc
? `${baseDesc} Can also be passed as \`${selfIdFlag}=<value>\`.`
: `Can also be passed as \`${selfIdFlag}=<value>\`.`;

lines.push('**Arguments**');
lines.push('');
lines.push('| Argument | Description | Required |');
lines.push('|----------|-------------|----------|');
lines.push(`| \`${argToken}\` | ${desc} | ${isRequired ? 'Yes' : 'No'} |`);
lines.push('');
Comment thread
patriksimek marked this conversation as resolved.
}

const flagEntries = propEntries.filter(([propName]) => propName !== selfIdProperty);

if (propEntries.length > 0) {
if (flagEntries.length > 0) {
lines.push('**Options**');
lines.push('');
lines.push('| Option | Description | Required |');
lines.push('|--------|-------------|----------|');

for (const [propName, schema] of propEntries) {
for (const [propName, schema] of flagEntries) {
const flagName = camelToKebab(propName);
const type = schemaTypeLabel(schema);
const isBooleanFlag = type === 'boolean';
const flag = isBooleanFlag
? schema.default === true
? `--no-${flagName}`
: `--${flagName}`
: `--${flagName}`;
const longForm = isBooleanFlag && schema.default === true ? `--no-${flagName}` : `--${flagName}`;

const isRequired = required.has(propName) && !isBooleanFlag;
const propDesc = schema.description?.replace(/\|/g, '\\|').replace(/\n/g, ' ') ?? '';

lines.push(`| \`${flag}\` | ${propDesc} | ${isRequired ? 'Yes' : 'No'} |`);
lines.push(`| \`${longForm}\` | ${propDesc} | ${isRequired ? 'Yes' : 'No'} |`);
}

lines.push('');
Expand All @@ -63,7 +90,7 @@ function buildToolSection(tool: MakeMCPTool, categorySlug: string): string {
const cmd = `make-cli ${categorySlug} ${action}`;
const example = tool.examples?.[0];
if (example && Object.keys(example).length > 0) {
lines.push(formatExampleCommand(cmd, example));
lines.push(formatExampleCommand(cmd, example, selfIdProperty));
} else {
lines.push(cmd);
}
Expand All @@ -73,7 +100,7 @@ function buildToolSection(tool: MakeMCPTool, categorySlug: string): string {
return lines.join('\n');
}

function buildCategoryDoc(categorySlug: string, tools: MakeMCPTool[]): string {
function buildCategoryDoc(categorySlug: string, tools: MakeTool[]): string {
const originalCategory = tools[0]!.category;
const title = CATEGORY_TITLES[originalCategory] ?? categorySlug;
const lines: string[] = [];
Expand All @@ -98,7 +125,7 @@ function buildCategoryDoc(categorySlug: string, tools: MakeMCPTool[]): string {
return lines.join('\n');
}

function buildIndex(categoryMap: Map<string, MakeMCPTool[]>): string {
function buildIndex(categoryMap: Map<string, MakeTool[]>): string {
const lines: string[] = [];

lines.push('# Make CLI Documentation');
Expand Down Expand Up @@ -160,9 +187,9 @@ function buildIndex(categoryMap: Map<string, MakeMCPTool[]>): string {

// --- Main ---

const categoryMap = new Map<string, MakeMCPTool[]>();
const categoryMap = new Map<string, MakeTool[]>();

for (const tool of MakeMCPTools) {
for (const tool of MakeTools) {
const slug = tool.category.replace(/\./g, '-');
const group = categoryMap.get(slug) ?? [];
group.push(tool);
Expand All @@ -181,6 +208,6 @@ for (const [slug, tools] of categoryMap) {
const index = buildIndex(categoryMap);
writeFileSync(join(DOCS_DIR, 'README.md'), index);

const totalTools = MakeMCPTools.length;
const totalTools = MakeTools.length;
const totalCategories = categoryMap.size;
console.log(`Generated docs for ${totalTools} commands across ${totalCategories} categories in docs/`);
Loading