@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,253 @@
1
+ import { DATABASE_TYPES, GRAPH_TYPES, HTTP_METHODS, JOB_EVENT_TYPES, NOTIFIERS, VECTOR_DB_TYPES, VECTOR_DISTANCE_METRICS, } from './enums.js';
2
+ const TAG = { key: 'tag', label: 'Tag (no spaces)', kind: 'text', required: true };
3
+ const NAME = { key: 'name', label: 'Name', kind: 'text', required: true };
4
+ const DESC = { key: 'description', label: 'Description', kind: 'text' };
5
+ function fields(...items) {
6
+ return items;
7
+ }
8
+ function optional(fields) {
9
+ return fields.map((f) => ({ ...f, required: false }));
10
+ }
11
+ const PRODUCT_CREATE = {
12
+ fields: fields({ key: 'name', label: 'Product name', kind: 'text', required: true }, { key: 'description', label: 'Description', kind: 'text', required: true }, TAG),
13
+ };
14
+ const APP_CREATE = {
15
+ fields: fields({ key: 'app_name', label: 'App name', kind: 'text', required: true }, { key: 'description', label: 'Description', kind: 'text', required: true }, TAG),
16
+ };
17
+ const COMPONENT_BASE = [NAME, DESC, TAG];
18
+ const SCHEMAS = {
19
+ 'products:create': PRODUCT_CREATE,
20
+ 'products:update': { fields: optional(PRODUCT_CREATE.fields) },
21
+ 'apps:create': APP_CREATE,
22
+ 'apps:update': {
23
+ fields: optional([
24
+ { key: 'app_name', label: 'App name', kind: 'text' },
25
+ { key: 'description', label: 'Description', kind: 'text' },
26
+ TAG,
27
+ ]),
28
+ },
29
+ 'storage:create': {
30
+ fields: [
31
+ ...COMPONENT_BASE,
32
+ { key: 'envs', label: 'Environments', kind: 'envs', required: true, envsVariant: 'storage' },
33
+ ],
34
+ },
35
+ 'storage:update': {
36
+ fields: [
37
+ ...optional(COMPONENT_BASE),
38
+ { key: 'envs', label: 'Environments', kind: 'envs', envsVariant: 'storage' },
39
+ ],
40
+ allowExtraJson: true,
41
+ },
42
+ 'databases:create': {
43
+ fields: [
44
+ ...COMPONENT_BASE,
45
+ {
46
+ key: 'type',
47
+ label: 'Database type',
48
+ kind: 'enum',
49
+ required: true,
50
+ choices: DATABASE_TYPES,
51
+ },
52
+ { key: 'envs', label: 'Environments', kind: 'envs', required: true, envsVariant: 'database' },
53
+ ],
54
+ },
55
+ 'databases:update': {
56
+ fields: [
57
+ ...optional(COMPONENT_BASE),
58
+ { key: 'type', label: 'Database type', kind: 'enum', choices: DATABASE_TYPES },
59
+ { key: 'envs', label: 'Environments', kind: 'envs', envsVariant: 'database' },
60
+ ],
61
+ allowExtraJson: true,
62
+ },
63
+ 'databases:connect': { fields: [TAG] },
64
+ 'graph:create': {
65
+ fields: [
66
+ ...COMPONENT_BASE,
67
+ { key: 'type', label: 'Graph type', kind: 'enum', required: true, choices: GRAPH_TYPES },
68
+ { key: 'envs', label: 'Environments', kind: 'envs', required: true, envsVariant: 'graph' },
69
+ ],
70
+ },
71
+ 'graph:update': {
72
+ fields: [
73
+ ...optional(COMPONENT_BASE),
74
+ { key: 'type', label: 'Graph type', kind: 'enum', choices: GRAPH_TYPES },
75
+ { key: 'envs', label: 'Environments', kind: 'envs', envsVariant: 'graph' },
76
+ ],
77
+ allowExtraJson: true,
78
+ },
79
+ 'graph:connect': { fields: [TAG] },
80
+ 'caches:create': {
81
+ fields: [
82
+ ...COMPONENT_BASE,
83
+ { key: 'expiry', label: 'Expiry (seconds)', kind: 'number', required: true },
84
+ ],
85
+ },
86
+ 'caches:update': {
87
+ fields: optional([...COMPONENT_BASE, { key: 'expiry', label: 'Expiry (seconds)', kind: 'number' }]),
88
+ allowExtraJson: true,
89
+ },
90
+ 'jobs:create': {
91
+ fields: [
92
+ TAG,
93
+ NAME,
94
+ { key: 'description', label: 'Description', kind: 'text', required: true },
95
+ { key: 'type', label: 'Job type', kind: 'enum', choices: JOB_EVENT_TYPES },
96
+ { key: 'event', label: 'Event tag', kind: 'text', required: true },
97
+ { key: 'executions', label: 'Max executions', kind: 'number', required: true },
98
+ { key: 'intervals', label: 'Interval (seconds)', kind: 'number', required: true },
99
+ { key: 'app', label: 'App tag (required for action jobs)', kind: 'text' },
100
+ ],
101
+ allowExtraJson: true,
102
+ },
103
+ 'jobs:update': {
104
+ fields: optional([
105
+ TAG,
106
+ NAME,
107
+ { key: 'description', label: 'Description', kind: 'text' },
108
+ { key: 'type', label: 'Job type', kind: 'enum', choices: JOB_EVENT_TYPES },
109
+ { key: 'event', label: 'Event tag', kind: 'text' },
110
+ { key: 'executions', label: 'Max executions', kind: 'number' },
111
+ { key: 'intervals', label: 'Interval (seconds)', kind: 'number' },
112
+ { key: 'app', label: 'App tag', kind: 'text' },
113
+ ]),
114
+ allowExtraJson: true,
115
+ },
116
+ 'vector:create': {
117
+ fields: [
118
+ ...COMPONENT_BASE,
119
+ {
120
+ key: 'type',
121
+ label: 'Vector DB type',
122
+ kind: 'enum',
123
+ required: true,
124
+ choices: VECTOR_DB_TYPES,
125
+ },
126
+ { key: 'dimensions', label: 'Dimensions', kind: 'number', required: true },
127
+ {
128
+ key: 'metric',
129
+ label: 'Distance metric',
130
+ kind: 'enum',
131
+ choices: VECTOR_DISTANCE_METRICS,
132
+ default: 'cosine',
133
+ },
134
+ { key: 'envs', label: 'Environments', kind: 'envs', required: true, envsVariant: 'vector' },
135
+ ],
136
+ },
137
+ 'vector:update': {
138
+ fields: [
139
+ ...optional(COMPONENT_BASE),
140
+ { key: 'type', label: 'Vector DB type', kind: 'enum', choices: VECTOR_DB_TYPES },
141
+ { key: 'dimensions', label: 'Dimensions', kind: 'number' },
142
+ { key: 'metric', label: 'Distance metric', kind: 'enum', choices: VECTOR_DISTANCE_METRICS },
143
+ { key: 'envs', label: 'Environments', kind: 'envs', envsVariant: 'vector' },
144
+ ],
145
+ allowExtraJson: true,
146
+ },
147
+ 'vector:connect': { fields: [TAG] },
148
+ 'messageBrokers:create': {
149
+ fields: [
150
+ ...COMPONENT_BASE,
151
+ { key: 'envs', label: 'Environments', kind: 'envs', required: true, envsVariant: 'broker' },
152
+ ],
153
+ },
154
+ 'messageBrokers:update': {
155
+ fields: [
156
+ ...optional(COMPONENT_BASE),
157
+ { key: 'envs', label: 'Environments', kind: 'envs', envsVariant: 'broker' },
158
+ ],
159
+ allowExtraJson: true,
160
+ },
161
+ 'notifications:create': {
162
+ fields: [
163
+ ...COMPONENT_BASE,
164
+ { key: 'notifier', label: 'Notifier', kind: 'enum', required: true, choices: NOTIFIERS },
165
+ ],
166
+ allowExtraJson: true,
167
+ },
168
+ 'notifications:update': {
169
+ fields: optional([
170
+ ...COMPONENT_BASE,
171
+ { key: 'notifier', label: 'Notifier', kind: 'enum', choices: NOTIFIERS },
172
+ ]),
173
+ allowExtraJson: true,
174
+ },
175
+ 'actions:create': {
176
+ fields: [
177
+ TAG,
178
+ NAME,
179
+ DESC,
180
+ { key: 'method', label: 'HTTP method', kind: 'enum', choices: HTTP_METHODS },
181
+ ],
182
+ allowExtraJson: true,
183
+ },
184
+ 'actions:update': {
185
+ fields: optional([
186
+ TAG,
187
+ NAME,
188
+ DESC,
189
+ { key: 'method', label: 'HTTP method', kind: 'enum', choices: HTTP_METHODS },
190
+ ]),
191
+ allowExtraJson: true,
192
+ },
193
+ 'features:create': {
194
+ fields: [...COMPONENT_BASE],
195
+ allowExtraJson: true,
196
+ },
197
+ 'features:update': { fields: optional(COMPONENT_BASE), allowExtraJson: true },
198
+ 'secrets:create': {
199
+ fields: [
200
+ { key: 'key', label: 'Secret key', kind: 'text', required: true },
201
+ { key: 'value', label: 'Secret value', kind: 'text', required: true },
202
+ DESC,
203
+ ],
204
+ },
205
+ 'secrets:update': {
206
+ fields: optional([
207
+ { key: 'value', label: 'Secret value', kind: 'text' },
208
+ DESC,
209
+ ]),
210
+ },
211
+ 'quotas:create': { fields: [...COMPONENT_BASE], allowExtraJson: true },
212
+ 'quotas:update': { fields: optional(COMPONENT_BASE), allowExtraJson: true },
213
+ 'health:create': { fields: [...COMPONENT_BASE], allowExtraJson: true },
214
+ 'health:update': { fields: optional(COMPONENT_BASE), allowExtraJson: true },
215
+ 'fallback:create': { fields: [...COMPONENT_BASE], allowExtraJson: true },
216
+ 'fallback:update': { fields: optional(COMPONENT_BASE), allowExtraJson: true },
217
+ 'sessions:create': { fields: [...COMPONENT_BASE], allowExtraJson: true },
218
+ 'sessions:update': { fields: optional(COMPONENT_BASE), allowExtraJson: true },
219
+ };
220
+ const GENERIC_COMPONENT = {
221
+ fields: [...COMPONENT_BASE],
222
+ allowExtraJson: true,
223
+ };
224
+ const CONNECT_MODULES = new Set(['databases', 'graph', 'vector', 'storage']);
225
+ export function getFormSchema(schemaKey) {
226
+ return SCHEMAS[schemaKey] ?? undefined;
227
+ }
228
+ export function schemaKeyForResource(module, verb) {
229
+ if (verb === 'connect' && CONNECT_MODULES.has(module)) {
230
+ return `${module}:connect`;
231
+ }
232
+ if (verb === 'create' || verb === 'update') {
233
+ return `${module}:${verb}`;
234
+ }
235
+ return undefined;
236
+ }
237
+ export function schemaKeyForEntity(entity, verb) {
238
+ if (verb === 'create' || verb === 'update')
239
+ return `${entity}:${verb}`;
240
+ return undefined;
241
+ }
242
+ /** Fallback schema for component types without a dedicated form */
243
+ export function genericComponentSchema(verb) {
244
+ if (verb === 'create')
245
+ return GENERIC_COMPONENT;
246
+ return { fields: optional(COMPONENT_BASE), allowExtraJson: true };
247
+ }
248
+ export function resolveResourceSchema(module, verb) {
249
+ const key = schemaKeyForResource(module, verb);
250
+ if (!key)
251
+ return undefined;
252
+ return getFormSchema(key) ?? (verb === 'create' || verb === 'update' ? genericComponentSchema(verb) : undefined);
253
+ }
@@ -0,0 +1,18 @@
1
+ export type FieldKind = 'text' | 'number' | 'enum' | 'envs';
2
+ export type EnvsVariant = 'database' | 'graph' | 'storage' | 'broker' | 'vector' | 'simple';
3
+ export interface FormField {
4
+ key: string;
5
+ label: string;
6
+ kind: FieldKind;
7
+ required?: boolean;
8
+ default?: string | number;
9
+ /** enum */
10
+ choices?: readonly string[];
11
+ /** envs */
12
+ envsVariant?: EnvsVariant;
13
+ }
14
+ export interface FormSchema {
15
+ fields: FormField[];
16
+ /** Merge optional JSON object after prompts */
17
+ allowExtraJson?: boolean;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ /** Parse fetch body as JSON; surface HTML/misconfigured api_url clearly. */
2
+ export declare function parseJsonResponse<T = Record<string, unknown>>(res: Response, url: string): Promise<T>;
3
+ export interface ProxyJsonResponse<T> {
4
+ status?: boolean;
5
+ data?: {
6
+ data: T;
7
+ };
8
+ message?: string;
9
+ }
10
+ export declare function postProxyJson<T>(url: string, body: Record<string, unknown>, headers: Record<string, string>): Promise<T>;
@@ -0,0 +1,51 @@
1
+ import { gzipSync } from 'node:zlib';
2
+ const GZIP_THRESHOLD = 200 * 1024;
3
+ /** Parse fetch body as JSON; surface HTML/misconfigured api_url clearly. */
4
+ export async function parseJsonResponse(res, url) {
5
+ const text = await res.text();
6
+ const contentType = res.headers.get('content-type') ?? '';
7
+ if (!text.trim()) {
8
+ if (!res.ok)
9
+ throw new Error(`Empty response from ${url} (HTTP ${res.status})`);
10
+ return {};
11
+ }
12
+ try {
13
+ return JSON.parse(text);
14
+ }
15
+ catch {
16
+ const htmlHint = text.trimStart().startsWith('<')
17
+ ? [
18
+ '',
19
+ 'The server returned HTML instead of JSON. Check your API profile:',
20
+ ' ductape profiles list',
21
+ ' ductape profiles use local → http://localhost:4311/api (run `ductape start` first)',
22
+ ' ductape profiles use cloud → https://api.ductape.app/api',
23
+ 'Then: ductape login --profile local',
24
+ ].join('\n')
25
+ : '';
26
+ throw new Error(`Invalid JSON from ${url} (HTTP ${res.status}, Content-Type: ${contentType}): ${text.slice(0, 200)}${htmlHint}`);
27
+ }
28
+ }
29
+ export async function postProxyJson(url, body, headers) {
30
+ const json = JSON.stringify(body);
31
+ const reqHeaders = { ...headers, 'Content-Type': 'application/json' };
32
+ let bodyInit = json;
33
+ if (json.length > GZIP_THRESHOLD) {
34
+ const gz = gzipSync(json);
35
+ bodyInit = new Uint8Array(gz);
36
+ reqHeaders['Content-Encoding'] = 'gzip';
37
+ }
38
+ const res = await fetch(url, {
39
+ method: 'POST',
40
+ headers: reqHeaders,
41
+ body: bodyInit,
42
+ });
43
+ const parsed = await parseJsonResponse(res, url);
44
+ if (!res.ok) {
45
+ throw new Error(parsed.message ?? `Request failed: ${res.status}`);
46
+ }
47
+ if (typeof parsed.status === 'boolean' && !parsed.status) {
48
+ throw new Error(parsed.message ?? 'Proxy operation failed');
49
+ }
50
+ return parsed.data?.data;
51
+ }
@@ -0,0 +1,20 @@
1
+ import type { Credentials } from './config.js';
2
+ export interface GeneratePayloadRequest {
3
+ workspace_id: string;
4
+ user_id: string;
5
+ public_key: string;
6
+ product_tag: string;
7
+ env_slug: string;
8
+ operation_family: string;
9
+ method: string;
10
+ targets?: Record<string, unknown>;
11
+ include_session?: boolean;
12
+ include_cache?: boolean;
13
+ schema_mode?: 'strict' | 'best_effort';
14
+ input_hint?: Record<string, unknown>;
15
+ }
16
+ export interface GeneratePayloadResponse {
17
+ payload: Record<string, unknown>;
18
+ meta: Record<string, unknown>;
19
+ }
20
+ export declare function generateExecutablePayload(apiUrl: string, creds: Credentials, request: GeneratePayloadRequest): Promise<GeneratePayloadResponse>;
@@ -0,0 +1,20 @@
1
+ export async function generateExecutablePayload(apiUrl, creds, request) {
2
+ const url = `${apiUrl}/integrations/v1/payloads/generate`;
3
+ const res = await fetch(url, {
4
+ method: 'POST',
5
+ headers: {
6
+ 'Content-Type': 'application/json',
7
+ Authorization: `Bearer ${creds.auth_token}`,
8
+ 'x-access-key': creds.public_key,
9
+ },
10
+ body: JSON.stringify(request),
11
+ });
12
+ const body = await res.json();
13
+ if (!res.ok) {
14
+ throw new Error(body.message ?? `Payload generation failed (${res.status})`);
15
+ }
16
+ if (body.status === false) {
17
+ throw new Error(body.message ?? 'Payload generation failed');
18
+ }
19
+ return (body.data ?? body);
20
+ }
@@ -0,0 +1,6 @@
1
+ import type { InteractiveOpts } from './forms/index.js';
2
+ export type CommandInteractiveFlags = {
3
+ interactive?: boolean;
4
+ noInteractive?: boolean;
5
+ };
6
+ export declare function toInteractiveOpts(flags: CommandInteractiveFlags): InteractiveOpts;
@@ -0,0 +1,6 @@
1
+ export function toInteractiveOpts(flags) {
2
+ return {
3
+ interactive: flags.noInteractive ? false : flags.interactive,
4
+ noInteractive: flags.noInteractive,
5
+ };
6
+ }
@@ -0,0 +1,3 @@
1
+ export declare function printJson(data: unknown, jsonMode: boolean): void;
2
+ export declare function success(message: string): void;
3
+ export declare function fail(message: string): never;
@@ -0,0 +1,22 @@
1
+ export function printJson(data, jsonMode) {
2
+ if (jsonMode) {
3
+ console.log(JSON.stringify(data, null, 2));
4
+ return;
5
+ }
6
+ if (data === undefined || data === null) {
7
+ console.log('(no data)');
8
+ return;
9
+ }
10
+ if (typeof data === 'object') {
11
+ console.log(JSON.stringify(data, null, 2));
12
+ return;
13
+ }
14
+ console.log(String(data));
15
+ }
16
+ export function success(message) {
17
+ console.log(`✓ ${message}`);
18
+ }
19
+ export function fail(message) {
20
+ console.error(`✗ ${message}`);
21
+ process.exit(1);
22
+ }
@@ -0,0 +1,19 @@
1
+ import type { WorkspaceContext } from './workspace-context.js';
2
+ export declare function listProducts(ctx: WorkspaceContext, status?: string): Promise<unknown[]>;
3
+ export declare function getProduct(ctx: WorkspaceContext, opts: {
4
+ id?: string;
5
+ tag?: string;
6
+ }): Promise<unknown>;
7
+ export declare function createProduct(ctx: WorkspaceContext, body: Record<string, unknown>): Promise<unknown>;
8
+ export declare function updateProduct(ctx: WorkspaceContext, tag: string, body: Record<string, unknown>): Promise<unknown>;
9
+ export declare function deleteProduct(ctx: WorkspaceContext, productId: string): Promise<unknown>;
10
+ export declare function listProductApps(ctx: WorkspaceContext, productId: string): Promise<unknown[]>;
11
+ export declare function listApps(ctx: WorkspaceContext, status?: string): Promise<unknown[]>;
12
+ export declare function getApp(ctx: WorkspaceContext, opts: {
13
+ id?: string;
14
+ tag?: string;
15
+ }): Promise<unknown>;
16
+ export declare function createApp(ctx: WorkspaceContext, body: Record<string, unknown>): Promise<unknown>;
17
+ export declare function updateApp(ctx: WorkspaceContext, appId: string, body: Record<string, unknown>): Promise<unknown>;
18
+ export declare function updateAppViaProxy(ctx: WorkspaceContext, tag: string, body: Record<string, unknown>): Promise<unknown>;
19
+ export declare function deleteApp(ctx: WorkspaceContext, appId: string): Promise<unknown>;
@@ -0,0 +1,123 @@
1
+ import { ApiClient } from './api-client.js';
2
+ import { createSdkProxyClient } from './proxy/sdk-proxy.js';
3
+ import { workspaceAuthQuery } from './workspace-context.js';
4
+ function client(ctx) {
5
+ return ApiClient.forProfile(ctx.profile, ctx.credentials);
6
+ }
7
+ function proxy(ctx) {
8
+ return createSdkProxyClient({
9
+ apiUrl: ctx.apiUrl,
10
+ workspaceId: ctx.workspaceId,
11
+ userId: ctx.credentials.user_id,
12
+ publicKey: ctx.credentials.public_key,
13
+ token: ctx.credentials.auth_token,
14
+ });
15
+ }
16
+ function unwrapList(result) {
17
+ if (Array.isArray(result))
18
+ return result;
19
+ if (result && typeof result === 'object' && Array.isArray(result.data)) {
20
+ return result.data;
21
+ }
22
+ return [];
23
+ }
24
+ function unwrapOne(result) {
25
+ if (result && typeof result === 'object' && 'data' in result) {
26
+ return result.data;
27
+ }
28
+ return result;
29
+ }
30
+ // —— Products (integrations REST + sdk-proxy for update) ——
31
+ export async function listProducts(ctx, status = 'all') {
32
+ const result = await client(ctx).getPath(`/integrations/v1/workspace/${ctx.workspaceId}/${status}`, workspaceAuthQuery(ctx));
33
+ return unwrapList(result);
34
+ }
35
+ export async function getProduct(ctx, opts) {
36
+ const q = workspaceAuthQuery(ctx);
37
+ if (opts.id) {
38
+ const result = await client(ctx).getPath(`/integrations/v1/${opts.id}`, q);
39
+ return unwrapOne(result);
40
+ }
41
+ if (opts.tag) {
42
+ const result = await client(ctx).getPath(`/integrations/v1/fetch/tag`, {
43
+ ...q,
44
+ tag: opts.tag,
45
+ });
46
+ return unwrapOne(result);
47
+ }
48
+ throw new Error('Provide --id <product_id> or --tag <product_tag>');
49
+ }
50
+ export async function createProduct(ctx, body) {
51
+ const payload = {
52
+ workspace_id: ctx.workspaceId,
53
+ user_id: ctx.credentials.user_id,
54
+ public_key: ctx.credentials.public_key,
55
+ ...body,
56
+ };
57
+ const result = await client(ctx).post('/integrations/v1/create', payload);
58
+ return unwrapOne(result);
59
+ }
60
+ export async function updateProduct(ctx, tag, body) {
61
+ return proxy(ctx).execute('product', 'update', [tag, body]);
62
+ }
63
+ export async function deleteProduct(ctx, productId) {
64
+ return client(ctx).delete(`/integrations/v1/${productId}?${new URLSearchParams(workspaceAuthQuery(ctx)).toString()}`);
65
+ }
66
+ export async function listProductApps(ctx, productId) {
67
+ const result = await client(ctx).getPath(`/integrations/v1/fetch-product-apps/${productId}`, workspaceAuthQuery(ctx));
68
+ return unwrapList(result);
69
+ }
70
+ // —— Apps (apps REST) ——
71
+ export async function listApps(ctx, status = 'all') {
72
+ const result = await client(ctx).getPath(`/apps/v1/workspace/${ctx.workspaceId}/${status}`, {
73
+ user_id: ctx.credentials.user_id,
74
+ public_key: ctx.credentials.public_key,
75
+ });
76
+ return unwrapList(result);
77
+ }
78
+ export async function getApp(ctx, opts) {
79
+ const q = {
80
+ user_id: ctx.credentials.user_id,
81
+ public_key: ctx.credentials.public_key,
82
+ };
83
+ if (opts.id) {
84
+ const result = await client(ctx).getPath(`/apps/v1/${opts.id}`, q);
85
+ return unwrapOne(result);
86
+ }
87
+ if (opts.tag) {
88
+ const result = await client(ctx).getPath(`/apps/v1/fetch/tag`, { ...q, tag: opts.tag });
89
+ return unwrapOne(result);
90
+ }
91
+ throw new Error('Provide --id <app_id> or --tag <app_tag>');
92
+ }
93
+ export async function createApp(ctx, body) {
94
+ const payload = {
95
+ workspace_id: ctx.workspaceId,
96
+ user_id: ctx.credentials.user_id,
97
+ public_key: ctx.credentials.public_key,
98
+ version: '1.0.0',
99
+ logo: '',
100
+ envs: [],
101
+ ...body,
102
+ };
103
+ const result = await client(ctx).post('/apps/v1/create', payload);
104
+ return unwrapOne(result);
105
+ }
106
+ export async function updateApp(ctx, appId, body) {
107
+ const q = {
108
+ user_id: ctx.credentials.user_id,
109
+ public_key: ctx.credentials.public_key,
110
+ };
111
+ const result = await client(ctx).put(`/apps/v1/${appId}?${new URLSearchParams(q).toString()}`, body);
112
+ return unwrapOne(result);
113
+ }
114
+ export async function updateAppViaProxy(ctx, tag, body) {
115
+ return proxy(ctx).execute('app', 'update', [tag, body]);
116
+ }
117
+ export async function deleteApp(ctx, appId) {
118
+ const q = {
119
+ user_id: ctx.credentials.user_id,
120
+ public_key: ctx.credentials.public_key,
121
+ };
122
+ return client(ctx).delete(`/apps/v1/${appId}?${new URLSearchParams(q).toString()}`);
123
+ }
@@ -0,0 +1,16 @@
1
+ import { type Credentials, type ResolvedProjectConfig } from '../config.js';
2
+ import { type SdkProxyClient } from './sdk-proxy.js';
3
+ import { DbProxyClient } from './db-proxy.js';
4
+ import { GraphProxyClient } from './graph-proxy.js';
5
+ export interface SessionContext {
6
+ credentials: Credentials;
7
+ project: ResolvedProjectConfig;
8
+ apiUrl: string;
9
+ }
10
+ export declare function requireCredentials(): Credentials;
11
+ export declare function requireSession(): SessionContext;
12
+ export declare function getSdkProxy(session?: SessionContext): SdkProxyClient;
13
+ export declare function getDbProxy(session?: SessionContext): DbProxyClient;
14
+ export declare function getDbContext(session?: SessionContext): import("../context-store.js").DatabaseContext;
15
+ export declare function getGraphProxy(session?: SessionContext): GraphProxyClient;
16
+ export declare function getOptionalProject(): ResolvedProjectConfig | null;
@@ -0,0 +1,73 @@
1
+ import { findProjectConfig, getActiveProfileName, getApiUrl, loadCredentials, loadProjectConfig, } from '../config.js';
2
+ import { loadRuntimeContext } from '../context-store.js';
3
+ import { createSdkProxyClient } from './sdk-proxy.js';
4
+ import { DbProxyClient } from './db-proxy.js';
5
+ import { GraphProxyClient } from './graph-proxy.js';
6
+ export function requireCredentials() {
7
+ const creds = loadCredentials();
8
+ if (!creds?.auth_token || !creds.public_key || !creds.user_id) {
9
+ throw new Error('Not logged in. Run: ductape login');
10
+ }
11
+ return creds;
12
+ }
13
+ export function requireSession() {
14
+ const credentials = requireCredentials();
15
+ const project = loadProjectConfig();
16
+ const profile = getActiveProfileName(project);
17
+ const apiUrl = getApiUrl(profile);
18
+ return { credentials, project, apiUrl };
19
+ }
20
+ export function getSdkProxy(session) {
21
+ const s = session ?? requireSession();
22
+ return createSdkProxyClient({
23
+ apiUrl: s.apiUrl,
24
+ workspaceId: s.project.workspace_id,
25
+ userId: s.credentials.user_id,
26
+ publicKey: s.credentials.public_key,
27
+ token: s.credentials.auth_token,
28
+ });
29
+ }
30
+ export function getDbProxy(session) {
31
+ const s = session ?? requireSession();
32
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
33
+ const proxy = new DbProxyClient({
34
+ apiUrl: s.apiUrl,
35
+ workspaceId: s.project.workspace_id,
36
+ userId: s.credentials.user_id,
37
+ publicKey: s.credentials.public_key,
38
+ token: s.credentials.auth_token,
39
+ });
40
+ return proxy;
41
+ }
42
+ export function getDbContext(session) {
43
+ const s = session ?? requireSession();
44
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
45
+ return (runtime?.database_context ?? {
46
+ database: '',
47
+ env: s.project.env_slug,
48
+ product: s.project.product_tag,
49
+ });
50
+ }
51
+ export function getGraphProxy(session) {
52
+ const s = session ?? requireSession();
53
+ const runtime = loadRuntimeContext(s.project.workspace_id, s.project.product_tag);
54
+ return new GraphProxyClient({
55
+ apiUrl: s.apiUrl,
56
+ workspaceId: s.project.workspace_id,
57
+ userId: s.credentials.user_id,
58
+ publicKey: s.credentials.public_key,
59
+ token: s.credentials.auth_token,
60
+ graphContext: runtime?.graph_context ?? null,
61
+ });
62
+ }
63
+ export function getOptionalProject() {
64
+ const found = findProjectConfig();
65
+ if (!found)
66
+ return null;
67
+ try {
68
+ return loadProjectConfig();
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
@@ -0,0 +1,13 @@
1
+ import type { DatabaseContext } from '../context-store.js';
2
+ export interface DbProxyContext {
3
+ apiUrl: string;
4
+ workspaceId: string;
5
+ userId: string;
6
+ publicKey: string;
7
+ token: string;
8
+ }
9
+ export declare class DbProxyClient {
10
+ private readonly ctx;
11
+ constructor(ctx: DbProxyContext);
12
+ execute<T>(method: string, params: unknown[], databaseContext?: DatabaseContext | null): Promise<T>;
13
+ }