pdauth 1.0.1

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 (60) hide show
  1. package/PROXY-SPEC.md +159 -0
  2. package/README.md +142 -0
  3. package/bin/ga.mjs +329 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +88 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/apps.d.ts +8 -0
  9. package/dist/commands/apps.d.ts.map +1 -0
  10. package/dist/commands/apps.js +76 -0
  11. package/dist/commands/apps.js.map +1 -0
  12. package/dist/commands/config.d.ts +5 -0
  13. package/dist/commands/config.d.ts.map +1 -0
  14. package/dist/commands/config.js +58 -0
  15. package/dist/commands/config.js.map +1 -0
  16. package/dist/commands/connect.d.ts +6 -0
  17. package/dist/commands/connect.d.ts.map +1 -0
  18. package/dist/commands/connect.js +67 -0
  19. package/dist/commands/connect.js.map +1 -0
  20. package/dist/commands/index.d.ts +7 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js +7 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/commands/proxy.d.ts +12 -0
  25. package/dist/commands/proxy.d.ts.map +1 -0
  26. package/dist/commands/proxy.js +127 -0
  27. package/dist/commands/proxy.js.map +1 -0
  28. package/dist/commands/status.d.ts +10 -0
  29. package/dist/commands/status.d.ts.map +1 -0
  30. package/dist/commands/status.js +117 -0
  31. package/dist/commands/status.js.map +1 -0
  32. package/dist/commands/tools.d.ts +10 -0
  33. package/dist/commands/tools.d.ts.map +1 -0
  34. package/dist/commands/tools.js +123 -0
  35. package/dist/commands/tools.js.map +1 -0
  36. package/dist/config.d.ts +21 -0
  37. package/dist/config.d.ts.map +1 -0
  38. package/dist/config.js +59 -0
  39. package/dist/config.js.map +1 -0
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +6 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/pipedream.d.ts +52 -0
  45. package/dist/pipedream.d.ts.map +1 -0
  46. package/dist/pipedream.js +142 -0
  47. package/dist/pipedream.js.map +1 -0
  48. package/package.json +46 -0
  49. package/src/cli.ts +111 -0
  50. package/src/commands/apps.ts +88 -0
  51. package/src/commands/config.ts +68 -0
  52. package/src/commands/connect.ts +76 -0
  53. package/src/commands/index.ts +6 -0
  54. package/src/commands/proxy.ts +155 -0
  55. package/src/commands/status.ts +143 -0
  56. package/src/commands/tools.ts +150 -0
  57. package/src/config.ts +78 -0
  58. package/src/index.ts +6 -0
  59. package/src/pipedream.ts +216 -0
  60. package/tsconfig.json +20 -0
@@ -0,0 +1,150 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { listMcpTools, invokeMcpTool, listAccounts } from '../pipedream.js';
4
+ import { isConfigured, getDefaultUser } from '../config.js';
5
+
6
+ export async function toolsCommand(
7
+ appSlug: string,
8
+ options: { user?: string; json?: boolean }
9
+ ): Promise<void> {
10
+ if (!isConfigured()) {
11
+ console.error(chalk.red('Not configured. Run: pdauth config'));
12
+ process.exit(1);
13
+ }
14
+
15
+ const userId = options.user || getDefaultUser();
16
+ const spinner = ora(`Fetching tools for ${appSlug}...`).start();
17
+
18
+ try {
19
+ // Check if app is connected
20
+ const accounts = await listAccounts(userId);
21
+ const connected = accounts.find(a => a.app.nameSlug === appSlug);
22
+
23
+ if (!connected) {
24
+ spinner.fail(`App not connected: ${appSlug}`);
25
+ console.log(chalk.gray(`\nRun: pdauth connect ${appSlug} --user ${userId}`));
26
+ process.exit(1);
27
+ }
28
+
29
+ const tools = await listMcpTools(userId, appSlug);
30
+ spinner.stop();
31
+
32
+ if (options.json) {
33
+ console.log(JSON.stringify(tools, null, 2));
34
+ return;
35
+ }
36
+
37
+ console.log(chalk.bold(`\n🔧 Tools for ${chalk.cyan(appSlug)} (${tools.length} available)\n`));
38
+
39
+ if (tools.length === 0) {
40
+ console.log(chalk.yellow(' No tools available for this app.\n'));
41
+ return;
42
+ }
43
+
44
+ for (const tool of tools) {
45
+ console.log(` ${chalk.cyan(tool.name)}`);
46
+ if (tool.description) {
47
+ console.log(` ${chalk.gray(tool.description.slice(0, 80))}${tool.description.length > 80 ? '...' : ''}`);
48
+ }
49
+
50
+ const required = tool.inputSchema?.required || [];
51
+ const props = Object.keys(tool.inputSchema?.properties || {});
52
+
53
+ if (props.length > 0) {
54
+ const propDisplay = props.map(p => required.includes(p) ? chalk.yellow(p) : chalk.gray(p)).join(', ');
55
+ console.log(` ${chalk.dim('params:')} ${propDisplay}`);
56
+ }
57
+ console.log();
58
+ }
59
+
60
+ console.log(chalk.gray(` Invoke: pdauth call ${appSlug}.<tool_name> key=value\n`));
61
+ } catch (error) {
62
+ spinner.fail('Failed to fetch tools');
63
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ export async function callCommand(
69
+ toolSelector: string,
70
+ args: string[],
71
+ options: { user?: string; json?: boolean; args?: string }
72
+ ): Promise<void> {
73
+ if (!isConfigured()) {
74
+ console.error(chalk.red('Not configured. Run: pdauth config'));
75
+ process.exit(1);
76
+ }
77
+
78
+ // Parse tool selector: app.tool_name
79
+ const [appSlug, ...toolParts] = toolSelector.split('.');
80
+ const toolName = toolParts.join('.');
81
+
82
+ if (!appSlug || !toolName) {
83
+ console.error(chalk.red('Invalid tool selector. Use: app_slug.tool_name'));
84
+ console.error(chalk.gray('Example: pdauth call slack.send_message channel=general text="Hello"'));
85
+ process.exit(1);
86
+ }
87
+
88
+ const userId = options.user || getDefaultUser();
89
+
90
+ // Parse arguments
91
+ let toolArgs: Record<string, unknown> = {};
92
+
93
+ if (options.args) {
94
+ // JSON args from --args flag
95
+ try {
96
+ toolArgs = JSON.parse(options.args);
97
+ } catch {
98
+ console.error(chalk.red('Invalid JSON in --args'));
99
+ process.exit(1);
100
+ }
101
+ } else {
102
+ // Parse key=value args
103
+ for (const arg of args) {
104
+ const eqIndex = arg.indexOf('=');
105
+ if (eqIndex === -1) continue;
106
+
107
+ const key = arg.slice(0, eqIndex);
108
+ let value: unknown = arg.slice(eqIndex + 1);
109
+
110
+ // Try to parse as JSON for complex values
111
+ try {
112
+ value = JSON.parse(value as string);
113
+ } catch {
114
+ // Keep as string
115
+ }
116
+
117
+ toolArgs[key] = value;
118
+ }
119
+ }
120
+
121
+ const spinner = ora(`Calling ${appSlug}.${toolName}...`).start();
122
+
123
+ try {
124
+ // Check if app is connected
125
+ const accounts = await listAccounts(userId);
126
+ const connected = accounts.find(a => a.app.nameSlug === appSlug);
127
+
128
+ if (!connected) {
129
+ spinner.fail(`App not connected: ${appSlug}`);
130
+ console.log(chalk.gray(`\nRun: pdauth connect ${appSlug} --user ${userId}`));
131
+ process.exit(1);
132
+ }
133
+
134
+ const result = await invokeMcpTool(userId, appSlug, toolName, toolArgs);
135
+ spinner.stop();
136
+
137
+ if (options.json) {
138
+ console.log(JSON.stringify(result, null, 2));
139
+ return;
140
+ }
141
+
142
+ console.log(chalk.bold(`\n✅ ${appSlug}.${toolName} completed\n`));
143
+ console.log(JSON.stringify(result, null, 2));
144
+ console.log();
145
+ } catch (error) {
146
+ spinner.fail('Tool call failed');
147
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
148
+ process.exit(1);
149
+ }
150
+ }
package/src/config.ts ADDED
@@ -0,0 +1,78 @@
1
+ import Conf from 'conf';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+
5
+ export interface PdAuthConfig {
6
+ clientId: string;
7
+ clientSecret: string;
8
+ projectId: string;
9
+ environment: 'development' | 'production';
10
+ }
11
+
12
+ export interface UserMapping {
13
+ [externalUserId: string]: {
14
+ label?: string;
15
+ createdAt: string;
16
+ };
17
+ }
18
+
19
+ const schema = {
20
+ clientId: { type: 'string' as const, default: '' },
21
+ clientSecret: { type: 'string' as const, default: '' },
22
+ projectId: { type: 'string' as const, default: '' },
23
+ environment: { type: 'string' as const, default: 'development' },
24
+ defaultUser: { type: 'string' as const, default: 'default' },
25
+ users: { type: 'object' as const, default: {} },
26
+ };
27
+
28
+ const config = new Conf({
29
+ projectName: 'pdauth',
30
+ cwd: join(homedir(), '.config', 'pdauth'),
31
+ schema,
32
+ });
33
+
34
+ export function getConfig(): PdAuthConfig {
35
+ return {
36
+ clientId: config.get('clientId') as string,
37
+ clientSecret: config.get('clientSecret') as string,
38
+ projectId: config.get('projectId') as string,
39
+ environment: config.get('environment') as 'development' | 'production',
40
+ };
41
+ }
42
+
43
+ export function setConfig(values: Partial<PdAuthConfig>): void {
44
+ if (values.clientId !== undefined) config.set('clientId', values.clientId);
45
+ if (values.clientSecret !== undefined) config.set('clientSecret', values.clientSecret);
46
+ if (values.projectId !== undefined) config.set('projectId', values.projectId);
47
+ if (values.environment !== undefined) config.set('environment', values.environment);
48
+ }
49
+
50
+ export function isConfigured(): boolean {
51
+ const cfg = getConfig();
52
+ return !!(cfg.clientId && cfg.clientSecret && cfg.projectId);
53
+ }
54
+
55
+ export function getDefaultUser(): string {
56
+ return config.get('defaultUser') as string;
57
+ }
58
+
59
+ export function setDefaultUser(userId: string): void {
60
+ config.set('defaultUser', userId);
61
+ addUser(userId);
62
+ }
63
+
64
+ export function getUsers(): UserMapping {
65
+ return config.get('users') as UserMapping;
66
+ }
67
+
68
+ export function addUser(userId: string, label?: string): void {
69
+ const users = getUsers();
70
+ if (!users[userId]) {
71
+ users[userId] = { createdAt: new Date().toISOString(), label };
72
+ config.set('users', users);
73
+ }
74
+ }
75
+
76
+ export function getConfigPath(): string {
77
+ return config.path;
78
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // pdauth - Pipedream OAuth CLI
2
+ // Programmatic API for library usage
3
+
4
+ export * from './config.js';
5
+ export * from './pipedream.js';
6
+ export * from './commands/index.js';
@@ -0,0 +1,216 @@
1
+ import { PipedreamClient } from '@pipedream/sdk';
2
+ import { getConfig, isConfigured } from './config.js';
3
+
4
+ let _client: PipedreamClient | null = null;
5
+
6
+ export function getClient(): PipedreamClient {
7
+ if (!isConfigured()) {
8
+ throw new Error('Pipedream not configured. Run: pdauth config');
9
+ }
10
+
11
+ if (!_client) {
12
+ const config = getConfig();
13
+ _client = new PipedreamClient({
14
+ projectEnvironment: config.environment,
15
+ clientId: config.clientId,
16
+ clientSecret: config.clientSecret,
17
+ projectId: config.projectId,
18
+ });
19
+ }
20
+ return _client;
21
+ }
22
+
23
+ export function resetClient(): void {
24
+ _client = null;
25
+ }
26
+
27
+ export interface ConnectLinkResult {
28
+ token: string;
29
+ expiresAt: string;
30
+ connectLinkUrl: string;
31
+ }
32
+
33
+ export async function createConnectLink(
34
+ externalUserId: string,
35
+ appSlug: string
36
+ ): Promise<ConnectLinkResult> {
37
+ const client = getClient();
38
+
39
+ const response = await client.tokens.create({
40
+ externalUserId,
41
+ });
42
+
43
+ const expiresAt =
44
+ typeof response.expiresAt === 'string'
45
+ ? response.expiresAt
46
+ : response.expiresAt
47
+ ? new Date(response.expiresAt).toISOString()
48
+ : new Date(Date.now() + 3600000).toISOString();
49
+
50
+ // Build connect link URL with app parameter
51
+ const baseUrl = response.connectLinkUrl || 'https://connect.pipedream.com';
52
+ const separator = baseUrl.includes('?') ? '&' : '?';
53
+ const connectLinkUrl = `${baseUrl}${separator}app=${encodeURIComponent(appSlug)}&connectLink=true`;
54
+
55
+ return {
56
+ token: response.token ?? '',
57
+ expiresAt,
58
+ connectLinkUrl,
59
+ };
60
+ }
61
+
62
+ export interface PipedreamAccount {
63
+ id: string;
64
+ name: string;
65
+ externalId?: string;
66
+ healthy: boolean;
67
+ dead: boolean;
68
+ app: {
69
+ id: string;
70
+ nameSlug: string;
71
+ name: string;
72
+ imgSrc?: string;
73
+ };
74
+ createdAt: string;
75
+ updatedAt: string;
76
+ }
77
+
78
+ export async function listAccounts(externalUserId: string): Promise<PipedreamAccount[]> {
79
+ const client = getClient();
80
+ const response = await client.accounts.list({ externalUserId });
81
+ return (response.data ?? []) as unknown as PipedreamAccount[];
82
+ }
83
+
84
+ export async function deleteAccount(accountId: string): Promise<boolean> {
85
+ const client = getClient();
86
+ try {
87
+ await client.accounts.delete(accountId);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ export interface PipedreamApp {
95
+ id: string;
96
+ nameSlug: string;
97
+ name: string;
98
+ authType: string;
99
+ description: string;
100
+ imgSrc: string;
101
+ categories: string[];
102
+ }
103
+
104
+ export async function searchApps(query?: string): Promise<PipedreamApp[]> {
105
+ const client = getClient();
106
+ const response = await client.apps.list(query ? { q: query } : {});
107
+ return (response.data ?? []) as unknown as PipedreamApp[];
108
+ }
109
+
110
+ export async function getApp(nameSlug: string): Promise<PipedreamApp | null> {
111
+ const apps = await searchApps(nameSlug);
112
+ return apps.find(app => app.nameSlug === nameSlug) || null;
113
+ }
114
+
115
+ export async function getAccessToken(): Promise<string> {
116
+ const client = getClient();
117
+ return client.rawAccessToken;
118
+ }
119
+
120
+ export function getMcpServerUrl(): string {
121
+ return 'https://remote.mcp.pipedream.net';
122
+ }
123
+
124
+ export async function getMcpHeaders(externalUserId: string, appSlug?: string): Promise<Record<string, string>> {
125
+ const accessToken = await getAccessToken();
126
+ const config = getConfig();
127
+
128
+ const headers: Record<string, string> = {
129
+ 'Authorization': `Bearer ${accessToken}`,
130
+ 'x-pd-project-id': config.projectId,
131
+ 'x-pd-environment': config.environment,
132
+ 'x-pd-external-user-id': externalUserId,
133
+ };
134
+
135
+ if (appSlug) {
136
+ headers['x-pd-app-slug'] = appSlug;
137
+ }
138
+
139
+ return headers;
140
+ }
141
+
142
+ export interface McpTool {
143
+ name: string;
144
+ description: string;
145
+ inputSchema: {
146
+ type: string;
147
+ properties: Record<string, unknown>;
148
+ required?: string[];
149
+ };
150
+ }
151
+
152
+ export async function listMcpTools(externalUserId: string, appSlug: string): Promise<McpTool[]> {
153
+ const headers = await getMcpHeaders(externalUserId, appSlug);
154
+
155
+ const response = await fetch(getMcpServerUrl(), {
156
+ method: 'POST',
157
+ headers: {
158
+ ...headers,
159
+ 'Content-Type': 'application/json',
160
+ 'Accept': 'application/json, text/event-stream',
161
+ },
162
+ body: JSON.stringify({
163
+ jsonrpc: '2.0',
164
+ method: 'tools/list',
165
+ id: Date.now(),
166
+ }),
167
+ });
168
+
169
+ const text = await response.text();
170
+ // Parse SSE format: "event: message\ndata: {...}"
171
+ const dataMatch = text.match(/^data: (.+)$/m);
172
+ if (!dataMatch) {
173
+ throw new Error('Invalid MCP response');
174
+ }
175
+ const data = JSON.parse(dataMatch[1]) as { result?: { tools?: McpTool[] } };
176
+ return data.result?.tools ?? [];
177
+ }
178
+
179
+ export async function invokeMcpTool(
180
+ externalUserId: string,
181
+ appSlug: string,
182
+ toolName: string,
183
+ args: Record<string, unknown>
184
+ ): Promise<unknown> {
185
+ const headers = await getMcpHeaders(externalUserId, appSlug);
186
+
187
+ const response = await fetch(getMcpServerUrl(), {
188
+ method: 'POST',
189
+ headers: {
190
+ ...headers,
191
+ 'Content-Type': 'application/json',
192
+ 'Accept': 'application/json, text/event-stream',
193
+ },
194
+ body: JSON.stringify({
195
+ jsonrpc: '2.0',
196
+ method: 'tools/call',
197
+ params: {
198
+ name: toolName,
199
+ arguments: args,
200
+ },
201
+ id: Date.now(),
202
+ }),
203
+ });
204
+
205
+ const text = await response.text();
206
+ // Parse SSE format: "event: message\ndata: {...}"
207
+ const dataMatch = text.match(/^data: (.+)$/m);
208
+ if (!dataMatch) {
209
+ throw new Error(`Invalid MCP response: ${text.slice(0, 200)}`);
210
+ }
211
+ const data = JSON.parse(dataMatch[1]) as { result?: unknown; error?: unknown };
212
+ if (data.error) {
213
+ throw new Error(JSON.stringify(data.error));
214
+ }
215
+ return data.result;
216
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }