@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,155 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ export const GLOBAL_DIR = path.join(os.homedir(), '.ductape');
7
+ export const CREDENTIALS_PATH = path.join(GLOBAL_DIR, 'credentials.json');
8
+ export const GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, 'config.json');
9
+ export const PROJECT_CONFIG_DIR = '.ductape';
10
+ export const PROJECT_CONFIG_PATH = path.join(PROJECT_CONFIG_DIR, 'config.json');
11
+ const DEFAULT_GLOBAL = {
12
+ default_profile: 'cloud',
13
+ profiles: {
14
+ /** Production gateway — paths are /users/v1/…, /proxy/v1/… (no /api prefix). */
15
+ cloud: { api_url: 'https://api.ductape.app' },
16
+ /** Local docker api-gateway — paths are /api/users/v1/…, /api/proxy/v1/… */
17
+ local: { api_url: 'http://localhost:4311/api' },
18
+ },
19
+ };
20
+ function readJson(filePath) {
21
+ if (!fs.existsSync(filePath))
22
+ return null;
23
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
24
+ }
25
+ function writeJson(filePath, data, mode = 0o644) {
26
+ const dir = path.dirname(filePath);
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', { mode });
29
+ }
30
+ export function ensureGlobalDir() {
31
+ fs.mkdirSync(GLOBAL_DIR, { recursive: true });
32
+ }
33
+ export function loadCredentials() {
34
+ return readJson(CREDENTIALS_PATH);
35
+ }
36
+ export function saveCredentials(creds) {
37
+ ensureGlobalDir();
38
+ writeJson(CREDENTIALS_PATH, creds, 0o600);
39
+ }
40
+ export function clearCredentials() {
41
+ if (fs.existsSync(CREDENTIALS_PATH))
42
+ fs.unlinkSync(CREDENTIALS_PATH);
43
+ const global = loadGlobalConfig();
44
+ delete global.active_workspace_id;
45
+ delete global.active_workspace_name;
46
+ delete global.active_workspace_tag;
47
+ saveGlobalConfig(global);
48
+ }
49
+ export function getActiveWorkspaceId() {
50
+ const global = loadGlobalConfig();
51
+ return global.active_workspace_id ?? null;
52
+ }
53
+ export function loadGlobalConfig() {
54
+ const cfg = readJson(GLOBAL_CONFIG_PATH) ?? {
55
+ ...DEFAULT_GLOBAL,
56
+ profiles: { ...DEFAULT_GLOBAL.profiles },
57
+ };
58
+ // Migrate older configs that used /api on production (returns HTML 404).
59
+ if (cfg.profiles.cloud?.api_url?.replace(/\/$/, '') === 'https://api.ductape.app/api') {
60
+ cfg.profiles.cloud.api_url = DEFAULT_GLOBAL.profiles.cloud.api_url;
61
+ }
62
+ return cfg;
63
+ }
64
+ export function saveGlobalConfig(config) {
65
+ ensureGlobalDir();
66
+ writeJson(GLOBAL_CONFIG_PATH, config);
67
+ }
68
+ export function getActiveProfileName(project) {
69
+ if (project?.linked_profile)
70
+ return project.linked_profile;
71
+ return loadGlobalConfig().default_profile;
72
+ }
73
+ export function getApiUrl(profileName) {
74
+ const global = loadGlobalConfig();
75
+ const name = profileName ?? global.default_profile;
76
+ const profile = global.profiles[name];
77
+ if (!profile) {
78
+ throw new Error(`Unknown profile "${name}". Run: ductape profiles list`);
79
+ }
80
+ return profile.api_url.replace(/\/$/, '');
81
+ }
82
+ function matchWorkspaceFromCredentials(workspaces, selector) {
83
+ const trimmed = selector.trim();
84
+ const idx = Number(trimmed);
85
+ if (!Number.isNaN(idx) && workspaces[idx]) {
86
+ const w = workspaces[idx];
87
+ return w.workspace_id ?? w._id ?? null;
88
+ }
89
+ for (const w of workspaces) {
90
+ const id = w.workspace_id ?? w._id;
91
+ const name = w.workspace_name ?? w.name ?? '';
92
+ const tag = w.tag ?? '';
93
+ if (id === trimmed ||
94
+ name.toLowerCase() === trimmed.toLowerCase() ||
95
+ tag.toLowerCase() === trimmed.toLowerCase()) {
96
+ return id ?? null;
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ /** Resolve workspace_id from tag (cached workspaces), explicit id, or global active workspace. */
102
+ export function resolveWorkspaceId(cfg, creds) {
103
+ if (cfg.workspace_id?.trim())
104
+ return cfg.workspace_id.trim();
105
+ if (cfg.workspace_tag?.trim() && creds?.workspaces?.length) {
106
+ const id = matchWorkspaceFromCredentials(creds.workspaces, cfg.workspace_tag);
107
+ if (id)
108
+ return id;
109
+ }
110
+ return getActiveWorkspaceId();
111
+ }
112
+ export function findProjectConfig(startDir = process.cwd()) {
113
+ let dir = startDir;
114
+ const root = path.parse(dir).root;
115
+ while (true) {
116
+ const candidate = path.join(dir, PROJECT_CONFIG_PATH);
117
+ const cfg = readJson(candidate);
118
+ if (cfg?.product_tag && (cfg.workspace_tag || cfg.workspace_id)) {
119
+ return { dir, config: cfg };
120
+ }
121
+ if (dir === root)
122
+ break;
123
+ dir = path.dirname(dir);
124
+ }
125
+ return null;
126
+ }
127
+ export function loadProjectConfig() {
128
+ const found = findProjectConfig();
129
+ if (!found) {
130
+ throw new Error('No linked project. Run `ductape link` from your project directory (or `ductape init`).');
131
+ }
132
+ const creds = loadCredentials();
133
+ const cfg = { ...found.config };
134
+ const workspace_id = resolveWorkspaceId(cfg, creds);
135
+ if (!workspace_id) {
136
+ throw new Error('No workspace resolved. Set workspace_tag in .ductape/config.json or run `ductape workspaces use`.');
137
+ }
138
+ return { ...cfg, workspace_id };
139
+ }
140
+ export function saveProjectConfig(dir, config) {
141
+ const configDir = path.join(dir, PROJECT_CONFIG_DIR);
142
+ fs.mkdirSync(configDir, { recursive: true });
143
+ writeJson(path.join(configDir, 'config.json'), config);
144
+ }
145
+ export function resolvePlatformDir() {
146
+ if (process.env.DUCTAPE_PLATFORM_DIR) {
147
+ return path.resolve(process.env.DUCTAPE_PLATFORM_DIR);
148
+ }
149
+ // cli/ -> repo root -> platform/
150
+ const fromPackage = path.resolve(__dirname, '../../../platform');
151
+ if (fs.existsSync(path.join(fromPackage, 'docker-compose.yml'))) {
152
+ return fromPackage;
153
+ }
154
+ throw new Error('Platform directory not found. Set DUCTAPE_PLATFORM_DIR to the folder containing docker-compose.yml');
155
+ }
@@ -0,0 +1,17 @@
1
+ import type { GraphContext } from './proxy/graph-proxy.js';
2
+ export interface DatabaseContext {
3
+ database: string;
4
+ env: string;
5
+ product: string;
6
+ }
7
+ export interface ProjectRuntimeContext {
8
+ workspace_id: string;
9
+ product_tag: string;
10
+ env_slug: string;
11
+ database_context?: DatabaseContext | null;
12
+ graph_context?: GraphContext | null;
13
+ }
14
+ export declare function loadRuntimeContext(workspaceId: string, productTag: string): ProjectRuntimeContext | null;
15
+ export declare function saveRuntimeContext(ctx: ProjectRuntimeContext): void;
16
+ export declare function setDatabaseContext(workspaceId: string, productTag: string, envSlug: string, db: DatabaseContext | null): void;
17
+ export declare function setGraphContext(workspaceId: string, productTag: string, envSlug: string, graph: GraphContext | null): void;
@@ -0,0 +1,43 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { GLOBAL_DIR } from './config.js';
4
+ const RUNTIME_PATH = path.join(GLOBAL_DIR, 'runtime-context.json');
5
+ function readAll() {
6
+ if (!fs.existsSync(RUNTIME_PATH))
7
+ return {};
8
+ return JSON.parse(fs.readFileSync(RUNTIME_PATH, 'utf8'));
9
+ }
10
+ function writeAll(data) {
11
+ fs.mkdirSync(GLOBAL_DIR, { recursive: true });
12
+ fs.writeFileSync(RUNTIME_PATH, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
13
+ }
14
+ function projectKey(workspaceId, productTag) {
15
+ return `${workspaceId}:${productTag}`;
16
+ }
17
+ export function loadRuntimeContext(workspaceId, productTag) {
18
+ const all = readAll();
19
+ return all[projectKey(workspaceId, productTag)] ?? null;
20
+ }
21
+ export function saveRuntimeContext(ctx) {
22
+ const all = readAll();
23
+ all[projectKey(ctx.workspace_id, ctx.product_tag)] = ctx;
24
+ writeAll(all);
25
+ }
26
+ export function setDatabaseContext(workspaceId, productTag, envSlug, db) {
27
+ const existing = loadRuntimeContext(workspaceId, productTag) ?? {
28
+ workspace_id: workspaceId,
29
+ product_tag: productTag,
30
+ env_slug: envSlug,
31
+ };
32
+ existing.database_context = db;
33
+ saveRuntimeContext(existing);
34
+ }
35
+ export function setGraphContext(workspaceId, productTag, envSlug, graph) {
36
+ const existing = loadRuntimeContext(workspaceId, productTag) ?? {
37
+ workspace_id: workspaceId,
38
+ product_tag: productTag,
39
+ env_slug: envSlug,
40
+ };
41
+ existing.graph_context = graph;
42
+ saveRuntimeContext(existing);
43
+ }
@@ -0,0 +1,3 @@
1
+ /** db-proxy methods (runtime). Dot notation supported: schema.list */
2
+ export declare const DB_PROXY_METHODS: readonly ["connect", "disconnect", "closeAll", "testConnection", "getCurrentContext", "connection", "query", "execute", "insert", "update", "delete", "upsert", "count", "sum", "avg", "min", "max", "aggregate", "listTables", "listTablesWithInfo", "describe", "getSchema", "beginTransaction", "schema.create", "schema.drop", "schema.list", "schema.describe", "schema.addField", "schema.dropField", "migration.list", "migration.run"];
3
+ export declare const GRAPH_PROXY_METHODS: readonly ["connect", "disconnect", "disconnectAll", "testConnection", "getCurrentContext", "create", "fetchAll", "fetch", "update", "delete", "createNode", "findNodes", "query", "traverse", "shortestPath", "countNodes", "dispatch", "execute", "executeTransaction"];
@@ -0,0 +1,55 @@
1
+ /** db-proxy methods (runtime). Dot notation supported: schema.list */
2
+ export const DB_PROXY_METHODS = [
3
+ 'connect',
4
+ 'disconnect',
5
+ 'closeAll',
6
+ 'testConnection',
7
+ 'getCurrentContext',
8
+ 'connection',
9
+ 'query',
10
+ 'execute',
11
+ 'insert',
12
+ 'update',
13
+ 'delete',
14
+ 'upsert',
15
+ 'count',
16
+ 'sum',
17
+ 'avg',
18
+ 'min',
19
+ 'max',
20
+ 'aggregate',
21
+ 'listTables',
22
+ 'listTablesWithInfo',
23
+ 'describe',
24
+ 'getSchema',
25
+ 'beginTransaction',
26
+ 'schema.create',
27
+ 'schema.drop',
28
+ 'schema.list',
29
+ 'schema.describe',
30
+ 'schema.addField',
31
+ 'schema.dropField',
32
+ 'migration.list',
33
+ 'migration.run',
34
+ ];
35
+ export const GRAPH_PROXY_METHODS = [
36
+ 'connect',
37
+ 'disconnect',
38
+ 'disconnectAll',
39
+ 'testConnection',
40
+ 'getCurrentContext',
41
+ 'create',
42
+ 'fetchAll',
43
+ 'fetch',
44
+ 'update',
45
+ 'delete',
46
+ 'createNode',
47
+ 'findNodes',
48
+ 'query',
49
+ 'traverse',
50
+ 'shortestPath',
51
+ 'countNodes',
52
+ 'dispatch',
53
+ 'execute',
54
+ 'executeTransaction',
55
+ ];
@@ -0,0 +1 @@
1
+ export declare function encryptProxyPayload(data: object, publicKey: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Proxy payload encryption — matches platform/workbench/src/utils/proxyEncryption.ts
3
+ */
4
+ import CryptoJS from 'crypto-js';
5
+ export function encryptProxyPayload(data, publicKey) {
6
+ const jsonString = JSON.stringify(data);
7
+ const iv = CryptoJS.lib.WordArray.random(16);
8
+ const hashedKey = CryptoJS.SHA256(publicKey);
9
+ const encrypted = CryptoJS.AES.encrypt(jsonString, hashedKey, { iv });
10
+ return iv.toString(CryptoJS.enc.Hex) + ':' + encrypted.toString();
11
+ }
@@ -0,0 +1,11 @@
1
+ /** Enum values aligned with @ductape/sdk — used for CLI dropdowns */
2
+ export declare const STORAGE_PROVIDERS: readonly ["aws", "gcp", "azure"];
3
+ export declare const DATABASE_TYPES: readonly ["postgresql", "mongodb", "mysql"];
4
+ export declare const GRAPH_TYPES: readonly ["neo4j", "neptune", "arangodb", "memgraph"];
5
+ export declare const VECTOR_DB_TYPES: readonly ["pinecone", "qdrant", "weaviate", "chroma", "milvus", "pgvector", "memory"];
6
+ export declare const VECTOR_DISTANCE_METRICS: readonly ["cosine", "euclidean", "dotproduct"];
7
+ export declare const MESSAGE_BROKER_TYPES: readonly ["rabbitmq", "kafka", "aws_sqs", "redis", "google_pubsub", "nats"];
8
+ export declare const JOB_EVENT_TYPES: readonly ["action", "notification", "database_action", "database_operation", "graph_action", "graph_operation", "workflow", "storage"];
9
+ export declare const DATABASE_ACTION_TYPES: readonly ["read", "count", "create", "update", "delete", "aggregate"];
10
+ export declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
11
+ export declare const NOTIFIERS: readonly ["email", "sms", "push", "callback"];
@@ -0,0 +1,42 @@
1
+ /** Enum values aligned with @ductape/sdk — used for CLI dropdowns */
2
+ export const STORAGE_PROVIDERS = ['aws', 'gcp', 'azure'];
3
+ export const DATABASE_TYPES = ['postgresql', 'mongodb', 'mysql'];
4
+ export const GRAPH_TYPES = ['neo4j', 'neptune', 'arangodb', 'memgraph'];
5
+ export const VECTOR_DB_TYPES = [
6
+ 'pinecone',
7
+ 'qdrant',
8
+ 'weaviate',
9
+ 'chroma',
10
+ 'milvus',
11
+ 'pgvector',
12
+ 'memory',
13
+ ];
14
+ export const VECTOR_DISTANCE_METRICS = ['cosine', 'euclidean', 'dotproduct'];
15
+ export const MESSAGE_BROKER_TYPES = [
16
+ 'rabbitmq',
17
+ 'kafka',
18
+ 'aws_sqs',
19
+ 'redis',
20
+ 'google_pubsub',
21
+ 'nats',
22
+ ];
23
+ export const JOB_EVENT_TYPES = [
24
+ 'action',
25
+ 'notification',
26
+ 'database_action',
27
+ 'database_operation',
28
+ 'graph_action',
29
+ 'graph_operation',
30
+ 'workflow',
31
+ 'storage',
32
+ ];
33
+ export const DATABASE_ACTION_TYPES = [
34
+ 'read',
35
+ 'count',
36
+ 'create',
37
+ 'update',
38
+ 'delete',
39
+ 'aggregate',
40
+ ];
41
+ export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
42
+ export const NOTIFIERS = ['email', 'sms', 'push', 'callback'];
@@ -0,0 +1,2 @@
1
+ export { isInteractive, promptForm, promptKey, promptTag, type InteractiveOpts } from './prompt.js';
2
+ export { genericComponentSchema, getFormSchema, resolveResourceSchema, schemaKeyForEntity, schemaKeyForResource, } from './schemas.js';
@@ -0,0 +1,2 @@
1
+ export { isInteractive, promptForm, promptKey, promptTag } from './prompt.js';
2
+ export { genericComponentSchema, getFormSchema, resolveResourceSchema, schemaKeyForEntity, schemaKeyForResource, } from './schemas.js';
@@ -0,0 +1,11 @@
1
+ import type { FormSchema } from './types.js';
2
+ export type InteractiveOpts = {
3
+ interactive?: boolean;
4
+ noInteractive?: boolean;
5
+ };
6
+ export declare function isInteractive(opts?: InteractiveOpts): boolean;
7
+ export declare function promptForm(schema: FormSchema, opts?: {
8
+ patch?: boolean;
9
+ }): Promise<Record<string, unknown>>;
10
+ export declare function promptTag(label?: string): Promise<string>;
11
+ export declare function promptKey(label?: string): Promise<string>;
@@ -0,0 +1,203 @@
1
+ import input from '@inquirer/input';
2
+ import select from '@inquirer/select';
3
+ import { MESSAGE_BROKER_TYPES, STORAGE_PROVIDERS } from './enums.js';
4
+ export function isInteractive(opts) {
5
+ if (opts?.noInteractive)
6
+ return false;
7
+ if (opts?.interactive === true)
8
+ return true;
9
+ if (opts?.interactive === false)
10
+ return false;
11
+ return Boolean(process.stdin.isTTY && !process.env.CI && process.env.DUCTAPE_NO_INTERACTIVE !== '1');
12
+ }
13
+ async function askYesNo(message, defaultYes = true) {
14
+ return select({
15
+ message,
16
+ choices: [
17
+ { name: 'Yes', value: true },
18
+ { name: 'No', value: false },
19
+ ],
20
+ default: defaultYes,
21
+ });
22
+ }
23
+ async function promptJsonObject(message, required = true) {
24
+ const raw = await input({
25
+ message: `${message} (JSON object${required ? '' : ', optional'})`,
26
+ });
27
+ if (!raw.trim()) {
28
+ if (required)
29
+ throw new Error('JSON object is required');
30
+ return {};
31
+ }
32
+ const parsed = JSON.parse(raw);
33
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
34
+ throw new Error('Expected a JSON object');
35
+ }
36
+ return parsed;
37
+ }
38
+ async function promptStorageConfig(provider) {
39
+ if (provider === 'aws') {
40
+ return {
41
+ bucketName: await input({ message: 'S3 bucket name' }),
42
+ accessKeyId: await input({ message: 'AWS access key ID' }),
43
+ secretAccessKey: await input({ message: 'AWS secret access key' }),
44
+ region: await input({ message: 'AWS region' }),
45
+ };
46
+ }
47
+ if (provider === 'azure') {
48
+ return {
49
+ containerName: await input({ message: 'Container name' }),
50
+ connectionString: await input({ message: 'Connection string' }),
51
+ };
52
+ }
53
+ if (provider === 'gcp') {
54
+ return {
55
+ bucketName: await input({ message: 'GCS bucket name' }),
56
+ config: await promptJsonObject('GCP service account config'),
57
+ };
58
+ }
59
+ return promptJsonObject(`Config for ${provider}`);
60
+ }
61
+ async function promptEnvs(variant, context) {
62
+ const envs = [];
63
+ let addMore = true;
64
+ while (addMore) {
65
+ const slug = await input({
66
+ message: `Environment slug #${envs.length + 1} (3 letters)`,
67
+ validate: (v) => (v.length >= 1 && v.length <= 3 ? true : 'Use 1–3 characters'),
68
+ });
69
+ if (variant === 'database' || variant === 'graph') {
70
+ const connection_url = await input({
71
+ message: 'Connection URL',
72
+ });
73
+ const description = await input({ message: 'Description (optional)', default: '' });
74
+ const row = { slug, connection_url };
75
+ if (description.trim())
76
+ row.description = description.trim();
77
+ envs.push(row);
78
+ }
79
+ else if (variant === 'storage') {
80
+ const type = await select({
81
+ message: 'Storage provider',
82
+ choices: STORAGE_PROVIDERS.map((p) => ({ name: p, value: p })),
83
+ });
84
+ const config = await promptStorageConfig(type);
85
+ envs.push({ slug, type, config });
86
+ }
87
+ else if (variant === 'broker') {
88
+ const type = await select({
89
+ message: 'Broker type',
90
+ choices: MESSAGE_BROKER_TYPES.map((p) => ({ name: p, value: p })),
91
+ });
92
+ const config = await promptJsonObject('Broker config');
93
+ envs.push({ slug, type, config });
94
+ }
95
+ else if (variant === 'vector') {
96
+ const vectorType = context?.vectorType ?? 'pinecone';
97
+ const row = { slug };
98
+ if (vectorType === 'memory') {
99
+ const index = await input({ message: 'Index name (optional)', default: '' });
100
+ if (index.trim())
101
+ row.index = index.trim();
102
+ }
103
+ else {
104
+ row.endpoint = await input({ message: 'Endpoint URL' });
105
+ const apiKey = await input({ message: 'API key (optional)', default: '' });
106
+ const index = await input({ message: 'Index / collection (optional)', default: '' });
107
+ if (apiKey.trim())
108
+ row.apiKey = apiKey.trim();
109
+ if (index.trim())
110
+ row.index = index.trim();
111
+ }
112
+ envs.push(row);
113
+ }
114
+ else {
115
+ envs.push({ slug });
116
+ }
117
+ addMore = await askYesNo('Add another environment?', false);
118
+ }
119
+ return envs;
120
+ }
121
+ async function promptField(field, patch) {
122
+ const required = patch ? field.required : field.required !== false;
123
+ if (field.kind === 'enum' && field.choices?.length) {
124
+ const value = await select({
125
+ message: field.label,
126
+ choices: field.choices.map((c) => ({ name: c, value: c })),
127
+ default: field.default,
128
+ });
129
+ return value;
130
+ }
131
+ if (field.kind === 'number') {
132
+ const raw = await input({
133
+ message: field.label,
134
+ default: field.default !== undefined ? String(field.default) : undefined,
135
+ });
136
+ if (!raw.trim() && !required)
137
+ return undefined;
138
+ const n = Number(raw);
139
+ if (Number.isNaN(n))
140
+ throw new Error(`${field.label} must be a number`);
141
+ return n;
142
+ }
143
+ if (field.kind === 'envs') {
144
+ if (!field.envsVariant)
145
+ throw new Error('envs field missing envsVariant');
146
+ return promptEnvs(field.envsVariant);
147
+ }
148
+ const value = await input({
149
+ message: field.label,
150
+ default: field.default !== undefined ? String(field.default) : undefined,
151
+ });
152
+ if (!value.trim() && !required)
153
+ return undefined;
154
+ if (!value.trim() && required)
155
+ throw new Error(`${field.label} is required`);
156
+ return value.trim();
157
+ }
158
+ export async function promptForm(schema, opts) {
159
+ const patch = Boolean(opts?.patch);
160
+ const body = {};
161
+ let vectorType;
162
+ for (const field of schema.fields) {
163
+ if (patch && !field.required) {
164
+ const set = await askYesNo(`Set ${field.label}?`, false);
165
+ if (!set)
166
+ continue;
167
+ }
168
+ if (field.key === 'type' && field.kind === 'enum') {
169
+ const value = await promptField(field, patch);
170
+ if (value !== undefined) {
171
+ body.type = value;
172
+ vectorType = String(value);
173
+ }
174
+ continue;
175
+ }
176
+ if (field.kind === 'envs' && field.envsVariant === 'vector') {
177
+ const envs = await promptEnvs('vector', { vectorType });
178
+ if (envs.length)
179
+ body.envs = envs;
180
+ continue;
181
+ }
182
+ const value = await promptField(field, patch);
183
+ if (value !== undefined && value !== '') {
184
+ body[field.key] = value;
185
+ }
186
+ }
187
+ if (schema.allowExtraJson) {
188
+ const useExtra = await askYesNo('Add extra fields as JSON?', false);
189
+ if (useExtra) {
190
+ Object.assign(body, await promptJsonObject('Extra fields', false));
191
+ }
192
+ }
193
+ return body;
194
+ }
195
+ export async function promptTag(label = 'Tag') {
196
+ const value = await input({ message: label });
197
+ if (!value.trim())
198
+ throw new Error(`${label} is required`);
199
+ return value.trim();
200
+ }
201
+ export async function promptKey(label = 'Secret key') {
202
+ return promptTag(label);
203
+ }
@@ -0,0 +1,9 @@
1
+ import type { SDKModule } from '../proxy/sdk-proxy.js';
2
+ import type { CrudVerb } from '../resources.js';
3
+ import type { FormSchema } from './types.js';
4
+ export declare function getFormSchema(schemaKey: string): FormSchema | undefined;
5
+ export declare function schemaKeyForResource(module: SDKModule, verb: CrudVerb): string | undefined;
6
+ export declare function schemaKeyForEntity(entity: 'products' | 'apps', verb: string): string | undefined;
7
+ /** Fallback schema for component types without a dedicated form */
8
+ export declare function genericComponentSchema(verb: 'create' | 'update'): FormSchema;
9
+ export declare function resolveResourceSchema(module: SDKModule, verb: CrudVerb): FormSchema | undefined;