@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,28 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+ export class DbProxyClient {
4
+ ctx;
5
+ constructor(ctx) {
6
+ this.ctx = ctx;
7
+ }
8
+ async execute(method, params, databaseContext) {
9
+ const sensitive = {
10
+ method,
11
+ params,
12
+ user_id: this.ctx.userId,
13
+ };
14
+ if (databaseContext)
15
+ sensitive.database_context = databaseContext;
16
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
17
+ const body = {
18
+ encrypted_payload,
19
+ workspace_id: this.ctx.workspaceId,
20
+ user_id: this.ctx.userId,
21
+ public_key: this.ctx.publicKey,
22
+ };
23
+ if (databaseContext)
24
+ body.database_context = databaseContext;
25
+ const url = `${this.ctx.apiUrl}/proxy/v1/db-proxy/execute`;
26
+ return postProxyJson(url, body, { 'x-access-token': this.ctx.token });
27
+ }
28
+ }
@@ -0,0 +1,19 @@
1
+ export interface GraphContext {
2
+ graph: string;
3
+ env: string;
4
+ product: string;
5
+ }
6
+ export interface GraphProxyContext {
7
+ apiUrl: string;
8
+ workspaceId: string;
9
+ userId: string;
10
+ publicKey: string;
11
+ token: string;
12
+ graphContext?: GraphContext | null;
13
+ }
14
+ export declare class GraphProxyClient {
15
+ private readonly ctx;
16
+ constructor(ctx: GraphProxyContext);
17
+ setGraphContext(context: GraphContext | null): void;
18
+ execute<T>(method: string, params?: unknown[], graphContext?: GraphContext | null): Promise<T>;
19
+ }
@@ -0,0 +1,43 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+ export class GraphProxyClient {
4
+ ctx;
5
+ constructor(ctx) {
6
+ this.ctx = ctx;
7
+ }
8
+ setGraphContext(context) {
9
+ this.ctx.graphContext = context;
10
+ }
11
+ async execute(method, params = [], graphContext) {
12
+ const ctx = graphContext ?? this.ctx.graphContext;
13
+ const sensitive = {
14
+ method,
15
+ params,
16
+ user_id: this.ctx.userId,
17
+ };
18
+ if (ctx)
19
+ sensitive.graph_context = ctx;
20
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
21
+ const body = {
22
+ encrypted_payload,
23
+ workspace_id: this.ctx.workspaceId,
24
+ user_id: this.ctx.userId,
25
+ public_key: this.ctx.publicKey,
26
+ };
27
+ if (ctx)
28
+ body.graph_context = ctx;
29
+ const url = `${this.ctx.apiUrl}/proxy/v1/graph-proxy/execute`;
30
+ const result = await postProxyJson(url, body, {
31
+ 'x-access-token': this.ctx.token,
32
+ });
33
+ if (method === 'connect' && params[0] && typeof params[0] === 'object') {
34
+ const cfg = params[0];
35
+ this.setGraphContext({
36
+ graph: cfg.graph,
37
+ env: cfg.env,
38
+ product: cfg.product,
39
+ });
40
+ }
41
+ return result;
42
+ }
43
+ }
@@ -0,0 +1,14 @@
1
+ export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' | 'notifications' | 'messageBrokers' | 'storage' | 'vector' | 'caches' | 'sessions' | 'quotas' | 'actions' | 'features' | 'jobs' | 'logs' | 'resilience' | 'health' | 'fallback' | 'secrets' | 'cloud';
2
+ export interface SdkProxyContext {
3
+ apiUrl: string;
4
+ workspaceId: string;
5
+ userId: string;
6
+ publicKey: string;
7
+ token: string;
8
+ }
9
+ export declare class SdkProxyClient {
10
+ private readonly ctx;
11
+ constructor(ctx: SdkProxyContext);
12
+ execute<T>(module: SDKModule, method: string, params?: unknown[]): Promise<T>;
13
+ }
14
+ export declare function createSdkProxyClient(ctx: SdkProxyContext): SdkProxyClient;
@@ -0,0 +1,22 @@
1
+ import { encryptProxyPayload } from '../encryption.js';
2
+ import { postProxyJson } from '../http.js';
3
+ export class SdkProxyClient {
4
+ ctx;
5
+ constructor(ctx) {
6
+ this.ctx = ctx;
7
+ }
8
+ async execute(module, method, params = []) {
9
+ const sensitive = { module, method, params, user_id: this.ctx.userId };
10
+ const encrypted_payload = encryptProxyPayload(sensitive, this.ctx.publicKey);
11
+ const url = `${this.ctx.apiUrl}/proxy/v1/sdk-proxy/execute`;
12
+ return postProxyJson(url, {
13
+ encrypted_payload,
14
+ workspace_id: this.ctx.workspaceId,
15
+ user_id: this.ctx.userId,
16
+ public_key: this.ctx.publicKey,
17
+ }, { 'x-access-token': this.ctx.token });
18
+ }
19
+ }
20
+ export function createSdkProxyClient(ctx) {
21
+ return new SdkProxyClient(ctx);
22
+ }
@@ -0,0 +1,11 @@
1
+ import { type InteractiveOpts } from './forms/index.js';
2
+ import type { FormSchema } from './forms/types.js';
3
+ export declare function readJsonBody(filePath?: string): unknown;
4
+ export declare function resolveBody(opts: InteractiveOpts & {
5
+ file?: string;
6
+ schemaKey?: string;
7
+ schema?: FormSchema;
8
+ patch?: boolean;
9
+ requireBody?: boolean;
10
+ }): Promise<Record<string, unknown> | undefined>;
11
+ export declare function bodyRequiredHint(schemaKey?: string): string;
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs';
2
+ import { getFormSchema, isInteractive, promptForm, } from './forms/index.js';
3
+ export function readJsonBody(filePath) {
4
+ if (!filePath)
5
+ return undefined;
6
+ const raw = fs.readFileSync(filePath, 'utf8');
7
+ return JSON.parse(raw);
8
+ }
9
+ function asRecord(value) {
10
+ if (value === undefined)
11
+ return undefined;
12
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
13
+ return value;
14
+ }
15
+ throw new Error('Body must be a JSON object');
16
+ }
17
+ export async function resolveBody(opts) {
18
+ if (opts.file) {
19
+ return asRecord(readJsonBody(opts.file));
20
+ }
21
+ const schema = opts.schema ?? (opts.schemaKey ? getFormSchema(opts.schemaKey) : undefined);
22
+ if (schema && isInteractive(opts)) {
23
+ return promptForm(schema, { patch: opts.patch ?? opts.schemaKey?.endsWith(':update') });
24
+ }
25
+ if (opts.requireBody && isInteractive(opts) === false) {
26
+ return undefined;
27
+ }
28
+ if (opts.requireBody) {
29
+ return undefined;
30
+ }
31
+ return undefined;
32
+ }
33
+ export function bodyRequiredHint(schemaKey) {
34
+ const json = 'Pass -f <file.json> or run interactively (TTY)';
35
+ if (schemaKey)
36
+ return `${json} — schema: ${schemaKey}`;
37
+ return json;
38
+ }
@@ -0,0 +1,12 @@
1
+ import type { SDKModule } from './proxy/sdk-proxy.js';
2
+ /** CLI resource type → sdk-proxy module */
3
+ export declare const RESOURCE_MODULES: Record<string, SDKModule>;
4
+ export type CrudVerb = 'create' | 'list' | 'get' | 'update' | 'delete' | 'connect';
5
+ export declare function resolveResourceType(name: string): SDKModule;
6
+ export declare function proxyMethodForVerb(verb: CrudVerb): string;
7
+ /** Build params array matching Workbench sdkProxy.ts conventions */
8
+ export declare function buildCrudParams(module: SDKModule, method: string, productTag: string, args: {
9
+ tag?: string;
10
+ body?: unknown;
11
+ }): unknown[];
12
+ export declare function listResourceTypes(): string[];
@@ -0,0 +1,147 @@
1
+ /** CLI resource type → sdk-proxy module */
2
+ export const RESOURCE_MODULES = {
3
+ storage: 'storage',
4
+ databases: 'databases',
5
+ database: 'databases',
6
+ graph: 'graph',
7
+ graphs: 'graph',
8
+ vector: 'vector',
9
+ vectors: 'vector',
10
+ cache: 'caches',
11
+ caches: 'caches',
12
+ job: 'jobs',
13
+ jobs: 'jobs',
14
+ feature: 'features',
15
+ features: 'features',
16
+ action: 'actions',
17
+ actions: 'actions',
18
+ notification: 'notifications',
19
+ notifications: 'notifications',
20
+ broker: 'messageBrokers',
21
+ brokers: 'messageBrokers',
22
+ 'message-brokers': 'messageBrokers',
23
+ messagebrokers: 'messageBrokers',
24
+ session: 'sessions',
25
+ sessions: 'sessions',
26
+ secret: 'secrets',
27
+ secrets: 'secrets',
28
+ quota: 'quotas',
29
+ quotas: 'quotas',
30
+ health: 'health',
31
+ healthcheck: 'health',
32
+ fallback: 'fallback',
33
+ product: 'product',
34
+ app: 'app',
35
+ };
36
+ export function resolveResourceType(name) {
37
+ const mod = RESOURCE_MODULES[name.toLowerCase()];
38
+ if (!mod) {
39
+ throw new Error(`Unknown resource type "${name}". Try: storage, databases, graph, product, app, … — or \`ductape products\` / \`ductape apps\` for workspace CRUD`);
40
+ }
41
+ return mod;
42
+ }
43
+ export function proxyMethodForVerb(verb) {
44
+ if (verb === 'get')
45
+ return 'fetch';
46
+ return verb;
47
+ }
48
+ /** Build params array matching Workbench sdkProxy.ts conventions */
49
+ export function buildCrudParams(module, method, productTag, args) {
50
+ const { tag, body } = args;
51
+ if (module === 'vector') {
52
+ if (method === 'list')
53
+ return [{ product: productTag }];
54
+ if (method === 'fetch' || method === 'delete') {
55
+ if (!tag)
56
+ throw new Error('tag is required');
57
+ return [{ product: productTag, tag }];
58
+ }
59
+ if (method === 'create' || method === 'update' || method === 'connect') {
60
+ const payload = body && typeof body === 'object'
61
+ ? { product: productTag, ...body }
62
+ : { product: productTag };
63
+ return [payload];
64
+ }
65
+ }
66
+ if (module === 'storage' && method === 'create') {
67
+ const payload = body && typeof body === 'object'
68
+ ? { product: productTag, ...body }
69
+ : { product: productTag };
70
+ return [payload];
71
+ }
72
+ if (module === 'secrets') {
73
+ if (method === 'list')
74
+ return [];
75
+ if (method === 'fetch' || method === 'delete') {
76
+ if (!tag)
77
+ throw new Error('key/tag is required');
78
+ return method === 'delete' ? [tag] : [tag];
79
+ }
80
+ if (method === 'create')
81
+ return [body ?? {}];
82
+ if (method === 'update') {
83
+ if (!tag)
84
+ throw new Error('key is required');
85
+ return [tag, body ?? {}];
86
+ }
87
+ }
88
+ if (module === 'product' || module === 'app') {
89
+ if (method === 'create')
90
+ return [body ?? {}];
91
+ if (method === 'fetch') {
92
+ if (!tag)
93
+ throw new Error('tag is required');
94
+ return [tag];
95
+ }
96
+ if (method === 'update') {
97
+ if (!tag)
98
+ throw new Error('tag is required');
99
+ return [tag, body ?? {}];
100
+ }
101
+ if (method === 'list')
102
+ return [];
103
+ }
104
+ // Default product-scoped components
105
+ if (method === 'create')
106
+ return [productTag, body ?? {}];
107
+ if (method === 'list')
108
+ return [productTag];
109
+ if (method === 'fetch' || method === 'delete') {
110
+ if (!tag)
111
+ throw new Error('tag is required');
112
+ return [productTag, tag];
113
+ }
114
+ if (method === 'update') {
115
+ if (!tag)
116
+ throw new Error('tag is required');
117
+ return [productTag, tag, body ?? {}];
118
+ }
119
+ if (method === 'connect') {
120
+ const payload = body && typeof body === 'object'
121
+ ? { product: productTag, ...body }
122
+ : { product: productTag };
123
+ return [payload];
124
+ }
125
+ throw new Error(`Unsupported operation ${module}.${method}`);
126
+ }
127
+ export function listResourceTypes() {
128
+ return [
129
+ 'storage',
130
+ 'databases',
131
+ 'graph',
132
+ 'vector',
133
+ 'caches',
134
+ 'jobs',
135
+ 'features',
136
+ 'actions',
137
+ 'notifications',
138
+ 'message-brokers',
139
+ 'sessions',
140
+ 'secrets',
141
+ 'quotas',
142
+ 'health',
143
+ 'fallback',
144
+ 'product',
145
+ 'app',
146
+ ];
147
+ }
@@ -0,0 +1,6 @@
1
+ export declare function ensureSupportedSnippetOperation(operationFamily: string, method: string): void;
2
+ export declare function buildTypeScriptSnippet(payload: Record<string, unknown>, operationFamily: string, method: string): string;
3
+ export declare function buildPythonSnippet(payload: Record<string, unknown>, operationFamily: string, method: string): string;
4
+ export declare function buildGoSnippet(payload: Record<string, unknown>, operationFamily: string, method: string): string;
5
+ export declare function buildDotnetSnippet(payload: Record<string, unknown>, operationFamily: string, method: string): string;
6
+ export declare function buildSnippet(language: 'typescript' | 'python' | 'go' | 'dotnet', payload: Record<string, unknown>, operationFamily: string, method: string): string;
@@ -0,0 +1,146 @@
1
+ const ALLOWED_SNIPPET_METHODS = {
2
+ action: ['run', 'dispatch', 'execute'],
3
+ workflow: ['run', 'dispatch', 'execute'],
4
+ database: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'upsert', 'count', 'aggregate'],
5
+ graph: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'execute'],
6
+ vector: ['query', 'find', 'findSimilar', 'upsert', 'insert', 'delete', 'dispatch'],
7
+ storage: ['dispatch', 'upload', 'download', 'remove', 'listFiles', 'getSignedUrl', 'stats'],
8
+ notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send'],
9
+ messaging: ['dispatch', 'send', 'publish', 'produce', 'consume'],
10
+ broker: ['dispatch', 'send', 'publish', 'produce', 'consume'],
11
+ quota: ['run', 'dispatch', 'check', 'consume'],
12
+ fallback: ['run', 'dispatch'],
13
+ healthcheck: ['run', 'check', 'status'],
14
+ health: ['run', 'check', 'status'],
15
+ session: ['start', 'verify', 'refresh', 'revoke', 'listActive'],
16
+ cache: ['get', 'set', 'clear', 'clearAll', 'dispatch', 'fetchValues'],
17
+ };
18
+ export function ensureSupportedSnippetOperation(operationFamily, method) {
19
+ const family = operationFamily.toLowerCase();
20
+ const allowed = ALLOWED_SNIPPET_METHODS[family] ?? [];
21
+ if (!allowed.includes(method)) {
22
+ throw new Error(`Unsupported snippet operation "${family}.${method}". Allowed: ${allowed.join(', ') || '(none)'}`);
23
+ }
24
+ }
25
+ function resolveSdkCallPath(operationFamily, method) {
26
+ const family = operationFamily.toLowerCase();
27
+ const m = method.toLowerCase();
28
+ if (family === 'action')
29
+ return `actions.${m}`;
30
+ if (family === 'workflow')
31
+ return `workflows.${m}`;
32
+ if (family === 'database')
33
+ return m === 'dispatch' ? 'databases.dispatch' : `databases.${m}`;
34
+ if (family === 'graph')
35
+ return m === 'dispatch' ? 'graph.dispatch' : `graph.${m}`;
36
+ if (family === 'vector')
37
+ return `vector.${m}`;
38
+ if (family === 'storage')
39
+ return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
40
+ if (family === 'notification')
41
+ return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
42
+ if (family === 'messaging' || family === 'broker') {
43
+ if (m === 'dispatch')
44
+ return 'messageBrokers.dispatch';
45
+ if (m === 'send' || m === 'publish' || m === 'produce')
46
+ return 'messageBrokers.produce';
47
+ return `messageBrokers.${m}`;
48
+ }
49
+ if (family === 'quota')
50
+ return `quotas.${m}`;
51
+ if (family === 'fallback')
52
+ return `fallback.${m}`;
53
+ if (family === 'healthcheck' || family === 'health')
54
+ return `health.${m}`;
55
+ if (family === 'session')
56
+ return `sessions.${m}`;
57
+ if (family === 'cache')
58
+ return (m === 'dispatch' ? 'caches.dispatch' : `caches.${m}`);
59
+ return `${family}.${m}`;
60
+ }
61
+ function buildInvocationArgs(payload) {
62
+ const input = payload.input ?? {};
63
+ return {
64
+ product: payload.product_tag,
65
+ env: payload.env,
66
+ ...input,
67
+ ...(payload.session ? { session: payload.session } : {}),
68
+ ...(payload.cache ? { cache: payload.cache } : {}),
69
+ };
70
+ }
71
+ function prettyJson(v) {
72
+ return JSON.stringify(v ?? {}, null, 2);
73
+ }
74
+ export function buildTypeScriptSnippet(payload, operationFamily, method) {
75
+ const callPath = resolveSdkCallPath(operationFamily, method);
76
+ const args = buildInvocationArgs(payload);
77
+ return `import Ductape from '@ductape/sdk';
78
+
79
+ const ductape = new Ductape({
80
+ publishableKey: process.env.DUCTAPE_PUBLISHABLE_KEY!,
81
+ product: process.env.DUCTAPE_PRODUCT!,
82
+ env: process.env.DUCTAPE_ENV!,
83
+ });
84
+
85
+ async function main() {
86
+ const result = await ductape.${callPath}(${prettyJson(args)});
87
+ console.log(JSON.stringify(result, null, 2));
88
+ }
89
+
90
+ main().catch(console.error);
91
+ `;
92
+ }
93
+ export function buildPythonSnippet(payload, operationFamily, method) {
94
+ const callPath = resolveSdkCallPath(operationFamily, method).replace(/\./g, '_');
95
+ const args = buildInvocationArgs(payload);
96
+ return `import os
97
+ import json
98
+ from ductape import Ductape
99
+
100
+ ductape = Ductape(
101
+ publishable_key=os.environ["DUCTAPE_PUBLISHABLE_KEY"],
102
+ product=os.environ["DUCTAPE_PRODUCT"],
103
+ env=os.environ["DUCTAPE_ENV"],
104
+ )
105
+
106
+ def main():
107
+ args = ${prettyJson(args)}
108
+ result = ductape.${callPath}(args)
109
+ print(json.dumps(result, indent=2))
110
+
111
+ if __name__ == "__main__":
112
+ main()
113
+ `;
114
+ }
115
+ export function buildGoSnippet(payload, operationFamily, method) {
116
+ const callPath = resolveSdkCallPath(operationFamily, method);
117
+ return `package main
118
+
119
+ // go get github.com/ductape/ductape/sdk/go
120
+ // See https://docs.ductape.app for ${operationFamily}.${method}
121
+ // Payload preview:
122
+ // ${prettyJson(payload).split('\n').join('\n// ')}
123
+ func main() {
124
+ // ductape.${callPath}(...)
125
+ }
126
+ `;
127
+ }
128
+ export function buildDotnetSnippet(payload, operationFamily, method) {
129
+ const callPath = resolveSdkCallPath(operationFamily, method);
130
+ return `// dotnet add package Ductape.Sdk
131
+ // Operation: ${operationFamily}.${method} -> ${callPath}
132
+ // Payload: ${JSON.stringify(payload)}
133
+ `;
134
+ }
135
+ export function buildSnippet(language, payload, operationFamily, method) {
136
+ switch (language) {
137
+ case 'python':
138
+ return buildPythonSnippet(payload, operationFamily, method);
139
+ case 'go':
140
+ return buildGoSnippet(payload, operationFamily, method);
141
+ case 'dotnet':
142
+ return buildDotnetSnippet(payload, operationFamily, method);
143
+ default:
144
+ return buildTypeScriptSnippet(payload, operationFamily, method);
145
+ }
146
+ }
@@ -0,0 +1,3 @@
1
+ export type InitLanguage = 'typescript' | 'python' | 'go' | 'dotnet' | 'unknown';
2
+ export declare function detectLanguage(dir: string): InitLanguage;
3
+ export declare function writeInitArtifacts(dir: string, lang: InitLanguage): void;
@@ -0,0 +1,74 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const TEMPLATES_ROOT = path.resolve(__dirname, '../../templates');
6
+ export function detectLanguage(dir) {
7
+ if (fs.existsSync(path.join(dir, 'package.json')))
8
+ return 'typescript';
9
+ if (fs.existsSync(path.join(dir, 'pyproject.toml')) || fs.existsSync(path.join(dir, 'requirements.txt'))) {
10
+ return 'python';
11
+ }
12
+ if (fs.existsSync(path.join(dir, 'go.mod')))
13
+ return 'go';
14
+ try {
15
+ if (fs.readdirSync(dir).some((f) => f.endsWith('.csproj')))
16
+ return 'dotnet';
17
+ }
18
+ catch {
19
+ /* ignore */
20
+ }
21
+ return 'unknown';
22
+ }
23
+ function copyTemplate(lang, targetDir) {
24
+ const map = {
25
+ typescript: { src: 'typescript/ductape.example.ts', dest: 'ductape.example.ts' },
26
+ python: { src: 'python/ductape_example.py', dest: 'ductape_example.py' },
27
+ go: { src: 'go/main.go', dest: 'ductape_example.go' },
28
+ dotnet: { src: 'dotnet/Program.cs', dest: 'DuctapeExample.cs' },
29
+ unknown: null,
30
+ };
31
+ const entry = map[lang];
32
+ if (!entry)
33
+ return;
34
+ const srcPath = path.join(TEMPLATES_ROOT, entry.src);
35
+ const destPath = path.join(targetDir, entry.dest);
36
+ if (!fs.existsSync(destPath) && fs.existsSync(srcPath)) {
37
+ fs.copyFileSync(srcPath, destPath);
38
+ }
39
+ }
40
+ export function writeInitArtifacts(dir, lang) {
41
+ const envContent = `# Ductape — credentials from Workbench → SDK Access
42
+ DUCTAPE_ACCESS_KEY=
43
+ # Passed to new Ductape({ accessKey, product, env }) — constructor is the only place for product/env defaults
44
+ DUCTAPE_PRODUCT=
45
+ DUCTAPE_ENV=dev
46
+ `;
47
+ const envPath = path.join(dir, '.env.ductape.example');
48
+ if (!fs.existsSync(envPath))
49
+ fs.writeFileSync(envPath, envContent);
50
+ const ductapeDir = path.join(dir, '.ductape');
51
+ fs.mkdirSync(ductapeDir, { recursive: true });
52
+ const readme = `# Ductape project
53
+
54
+ Linked via \`ductape link\`. Manage resources with:
55
+
56
+ \`\`\`bash
57
+ ductape resources storage list
58
+ ductape resources databases create -f database.json
59
+ \`\`\`
60
+
61
+ Docs: https://docs.ductape.app/docs/cli/
62
+ `;
63
+ const readmePath = path.join(ductapeDir, 'README.md');
64
+ if (!fs.existsSync(readmePath))
65
+ fs.writeFileSync(readmePath, readme);
66
+ copyTemplate(lang, dir);
67
+ const gitignorePath = path.join(dir, '.gitignore');
68
+ if (fs.existsSync(gitignorePath)) {
69
+ const gi = fs.readFileSync(gitignorePath, 'utf8');
70
+ if (!gi.includes('.env') && !gi.includes('.env.local')) {
71
+ fs.appendFileSync(gitignorePath, '\n.env\n.env.local\n');
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,12 @@
1
+ import { type Credentials } from './config.js';
2
+ export interface WorkspaceContext {
3
+ credentials: Credentials;
4
+ workspaceId: string;
5
+ apiUrl: string;
6
+ profile: string;
7
+ }
8
+ export declare function requireWorkspaceContext(opts?: {
9
+ profile?: string;
10
+ }): WorkspaceContext;
11
+ /** Query params required by integrations/apps REST endpoints (matches Workbench). */
12
+ export declare function workspaceAuthQuery(ctx: WorkspaceContext, extra?: Record<string, string>): Record<string, string>;
@@ -0,0 +1,25 @@
1
+ import { getActiveProfileName, getActiveWorkspaceId, getApiUrl, } from './config.js';
2
+ import { requireCredentials } from './proxy/context.js';
3
+ export function requireWorkspaceContext(opts) {
4
+ const credentials = requireCredentials();
5
+ const workspaceId = getActiveWorkspaceId();
6
+ if (!workspaceId) {
7
+ throw new Error('No active workspace. Run: ductape login or ductape workspaces use');
8
+ }
9
+ const profile = opts?.profile ?? getActiveProfileName(null);
10
+ return {
11
+ credentials,
12
+ workspaceId,
13
+ apiUrl: getApiUrl(profile),
14
+ profile,
15
+ };
16
+ }
17
+ /** Query params required by integrations/apps REST endpoints (matches Workbench). */
18
+ export function workspaceAuthQuery(ctx, extra) {
19
+ return {
20
+ user_id: ctx.credentials.user_id,
21
+ public_key: ctx.credentials.public_key,
22
+ workspace_id: ctx.workspaceId,
23
+ ...extra,
24
+ };
25
+ }
@@ -0,0 +1,45 @@
1
+ /** Workspace entry from login or workspaces API (field names vary). */
2
+ export interface WorkspaceSummary {
3
+ workspace_id?: string;
4
+ workspace_name?: string;
5
+ _id?: string;
6
+ name?: string;
7
+ tag?: string;
8
+ default?: boolean;
9
+ accepted?: boolean;
10
+ }
11
+ export declare function workspaceId(w: WorkspaceSummary): string;
12
+ export declare function workspaceLabel(w: WorkspaceSummary): string;
13
+ /** Human-readable workspace tag for .ductape/config.json (slug of name, or explicit tag). */
14
+ export declare function workspaceTag(w: WorkspaceSummary): string;
15
+ export declare function slugifyTag(value: string): string;
16
+ export declare function normalizeWorkspaces(raw: unknown[]): WorkspaceSummary[];
17
+ export declare function getActiveWorkspace(): {
18
+ id: string;
19
+ name?: string;
20
+ tag?: string;
21
+ } | null;
22
+ export declare function setActiveWorkspace(id: string, name?: string, tag?: string): void;
23
+ export declare function clearActiveWorkspace(): void;
24
+ export declare function fetchWorkspaces(profile?: string): Promise<WorkspaceSummary[]>;
25
+ export declare function findWorkspace(workspaces: WorkspaceSummary[], selector: string): WorkspaceSummary | undefined;
26
+ export declare function printWorkspaceTable(workspaces: WorkspaceSummary[], activeId?: string | null): void;
27
+ export declare function promptWorkspaceSelection(workspaces: WorkspaceSummary[], opts?: {
28
+ message?: string;
29
+ }): Promise<WorkspaceSummary>;
30
+ export declare function applyActiveWorkspace(workspace: WorkspaceSummary, opts?: {
31
+ syncProject?: boolean;
32
+ dir?: string;
33
+ }): Promise<void>;
34
+ export declare function selectAndApplyWorkspace(opts?: {
35
+ profile?: string;
36
+ selector?: string;
37
+ skipPrompt?: boolean;
38
+ syncProject?: boolean;
39
+ dir?: string;
40
+ }): Promise<WorkspaceSummary>;
41
+ export declare function finalizeLoginWorkspace(opts?: {
42
+ profile?: string;
43
+ workspace?: string;
44
+ skipPrompt?: boolean;
45
+ }): Promise<void>;