@ductape/cli 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 (155) hide show
  1. package/API_PARITY.md +58 -0
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +89 -0
  4. package/dist/commands/apps-import.d.ts +9 -0
  5. package/dist/commands/apps-import.js +29 -0
  6. package/dist/commands/apps.d.ts +10 -0
  7. package/dist/commands/apps.js +68 -0
  8. package/dist/commands/cloud.d.ts +7 -0
  9. package/dist/commands/cloud.js +42 -0
  10. package/dist/commands/completion.d.ts +1 -0
  11. package/dist/commands/completion.js +113 -0
  12. package/dist/commands/db.d.ts +5 -0
  13. package/dist/commands/db.js +55 -0
  14. package/dist/commands/generate.d.ts +14 -0
  15. package/dist/commands/generate.js +52 -0
  16. package/dist/commands/graph.d.ts +4 -0
  17. package/dist/commands/graph.js +44 -0
  18. package/dist/commands/init.d.ts +7 -0
  19. package/dist/commands/init.js +28 -0
  20. package/dist/commands/install.d.ts +1 -0
  21. package/dist/commands/install.js +42 -0
  22. package/dist/commands/link.d.ts +7 -0
  23. package/dist/commands/link.js +61 -0
  24. package/dist/commands/login.d.ts +9 -0
  25. package/dist/commands/login.js +103 -0
  26. package/dist/commands/logout.d.ts +1 -0
  27. package/dist/commands/logout.js +6 -0
  28. package/dist/commands/platform.d.ts +7 -0
  29. package/dist/commands/platform.js +99 -0
  30. package/dist/commands/products.d.ts +14 -0
  31. package/dist/commands/products.js +74 -0
  32. package/dist/commands/profiles.d.ts +2 -0
  33. package/dist/commands/profiles.js +18 -0
  34. package/dist/commands/resources.d.ts +7 -0
  35. package/dist/commands/resources.js +61 -0
  36. package/dist/commands/secrets.d.ts +6 -0
  37. package/dist/commands/secrets.js +38 -0
  38. package/dist/commands/unlink.d.ts +3 -0
  39. package/dist/commands/unlink.js +14 -0
  40. package/dist/commands/whoami.d.ts +1 -0
  41. package/dist/commands/whoami.js +22 -0
  42. package/dist/commands/workspaces.d.ts +16 -0
  43. package/dist/commands/workspaces.js +72 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.js +283 -0
  46. package/dist/lib/api-client.d.ts +18 -0
  47. package/dist/lib/api-client.js +65 -0
  48. package/dist/lib/app-import.d.ts +15 -0
  49. package/dist/lib/app-import.js +63 -0
  50. package/dist/lib/config.d.ts +63 -0
  51. package/dist/lib/config.js +155 -0
  52. package/dist/lib/context-store.d.ts +17 -0
  53. package/dist/lib/context-store.js +43 -0
  54. package/dist/lib/db-methods.d.ts +3 -0
  55. package/dist/lib/db-methods.js +55 -0
  56. package/dist/lib/encryption.d.ts +1 -0
  57. package/dist/lib/encryption.js +11 -0
  58. package/dist/lib/forms/enums.d.ts +11 -0
  59. package/dist/lib/forms/enums.js +42 -0
  60. package/dist/lib/forms/index.d.ts +2 -0
  61. package/dist/lib/forms/index.js +2 -0
  62. package/dist/lib/forms/prompt.d.ts +11 -0
  63. package/dist/lib/forms/prompt.js +203 -0
  64. package/dist/lib/forms/schemas.d.ts +9 -0
  65. package/dist/lib/forms/schemas.js +253 -0
  66. package/dist/lib/forms/types.d.ts +18 -0
  67. package/dist/lib/forms/types.js +1 -0
  68. package/dist/lib/http.d.ts +10 -0
  69. package/dist/lib/http.js +51 -0
  70. package/dist/lib/integrations.d.ts +20 -0
  71. package/dist/lib/integrations.js +20 -0
  72. package/dist/lib/interactive-opts.d.ts +6 -0
  73. package/dist/lib/interactive-opts.js +6 -0
  74. package/dist/lib/output.d.ts +3 -0
  75. package/dist/lib/output.js +22 -0
  76. package/dist/lib/platform-api.d.ts +19 -0
  77. package/dist/lib/platform-api.js +123 -0
  78. package/dist/lib/proxy/context.d.ts +16 -0
  79. package/dist/lib/proxy/context.js +73 -0
  80. package/dist/lib/proxy/db-proxy.d.ts +13 -0
  81. package/dist/lib/proxy/db-proxy.js +28 -0
  82. package/dist/lib/proxy/graph-proxy.d.ts +19 -0
  83. package/dist/lib/proxy/graph-proxy.js +43 -0
  84. package/dist/lib/proxy/sdk-proxy.d.ts +14 -0
  85. package/dist/lib/proxy/sdk-proxy.js +22 -0
  86. package/dist/lib/read-body.d.ts +11 -0
  87. package/dist/lib/read-body.js +38 -0
  88. package/dist/lib/resources.d.ts +12 -0
  89. package/dist/lib/resources.js +147 -0
  90. package/dist/lib/snippets.d.ts +6 -0
  91. package/dist/lib/snippets.js +146 -0
  92. package/dist/lib/templates.d.ts +3 -0
  93. package/dist/lib/templates.js +74 -0
  94. package/dist/lib/workspace-context.d.ts +12 -0
  95. package/dist/lib/workspace-context.js +25 -0
  96. package/dist/lib/workspaces.d.ts +45 -0
  97. package/dist/lib/workspaces.js +219 -0
  98. package/package.json +37 -0
  99. package/src/commands/apps-import.ts +43 -0
  100. package/src/commands/apps.ts +92 -0
  101. package/src/commands/cloud.ts +50 -0
  102. package/src/commands/completion.ts +119 -0
  103. package/src/commands/db.ts +63 -0
  104. package/src/commands/generate.ts +74 -0
  105. package/src/commands/graph.ts +51 -0
  106. package/src/commands/init.ts +39 -0
  107. package/src/commands/install.ts +48 -0
  108. package/src/commands/link.ts +84 -0
  109. package/src/commands/login.ts +145 -0
  110. package/src/commands/logout.ts +7 -0
  111. package/src/commands/platform.ts +123 -0
  112. package/src/commands/products.ts +104 -0
  113. package/src/commands/profiles.ts +23 -0
  114. package/src/commands/resources.ts +85 -0
  115. package/src/commands/secrets.ts +46 -0
  116. package/src/commands/unlink.ts +15 -0
  117. package/src/commands/whoami.ts +27 -0
  118. package/src/commands/workspaces.ts +98 -0
  119. package/src/index.ts +381 -0
  120. package/src/lib/api-client.ts +88 -0
  121. package/src/lib/app-import.ts +82 -0
  122. package/src/lib/config.ts +225 -0
  123. package/src/lib/context-store.ts +77 -0
  124. package/src/lib/db-methods.ts +56 -0
  125. package/src/lib/encryption.ts +12 -0
  126. package/src/lib/forms/enums.ts +52 -0
  127. package/src/lib/forms/index.ts +8 -0
  128. package/src/lib/forms/prompt.ts +209 -0
  129. package/src/lib/forms/schemas.ts +288 -0
  130. package/src/lib/forms/types.ts +27 -0
  131. package/src/lib/http.ts +70 -0
  132. package/src/lib/integrations.ts +47 -0
  133. package/src/lib/interactive-opts.ts +13 -0
  134. package/src/lib/output.ts +24 -0
  135. package/src/lib/platform-api.ts +178 -0
  136. package/src/lib/proxy/context.ts +94 -0
  137. package/src/lib/proxy/db-proxy.ts +40 -0
  138. package/src/lib/proxy/graph-proxy.ts +64 -0
  139. package/src/lib/proxy/sdk-proxy.ts +58 -0
  140. package/src/lib/read-body.ts +57 -0
  141. package/src/lib/resources.ts +156 -0
  142. package/src/lib/snippets.ts +165 -0
  143. package/src/lib/templates.ts +78 -0
  144. package/src/lib/workspace-context.ts +43 -0
  145. package/src/lib/workspaces.ts +283 -0
  146. package/templates/dotnet/Program.cs +2 -0
  147. package/templates/go/main.go +8 -0
  148. package/templates/python/ductape_example.py +16 -0
  149. package/templates/typescript/ductape.example.ts +18 -0
  150. package/test/encryption.test.ts +20 -0
  151. package/test/forms.test.ts +25 -0
  152. package/test/platform-api.test.ts +19 -0
  153. package/test/resources.test.ts +23 -0
  154. package/test/workspace-resolve.test.ts +29 -0
  155. package/tsconfig.json +15 -0
@@ -0,0 +1,178 @@
1
+ import { ApiClient } from './api-client.js';
2
+ import { createSdkProxyClient } from './proxy/sdk-proxy.js';
3
+ import type { WorkspaceContext } from './workspace-context.js';
4
+ import { workspaceAuthQuery } from './workspace-context.js';
5
+
6
+ function client(ctx: WorkspaceContext): ApiClient {
7
+ return ApiClient.forProfile(ctx.profile, ctx.credentials);
8
+ }
9
+
10
+ function proxy(ctx: WorkspaceContext) {
11
+ return createSdkProxyClient({
12
+ apiUrl: ctx.apiUrl,
13
+ workspaceId: ctx.workspaceId,
14
+ userId: ctx.credentials.user_id,
15
+ publicKey: ctx.credentials.public_key,
16
+ token: ctx.credentials.auth_token,
17
+ });
18
+ }
19
+
20
+ function unwrapList<T>(result: unknown): T[] {
21
+ if (Array.isArray(result)) return result;
22
+ if (result && typeof result === 'object' && Array.isArray((result as { data?: T[] }).data)) {
23
+ return (result as { data: T[] }).data;
24
+ }
25
+ return [];
26
+ }
27
+
28
+ function unwrapOne<T>(result: unknown): T {
29
+ if (result && typeof result === 'object' && 'data' in result) {
30
+ return (result as { data: T }).data;
31
+ }
32
+ return result as T;
33
+ }
34
+
35
+ // —— Products (integrations REST + sdk-proxy for update) ——
36
+
37
+ export async function listProducts(
38
+ ctx: WorkspaceContext,
39
+ status = 'all',
40
+ ): Promise<unknown[]> {
41
+ const result = await client(ctx).getPath<unknown>(
42
+ `/integrations/v1/workspace/${ctx.workspaceId}/${status}`,
43
+ workspaceAuthQuery(ctx),
44
+ );
45
+ return unwrapList(result);
46
+ }
47
+
48
+ export async function getProduct(
49
+ ctx: WorkspaceContext,
50
+ opts: { id?: string; tag?: string },
51
+ ): Promise<unknown> {
52
+ const q = workspaceAuthQuery(ctx);
53
+ if (opts.id) {
54
+ const result = await client(ctx).getPath<unknown>(`/integrations/v1/${opts.id}`, q);
55
+ return unwrapOne(result);
56
+ }
57
+ if (opts.tag) {
58
+ const result = await client(ctx).getPath<unknown>(`/integrations/v1/fetch/tag`, {
59
+ ...q,
60
+ tag: opts.tag,
61
+ });
62
+ return unwrapOne(result);
63
+ }
64
+ throw new Error('Provide --id <product_id> or --tag <product_tag>');
65
+ }
66
+
67
+ export async function createProduct(ctx: WorkspaceContext, body: Record<string, unknown>): Promise<unknown> {
68
+ const payload = {
69
+ workspace_id: ctx.workspaceId,
70
+ user_id: ctx.credentials.user_id,
71
+ public_key: ctx.credentials.public_key,
72
+ ...body,
73
+ };
74
+ const result = await client(ctx).post<unknown>('/integrations/v1/create', payload);
75
+ return unwrapOne(result);
76
+ }
77
+
78
+ export async function updateProduct(
79
+ ctx: WorkspaceContext,
80
+ tag: string,
81
+ body: Record<string, unknown>,
82
+ ): Promise<unknown> {
83
+ return proxy(ctx).execute('product', 'update', [tag, body]);
84
+ }
85
+
86
+ export async function deleteProduct(ctx: WorkspaceContext, productId: string): Promise<unknown> {
87
+ return client(ctx).delete<unknown>(
88
+ `/integrations/v1/${productId}?${new URLSearchParams(workspaceAuthQuery(ctx)).toString()}`,
89
+ );
90
+ }
91
+
92
+ export async function listProductApps(ctx: WorkspaceContext, productId: string): Promise<unknown[]> {
93
+ const result = await client(ctx).getPath<unknown>(
94
+ `/integrations/v1/fetch-product-apps/${productId}`,
95
+ workspaceAuthQuery(ctx),
96
+ );
97
+ return unwrapList(result);
98
+ }
99
+
100
+ // —— Apps (apps REST) ——
101
+
102
+ export async function listApps(ctx: WorkspaceContext, status = 'all'): Promise<unknown[]> {
103
+ const result = await client(ctx).getPath<unknown>(
104
+ `/apps/v1/workspace/${ctx.workspaceId}/${status}`,
105
+ {
106
+ user_id: ctx.credentials.user_id,
107
+ public_key: ctx.credentials.public_key,
108
+ },
109
+ );
110
+ return unwrapList(result);
111
+ }
112
+
113
+ export async function getApp(
114
+ ctx: WorkspaceContext,
115
+ opts: { id?: string; tag?: string },
116
+ ): Promise<unknown> {
117
+ const q = {
118
+ user_id: ctx.credentials.user_id,
119
+ public_key: ctx.credentials.public_key,
120
+ };
121
+ if (opts.id) {
122
+ const result = await client(ctx).getPath<unknown>(`/apps/v1/${opts.id}`, q);
123
+ return unwrapOne(result);
124
+ }
125
+ if (opts.tag) {
126
+ const result = await client(ctx).getPath<unknown>(`/apps/v1/fetch/tag`, { ...q, tag: opts.tag });
127
+ return unwrapOne(result);
128
+ }
129
+ throw new Error('Provide --id <app_id> or --tag <app_tag>');
130
+ }
131
+
132
+ export async function createApp(ctx: WorkspaceContext, body: Record<string, unknown>): Promise<unknown> {
133
+ const payload = {
134
+ workspace_id: ctx.workspaceId,
135
+ user_id: ctx.credentials.user_id,
136
+ public_key: ctx.credentials.public_key,
137
+ version: '1.0.0',
138
+ logo: '',
139
+ envs: [],
140
+ ...body,
141
+ };
142
+ const result = await client(ctx).post<unknown>('/apps/v1/create', payload);
143
+ return unwrapOne(result);
144
+ }
145
+
146
+ export async function updateApp(
147
+ ctx: WorkspaceContext,
148
+ appId: string,
149
+ body: Record<string, unknown>,
150
+ ): Promise<unknown> {
151
+ const q = {
152
+ user_id: ctx.credentials.user_id,
153
+ public_key: ctx.credentials.public_key,
154
+ };
155
+ const result = await client(ctx).put<unknown>(
156
+ `/apps/v1/${appId}?${new URLSearchParams(q).toString()}`,
157
+ body,
158
+ );
159
+ return unwrapOne(result);
160
+ }
161
+
162
+ export async function updateAppViaProxy(
163
+ ctx: WorkspaceContext,
164
+ tag: string,
165
+ body: Record<string, unknown>,
166
+ ): Promise<unknown> {
167
+ return proxy(ctx).execute('app', 'update', [tag, body]);
168
+ }
169
+
170
+ export async function deleteApp(ctx: WorkspaceContext, appId: string): Promise<unknown> {
171
+ const q = {
172
+ user_id: ctx.credentials.user_id,
173
+ public_key: ctx.credentials.public_key,
174
+ };
175
+ return client(ctx).delete<unknown>(
176
+ `/apps/v1/${appId}?${new URLSearchParams(q).toString()}`,
177
+ );
178
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ findProjectConfig,
3
+ getActiveProfileName,
4
+ getApiUrl,
5
+ loadCredentials,
6
+ loadProjectConfig,
7
+ type Credentials,
8
+ type ResolvedProjectConfig,
9
+ } from '../config.js';
10
+ import { loadRuntimeContext } from '../context-store.js';
11
+ import { createSdkProxyClient, type SdkProxyClient } from './sdk-proxy.js';
12
+ import { DbProxyClient } from './db-proxy.js';
13
+ import { GraphProxyClient } from './graph-proxy.js';
14
+
15
+ export interface SessionContext {
16
+ credentials: Credentials;
17
+ project: ResolvedProjectConfig;
18
+ apiUrl: string;
19
+ }
20
+
21
+ export function requireCredentials(): Credentials {
22
+ const creds = loadCredentials();
23
+ if (!creds?.auth_token || !creds.public_key || !creds.user_id) {
24
+ throw new Error('Not logged in. Run: ductape login');
25
+ }
26
+ return creds;
27
+ }
28
+
29
+ export function requireSession(): SessionContext {
30
+ const credentials = requireCredentials();
31
+ const project = loadProjectConfig();
32
+ const profile = getActiveProfileName(project);
33
+ const apiUrl = getApiUrl(profile);
34
+ return { credentials, project, apiUrl };
35
+ }
36
+
37
+ export function getSdkProxy(session?: SessionContext): SdkProxyClient {
38
+ const s = session ?? requireSession();
39
+ return createSdkProxyClient({
40
+ apiUrl: s.apiUrl,
41
+ workspaceId: s.project.workspace_id,
42
+ userId: s.credentials.user_id,
43
+ publicKey: s.credentials.public_key,
44
+ token: s.credentials.auth_token,
45
+ });
46
+ }
47
+
48
+ export function getDbProxy(session?: SessionContext): DbProxyClient {
49
+ const s = session ?? requireSession();
50
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
51
+ const proxy = new DbProxyClient({
52
+ apiUrl: s.apiUrl,
53
+ workspaceId: s.project.workspace_id,
54
+ userId: s.credentials.user_id,
55
+ publicKey: s.credentials.public_key,
56
+ token: s.credentials.auth_token,
57
+ });
58
+ return proxy;
59
+ }
60
+
61
+ export function getDbContext(session?: SessionContext) {
62
+ const s = session ?? requireSession();
63
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
64
+ return (
65
+ runtime?.database_context ?? {
66
+ database: '',
67
+ env: s.project.env_slug,
68
+ product: s.project.product_tag,
69
+ }
70
+ );
71
+ }
72
+
73
+ export function getGraphProxy(session?: SessionContext): GraphProxyClient {
74
+ const s = session ?? requireSession();
75
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
76
+ return new GraphProxyClient({
77
+ apiUrl: s.apiUrl,
78
+ workspaceId: s.project.workspace_id,
79
+ userId: s.credentials.user_id,
80
+ publicKey: s.credentials.public_key,
81
+ token: s.credentials.auth_token,
82
+ graphContext: runtime?.graph_context ?? null,
83
+ });
84
+ }
85
+
86
+ export function getOptionalProject(): ResolvedProjectConfig | null {
87
+ const found = findProjectConfig();
88
+ if (!found) return null;
89
+ try {
90
+ return loadProjectConfig();
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
@@ -0,0 +1,40 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+ import type { DatabaseContext } from '../context-store.js';
4
+
5
+ export interface DbProxyContext {
6
+ apiUrl: string;
7
+ workspaceId: string;
8
+ userId: string;
9
+ publicKey: string;
10
+ token: string;
11
+ }
12
+
13
+ export class DbProxyClient {
14
+ constructor(private readonly ctx: DbProxyContext) {}
15
+
16
+ async execute<T>(
17
+ method: string,
18
+ params: unknown[],
19
+ databaseContext?: DatabaseContext | null,
20
+ ): Promise<T> {
21
+ const sensitive: Record<string, unknown> = {
22
+ method,
23
+ params,
24
+ user_id: this.ctx.userId,
25
+ };
26
+ if (databaseContext) sensitive.database_context = databaseContext;
27
+
28
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
29
+ const body: Record<string, unknown> = {
30
+ encrypted_payload,
31
+ workspace_id: this.ctx.workspaceId,
32
+ user_id: this.ctx.userId,
33
+ public_key: this.ctx.publicKey,
34
+ };
35
+ if (databaseContext) body.database_context = databaseContext;
36
+
37
+ const url = `${this.ctx.apiUrl}/proxy/v1/db-proxy/execute`;
38
+ return postProxyJson<T>(url, body, { 'x-access-token': this.ctx.token });
39
+ }
40
+ }
@@ -0,0 +1,64 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+
4
+ export interface GraphContext {
5
+ graph: string;
6
+ env: string;
7
+ product: string;
8
+ }
9
+
10
+ export interface GraphProxyContext {
11
+ apiUrl: string;
12
+ workspaceId: string;
13
+ userId: string;
14
+ publicKey: string;
15
+ token: string;
16
+ graphContext?: GraphContext | null;
17
+ }
18
+
19
+ export class GraphProxyClient {
20
+ constructor(private readonly ctx: GraphProxyContext) {}
21
+
22
+ setGraphContext(context: GraphContext | null): void {
23
+ this.ctx.graphContext = context;
24
+ }
25
+
26
+ async execute<T>(
27
+ method: string,
28
+ params: unknown[] = [],
29
+ graphContext?: GraphContext | null,
30
+ ): Promise<T> {
31
+ const ctx = graphContext ?? this.ctx.graphContext;
32
+ const sensitive: Record<string, unknown> = {
33
+ method,
34
+ params,
35
+ user_id: this.ctx.userId,
36
+ };
37
+ if (ctx) sensitive.graph_context = ctx;
38
+
39
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
40
+ const body: Record<string, unknown> = {
41
+ encrypted_payload,
42
+ workspace_id: this.ctx.workspaceId,
43
+ user_id: this.ctx.userId,
44
+ public_key: this.ctx.publicKey,
45
+ };
46
+ if (ctx) body.graph_context = ctx;
47
+
48
+ const url = `${this.ctx.apiUrl}/proxy/v1/graph-proxy/execute`;
49
+ const result = await postProxyJson<T>(url, body, {
50
+ 'x-access-token': this.ctx.token,
51
+ });
52
+
53
+ if (method === 'connect' && params[0] && typeof params[0] === 'object') {
54
+ const cfg = params[0] as GraphContext;
55
+ this.setGraphContext({
56
+ graph: cfg.graph,
57
+ env: cfg.env,
58
+ product: cfg.product,
59
+ });
60
+ }
61
+
62
+ return result;
63
+ }
64
+ }
@@ -0,0 +1,58 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+
4
+ export type SDKModule =
5
+ | 'product'
6
+ | 'app'
7
+ | 'databases'
8
+ | 'graph'
9
+ | 'webhooks'
10
+ | 'notifications'
11
+ | 'messageBrokers'
12
+ | 'storage'
13
+ | 'vector'
14
+ | 'caches'
15
+ | 'sessions'
16
+ | 'quotas'
17
+ | 'actions'
18
+ | 'features'
19
+ | 'jobs'
20
+ | 'logs'
21
+ | 'resilience'
22
+ | 'health'
23
+ | 'fallback'
24
+ | 'secrets'
25
+ | 'cloud';
26
+
27
+ export interface SdkProxyContext {
28
+ apiUrl: string;
29
+ workspaceId: string;
30
+ userId: string;
31
+ publicKey: string;
32
+ token: string;
33
+ }
34
+
35
+ export class SdkProxyClient {
36
+ constructor(private readonly ctx: SdkProxyContext) {}
37
+
38
+ async execute<T>(module: SDKModule, method: string, params: unknown[] = []): Promise<T> {
39
+ const sensitive = { module, method, params, user_id: this.ctx.userId };
40
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
41
+
42
+ const url = `${this.ctx.apiUrl}/proxy/v1/sdk-proxy/execute`;
43
+ return postProxyJson<T>(
44
+ url,
45
+ {
46
+ encrypted_payload,
47
+ workspace_id: this.ctx.workspaceId,
48
+ user_id: this.ctx.userId,
49
+ public_key: this.ctx.publicKey,
50
+ },
51
+ { 'x-access-token': this.ctx.token },
52
+ );
53
+ }
54
+ }
55
+
56
+ export function createSdkProxyClient(ctx: SdkProxyContext): SdkProxyClient {
57
+ return new SdkProxyClient(ctx);
58
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import {
3
+ getFormSchema,
4
+ isInteractive,
5
+ promptForm,
6
+ type InteractiveOpts,
7
+ } from './forms/index.js';
8
+ import type { FormSchema } from './forms/types.js';
9
+
10
+ export function readJsonBody(filePath?: string): unknown {
11
+ if (!filePath) return undefined;
12
+ const raw = fs.readFileSync(filePath, 'utf8');
13
+ return JSON.parse(raw) as unknown;
14
+ }
15
+
16
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
17
+ if (value === undefined) return undefined;
18
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
19
+ return value as Record<string, unknown>;
20
+ }
21
+ throw new Error('Body must be a JSON object');
22
+ }
23
+
24
+ export async function resolveBody(
25
+ opts: InteractiveOpts & {
26
+ file?: string;
27
+ schemaKey?: string;
28
+ schema?: FormSchema;
29
+ patch?: boolean;
30
+ requireBody?: boolean;
31
+ },
32
+ ): Promise<Record<string, unknown> | undefined> {
33
+ if (opts.file) {
34
+ return asRecord(readJsonBody(opts.file));
35
+ }
36
+
37
+ const schema = opts.schema ?? (opts.schemaKey ? getFormSchema(opts.schemaKey) : undefined);
38
+ if (schema && isInteractive(opts)) {
39
+ return promptForm(schema, { patch: opts.patch ?? opts.schemaKey?.endsWith(':update') });
40
+ }
41
+
42
+ if (opts.requireBody && isInteractive(opts) === false) {
43
+ return undefined;
44
+ }
45
+
46
+ if (opts.requireBody) {
47
+ return undefined;
48
+ }
49
+
50
+ return undefined;
51
+ }
52
+
53
+ export function bodyRequiredHint(schemaKey?: string): string {
54
+ const json = 'Pass -f <file.json> or run interactively (TTY)';
55
+ if (schemaKey) return `${json} — schema: ${schemaKey}`;
56
+ return json;
57
+ }
@@ -0,0 +1,156 @@
1
+ import type { SDKModule } from './proxy/sdk-proxy.js';
2
+
3
+ /** CLI resource type → sdk-proxy module */
4
+ export const RESOURCE_MODULES: Record<string, SDKModule> = {
5
+ storage: 'storage',
6
+ databases: 'databases',
7
+ database: 'databases',
8
+ graph: 'graph',
9
+ graphs: 'graph',
10
+ vector: 'vector',
11
+ vectors: 'vector',
12
+ cache: 'caches',
13
+ caches: 'caches',
14
+ job: 'jobs',
15
+ jobs: 'jobs',
16
+ feature: 'features',
17
+ features: 'features',
18
+ action: 'actions',
19
+ actions: 'actions',
20
+ notification: 'notifications',
21
+ notifications: 'notifications',
22
+ broker: 'messageBrokers',
23
+ brokers: 'messageBrokers',
24
+ 'message-brokers': 'messageBrokers',
25
+ messagebrokers: 'messageBrokers',
26
+ session: 'sessions',
27
+ sessions: 'sessions',
28
+ secret: 'secrets',
29
+ secrets: 'secrets',
30
+ quota: 'quotas',
31
+ quotas: 'quotas',
32
+ health: 'health',
33
+ healthcheck: 'health',
34
+ fallback: 'fallback',
35
+ product: 'product',
36
+ app: 'app',
37
+ };
38
+
39
+ export type CrudVerb = 'create' | 'list' | 'get' | 'update' | 'delete' | 'connect';
40
+
41
+ export function resolveResourceType(name: string): SDKModule {
42
+ const mod = RESOURCE_MODULES[name.toLowerCase()];
43
+ if (!mod) {
44
+ throw new Error(
45
+ `Unknown resource type "${name}". Try: storage, databases, graph, product, app, … — or \`ductape products\` / \`ductape apps\` for workspace CRUD`,
46
+ );
47
+ }
48
+ return mod;
49
+ }
50
+
51
+ export function proxyMethodForVerb(verb: CrudVerb): string {
52
+ if (verb === 'get') return 'fetch';
53
+ return verb;
54
+ }
55
+
56
+ /** Build params array matching Workbench sdkProxy.ts conventions */
57
+ export function buildCrudParams(
58
+ module: SDKModule,
59
+ method: string,
60
+ productTag: string,
61
+ args: { tag?: string; body?: unknown },
62
+ ): unknown[] {
63
+ const { tag, body } = args;
64
+
65
+ if (module === 'vector') {
66
+ if (method === 'list') return [{ product: productTag }];
67
+ if (method === 'fetch' || method === 'delete') {
68
+ if (!tag) throw new Error('tag is required');
69
+ return [{ product: productTag, tag }];
70
+ }
71
+ if (method === 'create' || method === 'update' || method === 'connect') {
72
+ const payload =
73
+ body && typeof body === 'object'
74
+ ? { product: productTag, ...(body as object) }
75
+ : { product: productTag };
76
+ return [payload];
77
+ }
78
+ }
79
+
80
+ if (module === 'storage' && method === 'create') {
81
+ const payload =
82
+ body && typeof body === 'object'
83
+ ? { product: productTag, ...(body as object) }
84
+ : { product: productTag };
85
+ return [payload];
86
+ }
87
+
88
+ if (module === 'secrets') {
89
+ if (method === 'list') return [];
90
+ if (method === 'fetch' || method === 'delete') {
91
+ if (!tag) throw new Error('key/tag is required');
92
+ return method === 'delete' ? [tag] : [tag];
93
+ }
94
+ if (method === 'create') return [body ?? {}];
95
+ if (method === 'update') {
96
+ if (!tag) throw new Error('key is required');
97
+ return [tag, body ?? {}];
98
+ }
99
+ }
100
+
101
+ if (module === 'product' || module === 'app') {
102
+ if (method === 'create') return [body ?? {}];
103
+ if (method === 'fetch') {
104
+ if (!tag) throw new Error('tag is required');
105
+ return [tag];
106
+ }
107
+ if (method === 'update') {
108
+ if (!tag) throw new Error('tag is required');
109
+ return [tag, body ?? {}];
110
+ }
111
+ if (method === 'list') return [];
112
+ }
113
+
114
+ // Default product-scoped components
115
+ if (method === 'create') return [productTag, body ?? {}];
116
+ if (method === 'list') return [productTag];
117
+ if (method === 'fetch' || method === 'delete') {
118
+ if (!tag) throw new Error('tag is required');
119
+ return [productTag, tag];
120
+ }
121
+ if (method === 'update') {
122
+ if (!tag) throw new Error('tag is required');
123
+ return [productTag, tag, body ?? {}];
124
+ }
125
+ if (method === 'connect') {
126
+ const payload =
127
+ body && typeof body === 'object'
128
+ ? { product: productTag, ...(body as object) }
129
+ : { product: productTag };
130
+ return [payload];
131
+ }
132
+
133
+ throw new Error(`Unsupported operation ${module}.${method}`);
134
+ }
135
+
136
+ export function listResourceTypes(): string[] {
137
+ return [
138
+ 'storage',
139
+ 'databases',
140
+ 'graph',
141
+ 'vector',
142
+ 'caches',
143
+ 'jobs',
144
+ 'features',
145
+ 'actions',
146
+ 'notifications',
147
+ 'message-brokers',
148
+ 'sessions',
149
+ 'secrets',
150
+ 'quotas',
151
+ 'health',
152
+ 'fallback',
153
+ 'product',
154
+ 'app',
155
+ ];
156
+ }