@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
package/API_PARITY.md ADDED
@@ -0,0 +1,58 @@
1
+ # CLI ↔ API parity (Nest / Workbench)
2
+
3
+ The CLI uses the **same REST paths as the Workbench and TypeScript SDK**.
4
+
5
+ | Profile | `api_url` | Example login URL |
6
+ |---------|-----------|-------------------|
7
+ | `cloud` | `https://api.ductape.app` | `https://api.ductape.app/users/v1/login` |
8
+ | `local` | `http://localhost:4311/api` | `http://localhost:4311/api/users/v1/login` |
9
+
10
+ Production does **not** use an `/api` prefix; the local docker gateway does.
11
+
12
+ ## Gateway routing
13
+
14
+ | Client calls | Gateway (`/api/...`) | Nest service receives |
15
+ |--------------|----------------------|------------------------|
16
+ | `{api}/users/v1/login` | `/api/users/` → users:8002 | `/users/v1/login` |
17
+ | `{api}/workspaces/v1/fetch/...` | `/api/workspaces/` → workspaces:8001 | `/workspaces/v1/...` |
18
+ | `{api}/apps/v1/import` | `/api/apps/` → apps:8004 | `/apps/v1/import` |
19
+ | `{api}/integrations/v1/create` | `/api/integrations/` → integrations:8009 | `/integrations/v1/create` |
20
+ | `{api}/proxy/v1/sdk-proxy/execute` | `/api/proxy/` → proxy:8020 | encrypted sdk-proxy |
21
+
22
+ Legacy Express services are **not** called by the CLI. All commands target Nest controllers under `platform/nest/apps/*/src/**/controllers/`.
23
+
24
+ ## Command → endpoint map
25
+
26
+ | CLI | Method | Path (after `{api_url}`) | Workbench equivalent |
27
+ |-----|--------|---------------------------|-------------------|
28
+ | `ductape login` | POST | `/users/v1/login` | Login modal |
29
+ | `ductape workspaces list` | GET | `/workspaces/v1/fetch/:user_id` | Workspace list |
30
+ | `ductape products list` | GET | `/integrations/v1/workspace/:id/:status` | Products sidebar |
31
+ | `ductape products create` | POST | `/integrations/v1/create` | Create product |
32
+ | `ductape products update` | sdk-proxy | `product.update` | Product settings |
33
+ | `ductape products delete` | DELETE | `/integrations/v1/:id` | Delete product |
34
+ | `ductape apps list` | GET | `/apps/v1/workspace/:id/:status` | Apps sidebar |
35
+ | `ductape apps create` | POST | `/apps/v1/create` | Create app |
36
+ | `ductape apps import` | POST multipart | `/apps/v1/import` | App import dialog |
37
+ | `ductape apps update` | PUT | `/apps/v1/:app_id` | App settings |
38
+ | `ductape apps delete` | DELETE | `/apps/v1/:app_id` | Delete app |
39
+ | `ductape resources *` | POST | `/proxy/v1/sdk-proxy/execute` | SDK proxy (encrypted) |
40
+ | `ductape db *` | POST | `/proxy/v1/db-proxy/...` | Database proxy |
41
+ | `ductape graph *` | POST | `/proxy/v1/graph-proxy/...` | Graph proxy |
42
+
43
+ ## Import body fields (`apps import`)
44
+
45
+ Matches `AppsImportController` and `@ductape/sdk` `AppApiService.importApp`:
46
+
47
+ | Field | CLI / SDK value |
48
+ |-------|-----------------|
49
+ | `type` | `PostmanV2.1` or `OpenApiV3.0` |
50
+ | `version` | default `0.0.1` |
51
+ | `update` | `true` / `false` |
52
+ | `workspace_id`, `user_id`, `public_key` | from session |
53
+ | `app_id` | optional (`--app-id` / `--app-tag`) |
54
+ | `file` | multipart upload |
55
+
56
+ ## SDK base URL note
57
+
58
+ The TS SDK client uses host `https://api.ductape.app` with paths like `/apps/v1/...` (no `/api` segment). Production routing may terminate TLS at a layer that forwards directly to services. **CLI and Workbench** use `{host}/api` as `api_url` — always set `ductape profiles use local` to `http://localhost:4311/api` for local parity.
package/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - `ductape apps import <file> --type postman|openapi` (Postman v2.1 / OpenAPI 3.0)
8
+ - Project config uses `workspace_tag` (CLI resolves `workspace_id` automatically)
9
+ - `ductape products` and `ductape apps` CRUD (workspace REST + sdk-proxy update)
10
+ - `ductape products apps list --product <id>`
11
+ - CLI docs split into `docs/docs/cli/` folder with per-topic pages
12
+ - Interactive workspace selection after `ductape login` (stored in `~/.ductape/config.json`)
13
+ - `ductape workspaces current`, `workspaces switch`, `workspaces refresh`
14
+ - `ductape login --workspace`, `--skip-workspace-select`
15
+ - `ductape link` defaults to active workspace; `whoami` shows `active_workspace`
16
+ - Switching workspaces updates linked `.ductape/config.json` when present
17
+
18
+ ## 0.2.0
19
+
20
+ ### Added
21
+
22
+ - OAuth login: `ductape login --browser`, `ductape login --token`
23
+ - `ductape install`, `ductape unlink`, `ductape workspaces list|use`
24
+ - `ductape graph <verb>` (graph-proxy, persisted graph context)
25
+ - Expanded `ductape db` (db-proxy, `db context`, more methods)
26
+ - `ductape secrets` CRUD
27
+ - `ductape generate payload|snippet`
28
+ - Shell completions: `ductape completion bash|zsh`
29
+ - Language bootstrap templates (TypeScript, Python, Go, .NET)
30
+ - Gzip support for large proxy payloads
31
+ - Runtime context store (`~/.ductape/runtime-context.json`)
32
+ - Unit tests (`npm test`)
33
+
34
+ ## 0.1.0
35
+
36
+ - Initial CLI: login, profiles, link, init, start/stop/status, resources CRUD, cloud, db basics
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @ductape/cli
2
+
3
+ Terminal interface for Ductape — **same transport as the Workbench** (encrypted sdk-proxy / db-proxy / graph-proxy).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ cd cli
9
+ npm install
10
+ npm run build
11
+ ```
12
+
13
+ ### Global `ductape` command (optional)
14
+
15
+ `npm link` installs into npm’s global prefix. If you see `EACCES` on `/usr/local/lib/node_modules`, that prefix is owned by root (common with the system Node installer). Use one of these:
16
+
17
+ **Option A — run without linking (no sudo)**
18
+
19
+ ```bash
20
+ npm exec ductape -- login
21
+ npm exec ductape -- resources storage list
22
+ # or: node dist/index.js login
23
+ ```
24
+
25
+ **Option B — user-owned global prefix (recommended for `npm link`)**
26
+
27
+ ```bash
28
+ mkdir -p ~/.npm-global
29
+ npm config set prefix "$HOME/.npm-global"
30
+ # Add to ~/.zshrc (or ~/.bashrc):
31
+ # export PATH="$HOME/.npm-global/bin:$PATH"
32
+ source ~/.zshrc
33
+ cd cli && npm link
34
+ ductape --help
35
+ ```
36
+
37
+ Do not use `sudo npm link`; it mixes root-owned globals with your normal user.
38
+
39
+ ## Quick start
40
+
41
+ ```bash
42
+ ductape install
43
+ ductape login # uses `cloud` profile (https://api.ductape.app)
44
+ # Local platform (UI + API on your machine):
45
+ ductape profiles use local
46
+ ductape start
47
+ ductape login --profile local
48
+
49
+ # Workbench UI locally, cloud API (no local backend):
50
+ ductape start --remote --build
51
+ ductape login
52
+ ductape init --link
53
+ ductape resources storage list
54
+ ductape generate snippet storage upload -l typescript -o upload.ts
55
+ ```
56
+
57
+ ## Command overview
58
+
59
+ ```
60
+ ductape login|logout|whoami|install
61
+ ductape profiles list|use <name>
62
+ ductape workspaces list|current|use # interactive picker on login/link/use
63
+ ductape products list|get|create|update|delete
64
+ ductape apps list|get|create|update|delete|import
65
+ ductape init|link|unlink
66
+ ductape start|stop|status # status explains proxy 502 when backend is down
67
+ ductape resources <type> <verb> # CRUD via sdk-proxy (interactive prompts or -f JSON)
68
+ ductape cloud connections list|create|get|update|complete|validate|delete
69
+ ductape cloud resources list|import|provision …
70
+ ductape db <verb> # db-proxy runtime
71
+ ductape graph <verb> # graph-proxy runtime
72
+ ductape secrets <verb>
73
+ ductape generate payload|snippet …
74
+ ductape completion bash|zsh
75
+ ```
76
+
77
+ ## Documentation
78
+
79
+ Full reference: [docs/docs/cli/](../docs/docs/cli/index.mdx)
80
+
81
+ ## Tests
82
+
83
+ ```bash
84
+ npm test
85
+ ```
86
+
87
+ ## Changelog
88
+
89
+ See [CHANGELOG.md](./CHANGELOG.md).
@@ -0,0 +1,9 @@
1
+ export declare function runAppsImport(file: string, opts: {
2
+ profile?: string;
3
+ type: string;
4
+ appId?: string;
5
+ appTag?: string;
6
+ update?: boolean;
7
+ version?: string;
8
+ json?: boolean;
9
+ }): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ import { getApp } from '../lib/platform-api.js';
3
+ import { importAppFromFile, parseImportFormat } from '../lib/app-import.js';
4
+ import { printJson, success } from '../lib/output.js';
5
+ import { requireWorkspaceContext } from '../lib/workspace-context.js';
6
+ export async function runAppsImport(file, opts) {
7
+ const ctx = requireWorkspaceContext({ profile: opts.profile });
8
+ const format = parseImportFormat(opts.type);
9
+ let appId = opts.appId;
10
+ if (!appId && opts.appTag) {
11
+ const app = (await getApp(ctx, { tag: opts.appTag }));
12
+ appId = app._id;
13
+ if (!appId)
14
+ throw new Error(`App not found for tag: ${opts.appTag}`);
15
+ }
16
+ const result = await importAppFromFile(ctx, {
17
+ filePath: path.resolve(file),
18
+ format,
19
+ appId,
20
+ update: Boolean(opts.update),
21
+ version: opts.version,
22
+ });
23
+ if (opts.json) {
24
+ printJson(result, true);
25
+ return;
26
+ }
27
+ const label = format === 'postman' ? 'Postman v2.1' : 'OpenAPI 3.0';
28
+ success(`Imported ${label} collection from ${path.basename(file)}`);
29
+ }
@@ -0,0 +1,10 @@
1
+ import { type CommandInteractiveFlags } from '../lib/interactive-opts.js';
2
+ export declare function runApps(verb: string, opts: {
3
+ profile?: string;
4
+ status?: string;
5
+ id?: string;
6
+ tag?: string;
7
+ file?: string;
8
+ json?: boolean;
9
+ proxy?: boolean;
10
+ } & CommandInteractiveFlags, extraArgs: string[]): Promise<void>;
@@ -0,0 +1,68 @@
1
+ import { schemaKeyForEntity } from '../lib/forms/index.js';
2
+ import { isInteractive, promptTag } from '../lib/forms/prompt.js';
3
+ import { toInteractiveOpts } from '../lib/interactive-opts.js';
4
+ import { bodyRequiredHint, resolveBody } from '../lib/read-body.js';
5
+ import { createApp, deleteApp, getApp, listApps, updateApp, updateAppViaProxy, } from '../lib/platform-api.js';
6
+ import { printJson } from '../lib/output.js';
7
+ import { requireWorkspaceContext } from '../lib/workspace-context.js';
8
+ const VERBS = ['list', 'get', 'create', 'update', 'delete'];
9
+ export async function runApps(verb, opts, extraArgs) {
10
+ const v = verb.toLowerCase();
11
+ if (!VERBS.includes(v)) {
12
+ throw new Error(`Unknown apps verb "${verb}". Use: ${VERBS.join(', ')}`);
13
+ }
14
+ const ctx = requireWorkspaceContext({ profile: opts.profile });
15
+ const status = opts.status ?? 'all';
16
+ const interactive = toInteractiveOpts(opts);
17
+ const schemaKey = schemaKeyForEntity('apps', v);
18
+ const body = v === 'create' || v === 'update'
19
+ ? await resolveBody({
20
+ ...interactive,
21
+ file: opts.file,
22
+ schemaKey,
23
+ patch: v === 'update',
24
+ requireBody: v === 'create',
25
+ })
26
+ : undefined;
27
+ const id = opts.id ?? extraArgs[0];
28
+ let tag = opts.tag ?? (extraArgs[0] && !opts.id ? extraArgs[0] : undefined);
29
+ if (v === 'update' && (opts.proxy || !id) && !tag && isInteractive(interactive)) {
30
+ tag = await promptTag('App tag');
31
+ }
32
+ let result;
33
+ switch (v) {
34
+ case 'list':
35
+ result = await listApps(ctx, status);
36
+ break;
37
+ case 'get':
38
+ result = await getApp(ctx, { id, tag });
39
+ break;
40
+ case 'create':
41
+ if (!body || Object.keys(body).length === 0) {
42
+ throw new Error(`create requires a body. ${bodyRequiredHint('apps:create')}`);
43
+ }
44
+ result = await createApp(ctx, body);
45
+ break;
46
+ case 'update': {
47
+ if (opts.proxy || tag) {
48
+ const appTag = tag ?? body?.tag;
49
+ if (!appTag)
50
+ throw new Error('update via proxy requires --tag <app_tag>');
51
+ result = await updateAppViaProxy(ctx, appTag, body ?? {});
52
+ break;
53
+ }
54
+ if (!id)
55
+ throw new Error('update requires --id <app_id> or --tag with --proxy');
56
+ result = await updateApp(ctx, id, body ?? {});
57
+ break;
58
+ }
59
+ case 'delete':
60
+ if (!id)
61
+ throw new Error('delete requires --id <app_id>');
62
+ result = await deleteApp(ctx, id);
63
+ break;
64
+ default:
65
+ throw new Error(`Unsupported: apps ${v}`);
66
+ }
67
+ printJson(result, Boolean(opts.json));
68
+ }
@@ -0,0 +1,7 @@
1
+ type CloudTarget = 'connections' | 'resources';
2
+ export declare function runCloud(target: CloudTarget, verb: string, opts: {
3
+ id?: string;
4
+ file?: string;
5
+ json?: boolean;
6
+ }, extraArgs: string[]): Promise<void>;
7
+ export {};
@@ -0,0 +1,42 @@
1
+ import { readJsonBody } from '../lib/read-body.js';
2
+ import { getSdkProxy, requireSession } from '../lib/proxy/context.js';
3
+ import { printJson } from '../lib/output.js';
4
+ export async function runCloud(target, verb, opts, extraArgs) {
5
+ requireSession();
6
+ const proxy = getSdkProxy();
7
+ const body = readJsonBody(opts.file);
8
+ const id = opts.id ?? extraArgs[0];
9
+ let method;
10
+ let params;
11
+ if (target === 'connections') {
12
+ const map = {
13
+ create: ['connections.create', [body]],
14
+ list: ['connections.list', []],
15
+ get: ['connections.fetch', [id]],
16
+ fetch: ['connections.fetch', [id]],
17
+ delete: ['connections.delete', [id]],
18
+ update: ['connections.update', [id, body]],
19
+ complete: ['connections.complete', [id, body]],
20
+ validate: ['connections.validate', [id]],
21
+ };
22
+ const entry = map[verb.toLowerCase()];
23
+ if (!entry)
24
+ throw new Error(`Unknown cloud connections verb: ${verb}`);
25
+ [method, params] = entry;
26
+ }
27
+ else {
28
+ const map = {
29
+ list: ['resources.list', [body]],
30
+ import: ['resources.import', [body]],
31
+ provision: ['resources.provision', [body]],
32
+ 'import-persist': ['resources.importAndPersist', [body]],
33
+ 'provision-persist': ['resources.provisionAndPersist', [body]],
34
+ };
35
+ const entry = map[verb.toLowerCase()];
36
+ if (!entry)
37
+ throw new Error(`Unknown cloud resources verb: ${verb}`);
38
+ [method, params] = entry;
39
+ }
40
+ const result = await proxy.execute('cloud', method, params);
41
+ printJson(result, Boolean(opts.json));
42
+ }
@@ -0,0 +1 @@
1
+ export declare function runCompletion(shell: 'bash' | 'zsh' | 'fish'): void;
@@ -0,0 +1,113 @@
1
+ import { listResourceTypes } from '../lib/resources.js';
2
+ import { DB_PROXY_METHODS, GRAPH_PROXY_METHODS } from '../lib/db-methods.js';
3
+ const TOP_LEVEL = [
4
+ 'login',
5
+ 'logout',
6
+ 'whoami',
7
+ 'profiles',
8
+ 'workspaces',
9
+ 'link',
10
+ 'unlink',
11
+ 'init',
12
+ 'install',
13
+ 'start',
14
+ 'stop',
15
+ 'status',
16
+ 'products',
17
+ 'apps',
18
+ 'resources',
19
+ 'cloud',
20
+ 'db',
21
+ 'graph',
22
+ 'secrets',
23
+ 'generate',
24
+ 'completion',
25
+ ];
26
+ const ENTITY_CRUD = 'list get create update delete';
27
+ export function runCompletion(shell) {
28
+ const resources = listResourceTypes();
29
+ const crud = 'create list get update delete connect';
30
+ if (shell === 'bash') {
31
+ console.log(`
32
+ _ductape_completions() {
33
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
34
+ local prev="\${COMP_WORDS[COMP_CWORD-1]}"
35
+ local cmd="\${COMP_WORDS[1]}"
36
+
37
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
38
+ COMPREPLY=( $(compgen -W "${TOP_LEVEL.join(' ')}" -- "$cur") )
39
+ return
40
+ fi
41
+
42
+ case "$cmd" in
43
+ products)
44
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
45
+ COMPREPLY=( $(compgen -W "${ENTITY_CRUD}" -- "$cur") )
46
+ fi
47
+ ;;
48
+ apps)
49
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
50
+ COMPREPLY=( $(compgen -W "import ${ENTITY_CRUD}" -- "$cur") )
51
+ elif [[ \${COMP_CWORD} -eq 3 && "\${COMP_WORDS[2]}" == "import" ]]; then
52
+ COMPREPLY=( $(compgen -f -- "$cur") )
53
+ fi
54
+ ;;
55
+ resources)
56
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
57
+ COMPREPLY=( $(compgen -W "types ${resources.join(' ')}" -- "$cur") )
58
+ elif [[ \${COMP_CWORD} -eq 3 ]]; then
59
+ COMPREPLY=( $(compgen -W "${crud}" -- "$cur") )
60
+ fi
61
+ ;;
62
+ db)
63
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
64
+ COMPREPLY=( $(compgen -W "${DB_PROXY_METHODS.join(' ')}" -- "$cur") )
65
+ fi
66
+ ;;
67
+ graph)
68
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
69
+ COMPREPLY=( $(compgen -W "${GRAPH_PROXY_METHODS.join(' ')}" -- "$cur") )
70
+ fi
71
+ ;;
72
+ profiles|workspaces)
73
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
74
+ COMPREPLY=( $(compgen -W "list use" -- "$cur") )
75
+ fi
76
+ ;;
77
+ generate)
78
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
79
+ COMPREPLY=( $(compgen -W "payload snippet" -- "$cur") )
80
+ fi
81
+ ;;
82
+ esac
83
+ }
84
+ complete -F _ductape_completions ductape
85
+ `);
86
+ return;
87
+ }
88
+ if (shell === 'zsh') {
89
+ console.log(`
90
+ #compdef ductape
91
+ _ductape() {
92
+ local -a commands
93
+ commands=(
94
+ ${TOP_LEVEL.map((c) => `'${c}:command'`).join('\n ')}
95
+ )
96
+ _arguments '1: :->cmd' '*: :->args'
97
+ case $state in
98
+ cmd) _describe 'command' commands ;;
99
+ args)
100
+ case $words[1] in
101
+ resources) _arguments '2: :(types ${resources.join(' ')})' '3: :(${crud.split(' ').join(' ')})' ;;
102
+ db) _arguments '2: :(${DB_PROXY_METHODS.join(' ')})' ;;
103
+ graph) _arguments '2: :(${GRAPH_PROXY_METHODS.join(' ')})' ;;
104
+ esac
105
+ ;;
106
+ esac
107
+ }
108
+ _ductape
109
+ `);
110
+ return;
111
+ }
112
+ console.log(`# Fish: use ductape completion bash | source`);
113
+ }
@@ -0,0 +1,5 @@
1
+ export declare function runDb(method: string, opts: {
2
+ file?: string;
3
+ json?: boolean;
4
+ }): Promise<void>;
5
+ export declare function runDbContext(json: boolean): void;
@@ -0,0 +1,55 @@
1
+ import { readJsonBody } from '../lib/read-body.js';
2
+ import { setDatabaseContext } from '../lib/context-store.js';
3
+ import { DB_PROXY_METHODS } from '../lib/db-methods.js';
4
+ import { getDbContext, getDbProxy, requireSession } from '../lib/proxy/context.js';
5
+ import { loadRuntimeContext } from '../lib/context-store.js';
6
+ import { printJson } from '../lib/output.js';
7
+ export async function runDb(method, opts) {
8
+ const session = requireSession();
9
+ const proxy = getDbProxy(session);
10
+ const runtime = loadRuntimeContext(session.project.workspace_id, session.project.product_tag);
11
+ const dbContext = runtime?.database_context ?? null;
12
+ const body = readJsonBody(opts.file);
13
+ const m = method.toLowerCase();
14
+ if (!DB_PROXY_METHODS.includes(m) && !m.includes('.')) {
15
+ console.warn(`Method "${m}" not in known list; sending anyway.`);
16
+ }
17
+ let params = [];
18
+ if (m === 'connect') {
19
+ const cfg = {
20
+ product: session.project.product_tag,
21
+ env: session.project.env_slug,
22
+ database: body.database ?? body.tag,
23
+ ...body,
24
+ };
25
+ params = [cfg];
26
+ }
27
+ else if (m === 'list') {
28
+ params = [session.project.product_tag];
29
+ }
30
+ else if (Object.keys(body).length > 0) {
31
+ params = [
32
+ {
33
+ product: session.project.product_tag,
34
+ env: session.project.env_slug,
35
+ ...body,
36
+ },
37
+ ];
38
+ }
39
+ const result = await proxy.execute(m, params, dbContext);
40
+ if (m === 'connect' && params[0] && typeof params[0] === 'object') {
41
+ const cfg = params[0];
42
+ setDatabaseContext(session.project.workspace_id, session.project.product_tag, session.project.env_slug, {
43
+ database: String(cfg.database),
44
+ env: String(cfg.env),
45
+ product: String(cfg.product),
46
+ });
47
+ }
48
+ printJson(result, Boolean(opts.json));
49
+ }
50
+ export function runDbContext(json) {
51
+ const session = requireSession();
52
+ const ctx = getDbContext(session);
53
+ const runtime = loadRuntimeContext(session.project.workspace_id, session.project.product_tag);
54
+ printJson({ database_context: runtime?.database_context ?? ctx }, json);
55
+ }
@@ -0,0 +1,14 @@
1
+ export declare function runGeneratePayload(opts: {
2
+ family: string;
3
+ method: string;
4
+ file?: string;
5
+ json?: boolean;
6
+ }): Promise<void>;
7
+ export declare function runGenerateSnippet(opts: {
8
+ family: string;
9
+ method: string;
10
+ language: 'typescript' | 'python' | 'go' | 'dotnet';
11
+ file?: string;
12
+ json?: boolean;
13
+ out?: string;
14
+ }): Promise<void>;
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs';
2
+ import { requireSession } from '../lib/proxy/context.js';
3
+ import { generateExecutablePayload } from '../lib/integrations.js';
4
+ import { buildSnippet, ensureSupportedSnippetOperation, } from '../lib/snippets.js';
5
+ import { printJson } from '../lib/output.js';
6
+ export async function runGeneratePayload(opts) {
7
+ const session = requireSession();
8
+ let targets;
9
+ if (opts.file) {
10
+ targets = JSON.parse(fs.readFileSync(opts.file, 'utf8'));
11
+ }
12
+ const result = await generateExecutablePayload(session.apiUrl, session.credentials, {
13
+ workspace_id: session.project.workspace_id,
14
+ user_id: session.credentials.user_id,
15
+ public_key: session.credentials.public_key,
16
+ product_tag: session.project.product_tag,
17
+ env_slug: session.project.env_slug,
18
+ operation_family: opts.family,
19
+ method: opts.method,
20
+ targets,
21
+ include_session: true,
22
+ include_cache: true,
23
+ schema_mode: 'best_effort',
24
+ });
25
+ printJson(result, Boolean(opts.json));
26
+ }
27
+ export async function runGenerateSnippet(opts) {
28
+ ensureSupportedSnippetOperation(opts.family, opts.method);
29
+ const session = requireSession();
30
+ let targets;
31
+ if (opts.file) {
32
+ targets = JSON.parse(fs.readFileSync(opts.file, 'utf8'));
33
+ }
34
+ const generated = await generateExecutablePayload(session.apiUrl, session.credentials, {
35
+ workspace_id: session.project.workspace_id,
36
+ user_id: session.credentials.user_id,
37
+ public_key: session.credentials.public_key,
38
+ product_tag: session.project.product_tag,
39
+ env_slug: session.project.env_slug,
40
+ operation_family: opts.family,
41
+ method: opts.method,
42
+ targets,
43
+ });
44
+ const payload = generated.payload ?? {};
45
+ const snippet = buildSnippet(opts.language, payload, opts.family, opts.method);
46
+ const output = { payload: generated, snippet };
47
+ if (opts.out) {
48
+ fs.writeFileSync(opts.out, snippet);
49
+ console.log(`Wrote snippet to ${opts.out}`);
50
+ }
51
+ printJson(output, Boolean(opts.json));
52
+ }
@@ -0,0 +1,4 @@
1
+ export declare function runGraph(method: string, opts: {
2
+ file?: string;
3
+ json?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { readJsonBody } from '../lib/read-body.js';
2
+ import { setGraphContext } from '../lib/context-store.js';
3
+ import { GRAPH_PROXY_METHODS } from '../lib/db-methods.js';
4
+ import { getGraphProxy, requireSession } from '../lib/proxy/context.js';
5
+ import { printJson } from '../lib/output.js';
6
+ export async function runGraph(method, opts) {
7
+ const session = requireSession();
8
+ const proxy = getGraphProxy(session);
9
+ const body = readJsonBody(opts.file);
10
+ const m = method.includes('.') ? method : method.toLowerCase();
11
+ if (!GRAPH_PROXY_METHODS.includes(m) && !m.includes('.')) {
12
+ console.warn(`Method "${m}" not in known list; sending anyway. Known: ${GRAPH_PROXY_METHODS.join(', ')}`);
13
+ }
14
+ const params = [];
15
+ if (m === 'connect') {
16
+ const cfg = {
17
+ product: session.project.product_tag,
18
+ env: session.project.env_slug,
19
+ graph: body.graph ?? body.tag,
20
+ ...body,
21
+ };
22
+ params.push(cfg);
23
+ }
24
+ else if (Object.keys(body).length > 0) {
25
+ params.push({
26
+ product: session.project.product_tag,
27
+ env: session.project.env_slug,
28
+ ...body,
29
+ });
30
+ }
31
+ else if (m === 'fetchAll' || m === 'list') {
32
+ params.push(session.project.product_tag);
33
+ }
34
+ const result = await proxy.execute(m === 'list' ? 'fetchAll' : m, params);
35
+ if (m === 'connect' && params[0] && typeof params[0] === 'object') {
36
+ const cfg = params[0];
37
+ setGraphContext(session.project.workspace_id, session.project.product_tag, session.project.env_slug, {
38
+ graph: String(cfg.graph),
39
+ env: String(cfg.env),
40
+ product: String(cfg.product),
41
+ });
42
+ }
43
+ printJson(result, Boolean(opts.json));
44
+ }
@@ -0,0 +1,7 @@
1
+ export declare function runInit(opts: {
2
+ link?: boolean;
3
+ workspace?: string;
4
+ product?: string;
5
+ env?: string;
6
+ language?: string;
7
+ }): Promise<void>;