Skip to content
Draft
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ For agents that don't load skills, `make-endpoints-cli agent --snippet` prints t
2. `make-endpoints-cli list --team-id <id>` lists the apps the team can use, and `list <app> --team-id <id>` their endpoints with usable connections. Skip it when you already know the app and endpoint.
3. `make-endpoints-cli describe <app> <endpoint>` shows the input fields and the connection types the endpoint accepts.
4. `make-endpoints-cli connections list --team-id <id> --app <app> --endpoint <endpoint>` gives the connection id; prefer a row with `"scoped": true`, but `false` only means Make couldn't confirm the scopes.
5. `make-endpoints-cli <app> <endpoint> --team-id <id> --connection-id <id> --<field> <value>` calls it; `--input '{...}'` passes the whole input as JSON.
5. `make-endpoints-cli <app> <endpoint> --team-id <id> --connection-id <id> --<field> <value>` calls it; `--input '{...}'` passes the whole input as JSON, `--confidential` keeps the data out of Make's execution log.

Output is JSON. Exit code 2 is a Make API error, 1 a usage error. Never guess ids. Full guide: `make-endpoints-cli agent`.
```
Expand Down
4 changes: 3 additions & 1 deletion skills/make-endpoints-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ make-endpoints-cli endpoints execute --app-name app#my-app --app-version 1 --end
Each input field is a flag (`documentId` becomes `--document-id`). `--input` takes the whole input as JSON, and
separate flags override its keys. A field whose flag would clash with a global one (`output`, `zone`, ...) can
only be passed through `--input`. `endpoints execute` reaches custom apps (`app#name`) and endpoints this build
doesn't bundle. The output is the endpoint's result, unwrapped. For example, the title of a Google Doc:
doesn't bundle. Add `--confidential` when the data must not stay in Make's Endpoint execution log: the run is
still listed there, but without its input, output or error. The output is the endpoint's result, unwrapped. For
example, the title of a Google Doc:

```sh
make-endpoints-cli connections list --team-id 7 --app google-docs --endpoint get-document | jq '.[] | {id, name, scoped}'
Expand Down
2 changes: 1 addition & 1 deletion skills/make-endpoints-cli/snippet.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
2. `make-endpoints-cli list --team-id <id>` lists the apps the team can use, and `list <app> --team-id <id>` their endpoints with usable connections. Skip it when you already know the app and endpoint.
3. `make-endpoints-cli describe <app> <endpoint>` shows the input fields and the connection types the endpoint accepts.
4. `make-endpoints-cli connections list --team-id <id> --app <app> --endpoint <endpoint>` gives the connection id; prefer a row with `"scoped": true`, but `false` only means Make couldn't confirm the scopes.
5. `make-endpoints-cli <app> <endpoint> --team-id <id> --connection-id <id> --<field> <value>` calls it; `--input '{...}'` passes the whole input as JSON.
5. `make-endpoints-cli <app> <endpoint> --team-id <id> --connection-id <id> --<field> <value>` calls it; `--input '{...}'` passes the whole input as JSON, `--confidential` keeps the data out of Make's execution log.

Output is JSON. Exit code 2 is a Make API error, 1 a usage error. Never guess ids. Full guide: `make-endpoints-cli agent`.
3 changes: 2 additions & 1 deletion src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Start here (agents):
4. connections list --team-id <id> --app <app> --endpoint <endpoint>
which --connection-id to pass; prefer "scoped": true, false is unconfirmed
5. <app> <endpoint> --team-id <id> --connection-id <id> --<field> <value>
call it; --input '{...}' passes the whole input as JSON instead
call it; --input '{...}' passes the whole input as JSON instead,
--confidential keeps the data out of Make's execution log
Output is JSON by default; --output table gives a quick look. Exit code 2 = Make API error, 1 = usage error.
Full guide: make-endpoints-cli agent`;

Expand Down
5 changes: 5 additions & 0 deletions src/lib/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export type EndpointPointer = {
export type EndpointOptions = {
input?: JSONValue;
connectionId?: number;
/**
* Run confidentially, like a scenario with "Data is confidential": Make keeps no input, output or
* error data of the execution in its Endpoint execution log, only metadata. Defaults to false.
*/
confidential?: boolean;
};

export type EndpointCaller = <OUTPUT>(
Expand Down
23 changes: 19 additions & 4 deletions src/lib/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,24 @@ export type UsableConnection = {
const SCOPE = 'endpoints:run';

/** Tool arguments that aren't input fields. Input fields with these names are only reachable through `input`. */
const RESERVED_ARGUMENTS = new Set(['teamId', 'connectionId', 'input']);
const RESERVED_ARGUMENTS = new Set(['teamId', 'connectionId', 'confidential', 'input']);

const TEAM_ID_SCHEMA: JSONSchema = {
type: 'number',
description: 'The ID of the Team the Endpoint is executed in.',
};

const CONFIDENTIAL_SCHEMA: JSONSchema = {
type: 'boolean',
description:
'Run confidentially: Make keeps no input, output or error data of the execution in its Endpoint execution log, only metadata. Defaults to false.',
};

type CallEndpointParams = {
pointer: EndpointPointer;
teamId: JSONValue;
connectionId: JSONValue;
confidential: JSONValue;
input: JSONValue;
};

Expand All @@ -123,14 +130,17 @@ const _toKebabCase = (name: string): string => {

const _callEndpoint = async (
make: Make,
{ pointer, teamId, connectionId, input }: CallEndpointParams,
{ pointer, teamId, connectionId, confidential, input }: CallEndpointParams,
): Promise<JSONValue> => {
if (typeof teamId !== 'number') {
throw new Error('teamId must be a number.');
}
if (connectionId !== undefined && typeof connectionId !== 'number') {
throw new Error('connectionId must be a number.');
}
if (confidential !== undefined && typeof confidential !== 'boolean') {
throw new Error('confidential must be a boolean.');
}
if (input !== undefined && !_isRecord(input)) {
throw new Error('input must be an object.');
}
Expand All @@ -139,6 +149,7 @@ const _callEndpoint = async (
const { output } = await sdk.execute<JSONValue>(pointer, {
input: input ?? {},
...(connectionId === undefined ? {} : { connectionId }),
...(confidential === undefined ? {} : { confidential }),
});
return output;
};
Expand Down Expand Up @@ -184,6 +195,7 @@ const _buildEndpointTool = (definition: EndpointDefinition, latest: boolean): En
},
}
: {}),
confidential: CONFIDENTIAL_SCHEMA,
...(Object.fromEntries(fields) as Record<string, JSONSchema>),
input: {
...(definition.inputSchema as JSONSchema),
Expand All @@ -196,7 +208,7 @@ const _buildEndpointTool = (definition: EndpointDefinition, latest: boolean): En
},
definition,
latest,
execute: async (make, { teamId, connectionId, input, ...rest }) => {
execute: async (make, { teamId, connectionId, confidential, input, ...rest }) => {
const merged = { ...(input !== undefined && _isRecord(input) ? input : {}), ...rest };
const missing = requiredFields.filter((field) => merged[field] === undefined);
if (missing.length > 0) {
Expand All @@ -206,6 +218,7 @@ const _buildEndpointTool = (definition: EndpointDefinition, latest: boolean): En
pointer: { appName, appVersion, endpointName },
teamId,
connectionId,
confidential,
input: merged,
});
},
Expand Down Expand Up @@ -238,14 +251,15 @@ const EXECUTE_TOOL: EndpointTool = {
type: 'number',
description: 'ID of the Connection to be used for executing the Endpoint.',
},
confidential: CONFIDENTIAL_SCHEMA,
input: {
type: 'object',
description: 'Object containing input values of the Endpoint.',
},
},
required: ['appName', 'appVersion', 'endpointName', 'teamId'],
},
execute: async (make, { appName, appVersion, endpointName, teamId, connectionId, input }) => {
execute: async (make, { appName, appVersion, endpointName, teamId, connectionId, confidential, input }) => {
if (
typeof appName !== 'string' ||
typeof appVersion !== 'number' ||
Expand All @@ -257,6 +271,7 @@ const EXECUTE_TOOL: EndpointTool = {
pointer: { appName, appVersion, endpointName },
teamId,
connectionId,
confidential,
input,
});
},
Expand Down
20 changes: 19 additions & 1 deletion src/test/cli/commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('command tree', () => {
(option) => option.long,
);

expect(flags).toEqual(['--team-id', '--include-archived', '--limit', '--tags', '--input']);
expect(flags).toEqual(['--team-id', '--confidential', '--include-archived', '--limit', '--tags', '--input']);
});

it('marks required flags, including required input fields, in their descriptions', () => {
Expand Down Expand Up @@ -199,6 +199,24 @@ describe('endpoint commands', () => {
});
});

it('sends --confidential as the body flag, not as an input field', async () => {
await run([
'alpha',
'get-item',
'--item-id',
'i1',
'--team-id',
'7',
'--connection-id',
'9',
'--input',
'{"teamId":"t"}',
'--confidential',
]);

expect(lastBody()).toMatchObject({ confidential: true, input: { itemId: 'i1', teamId: 't' } });
});

it('lets separate flags override keys of --input', async () => {
await run([
'alpha',
Expand Down
46 changes: 46 additions & 0 deletions src/test/tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ describe('buildEndpointTools', () => {
expect(Object.keys(inputSchema.properties ?? {})).toEqual([
'teamId',
'connectionId',
'confidential',
'itemId',
'input',
]);
expect(inputSchema.properties?.confidential).toMatchObject({ type: 'boolean' });
expect(inputSchema.properties?.teamId).toEqual({
type: 'number',
description: 'The ID of the Team the Endpoint is executed in.',
Expand Down Expand Up @@ -161,6 +163,50 @@ describe('endpoint tool execute', () => {
expect(fetch.mock.calls[0]?.[1]).toMatchObject({ body: { appVersion: 1 } });
});

it('forwards confidential as its own body field, never inside the input', async () => {
const { fetch, make } = createMake();

await getTool('alpha_get-item').execute(make, {
teamId: 7,
connectionId: 9,
confidential: true,
input: { teamId: 'field' },
itemId: 'item-1',
});

expect(fetch.mock.calls[0]?.[1]).toMatchObject({
body: { confidential: true, input: { itemId: 'item-1', teamId: 'field' } },
});
});

it('leaves confidential out of the body when not passed', async () => {
const { fetch, make } = createMake();

await getTool('alpha_get-item').execute(make, {
teamId: 7,
connectionId: 9,
input: { teamId: 'field' },
itemId: 'item-1',
});

expect(fetch.mock.calls[0]?.[1]).toHaveProperty('body');
expect(fetch.mock.calls[0]?.[1]?.body).not.toHaveProperty('confidential');
});

it('rejects a non-boolean confidential', async () => {
const { make } = createMake();

await expect(
getTool('alpha_get-item').execute(make, {
teamId: 7,
connectionId: 9,
confidential: 'yes',
input: { teamId: 'field' },
itemId: 'item-1',
}),
).rejects.toThrow('confidential must be a boolean.');
});

it('sends an empty input and no connection when there are none', async () => {
const { fetch, make } = createMake();

Expand Down
11 changes: 11 additions & 0 deletions src/test/transport/sdk-transport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ describe('SdkTransport driving execute()', () => {
});
});

it('posts the confidential flag next to the input when set', async () => {
const { fetch, sdk } = createSdk();

await sdk.execute(POINTER, { input: INPUT, connectionId: 42, confidential: true });

expect(fetch).toHaveBeenCalledWith('/endpoints/execute', {
method: 'POST',
body: { ...POINTER, input: INPUT, connectionId: 42, confidential: true, teamId: 77 },
});
});

it('resolves to the wrapped envelope', async () => {
const { sdk } = createSdk();

Expand Down
Loading