@runfusion/fusion 0.26.0 → 0.27.1

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 (174) hide show
  1. package/dist/bin.js +12847 -2514
  2. package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
  3. package/dist/client/assets/AgentDetailView-shgiiUb4.js +18 -0
  4. package/dist/client/assets/{AgentsView-CV3vm7Qk.css → AgentsView-B3ADnF0D.css} +1 -1
  5. package/dist/client/assets/{AgentsView-D6Zi5zfP.js → AgentsView-CpwqOVDz.js} +12 -7
  6. package/dist/client/assets/ChatView-DyRBOIKL.js +1 -0
  7. package/dist/client/assets/{DevServerView--_WBvIDQ.js → DevServerView-Cdelj9-m.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-xedtR-Rd.js → DirectoryPicker-C0kmRv0u.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-Bg2oaZks.js → DocumentsView-B94U9ijs.js} +1 -1
  10. package/dist/client/assets/{EvalsView-B3uOCXfr.js → EvalsView-O_4YWy--.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-Bx6yXVS5.js → ExperimentalAgentOnboardingModal-CkEiF85-.js} +1 -1
  12. package/dist/client/assets/InsightsView-D-Qe0tRr.js +11 -0
  13. package/dist/client/assets/{MemoryView-xcN_eouf.js → MemoryView-CoRUmRvb.js} +2 -2
  14. package/dist/client/assets/NodesView-DQzXjcLc.js +14 -0
  15. package/dist/client/assets/{PiExtensionsManager-Cc8aAZXg.js → PiExtensionsManager-Dn1LmFbq.js} +2 -2
  16. package/dist/client/assets/PluginManager-Y0fs-6No.js +1 -0
  17. package/dist/client/assets/{ResearchView-CERNf7sJ.js → ResearchView-CjOxKhdS.js} +1 -1
  18. package/dist/client/assets/{SettingsModal-B1r0yASu.js → SettingsModal-Bg1-3JO_.js} +1 -1
  19. package/dist/client/assets/{SettingsModal-Cis-4Lot.css → SettingsModal-Ci0_sqbU.css} +1 -1
  20. package/dist/client/assets/SettingsModal-DL7tjJQa.js +31 -0
  21. package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
  22. package/dist/client/assets/{SetupWizardModal-D1q548_L.js → SetupWizardModal-DuzYPbuJ.js} +1 -1
  23. package/dist/client/assets/{SkillsView-ClLM6u6p.js → SkillsView-BIFoVNUf.js} +1 -1
  24. package/dist/client/assets/{StashRecoveryView-ze0pEZ5U.js → StashRecoveryView-C52KsV7f.js} +1 -1
  25. package/dist/client/assets/{TodoView-CTmIfy2M.js → TodoView-sS_mT0Y7.js} +2 -2
  26. package/dist/client/assets/{dashboard-view-CyWN-d02.js → dashboard-view-BWGH_fAq.js} +1 -1
  27. package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
  28. package/dist/client/assets/dashboard-view-MB-86hAu.js +21 -0
  29. package/dist/client/assets/{folder-open-BZuKESeq.js → folder-open-B9cwJ-OX.js} +1 -1
  30. package/dist/client/assets/index-BOjPRqEk.js +692 -0
  31. package/dist/client/assets/index-BmSEq8Rb.css +1 -0
  32. package/dist/client/assets/{star-D75YKEq-.js → star-BDn04UYV.js} +1 -1
  33. package/dist/client/assets/{upload-BYYTgWFj.js → upload-zdPPycKQ.js} +1 -1
  34. package/dist/client/assets/{users-RS90Aii3.js → users-CPYZjK2g.js} +1 -1
  35. package/dist/client/index.html +2 -2
  36. package/dist/client/version.json +1 -1
  37. package/dist/droid-cli/package.json +1 -1
  38. package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
  39. package/dist/extension.js +7433 -1920
  40. package/dist/pi-claude-cli/package.json +1 -1
  41. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
  42. package/dist/pi-claude-cli/src/provider.ts +7 -1
  43. package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +19 -1
  44. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +20 -2
  45. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
  46. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
  47. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
  48. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
  49. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
  50. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
  51. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
  52. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
  53. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
  54. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
  55. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +21 -5
  56. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
  57. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
  58. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
  59. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
  60. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
  61. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
  62. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
  63. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
  64. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
  65. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
  66. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
  67. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
  68. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
  69. package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +46 -2
  70. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
  71. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
  72. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
  73. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
  74. package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
  75. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
  76. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
  77. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
  78. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
  79. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
  80. package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
  81. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
  82. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
  83. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
  84. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
  85. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
  86. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
  87. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
  88. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
  89. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  90. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  91. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  92. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  93. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
  96. package/dist/plugins/fusion-plugin-reports/package.json +18 -2
  97. package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
  98. package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
  99. package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
  100. package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
  101. package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
  102. package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
  103. package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
  104. package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
  105. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
  106. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
  107. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
  108. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
  109. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
  110. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
  111. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
  112. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
  113. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
  114. package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
  115. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
  116. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
  117. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
  118. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
  119. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
  120. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
  121. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
  122. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
  123. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
  124. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
  125. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
  126. package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
  127. package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
  128. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
  129. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
  130. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
  131. package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
  132. package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
  133. package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
  134. package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
  135. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
  136. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
  137. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
  138. package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
  139. package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
  140. package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
  141. package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
  142. package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
  143. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
  144. package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
  145. package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
  146. package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
  147. package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
  148. package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
  149. package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
  150. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
  151. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
  152. package/dist/plugins/fusion-plugin-roadmap/bundled.js +1528 -29391
  153. package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
  154. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  155. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  156. package/package.json +1 -1
  157. package/skill/fusion/SKILL.md +1 -1
  158. package/skill/fusion/references/engine-tools.md +2 -2
  159. package/skill/fusion/references/extension-tools.md +4 -3
  160. package/skill/fusion/references/fusion-capabilities.md +1 -1
  161. package/skill/fusion/workflows/task-management.md +3 -1
  162. package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
  163. package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +0 -18
  164. package/dist/client/assets/ChatView-CAHjY9uO.js +0 -1
  165. package/dist/client/assets/InsightsView-Q1zvtF4F.js +0 -11
  166. package/dist/client/assets/NodesView-RxXg58_Q.js +0 -14
  167. package/dist/client/assets/PluginManager-BEkyBajl.js +0 -1
  168. package/dist/client/assets/SettingsModal-BLsac7CJ.js +0 -31
  169. package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
  170. package/dist/client/assets/dashboard-view-4xAN3yO5.js +0 -21
  171. package/dist/client/assets/dashboard-view-BkTMSZYn.css +0 -1
  172. package/dist/client/assets/index-Bdw6llW6.js +0 -692
  173. package/dist/client/assets/index-CZGlyJuS.css +0 -1
  174. package/dist/plugins/fusion-plugin-roadmap/bundled.css +0 -1093
@@ -0,0 +1,427 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Database } from "@fusion/core";
3
+ import {
4
+ InvalidCredentialPlacementError,
5
+ OAuthNotSupportedError,
6
+ type CliArtifact,
7
+ type CliArtifactCreateInput,
8
+ type CliArtifactUpdateInput,
9
+ type CliSpec,
10
+ type CliSpecCreateInput,
11
+ type CliSpecUpdateInput,
12
+ type Credential,
13
+ type CredentialCreateInput,
14
+ type CredentialUpdateInput,
15
+ type Service,
16
+ type ServiceCreateInput,
17
+ type ServiceSetting,
18
+ type ServiceSettingCreateInput,
19
+ type ServiceUpdateInput,
20
+ } from "./cli-press-types.js";
21
+
22
+ interface ServiceRow {
23
+ id: string;
24
+ slug: string;
25
+ displayName: string;
26
+ description: string | null;
27
+ baseUrl: string;
28
+ sourceKind: Service["sourceKind"];
29
+ sourceRef: string | null;
30
+ createdAt: string;
31
+ updatedAt: string;
32
+ }
33
+
34
+ interface CliSpecRow {
35
+ id: string;
36
+ serviceId: string;
37
+ name: string;
38
+ version: string;
39
+ generatorVersion: string;
40
+ specJson: string;
41
+ generatedAt: string | null;
42
+ status: CliSpec["status"];
43
+ lastGenerationError: string | null;
44
+ createdAt: string;
45
+ updatedAt: string;
46
+ }
47
+
48
+ interface CliArtifactRow {
49
+ id: string;
50
+ cliSpecId: string;
51
+ kind: CliArtifact["kind"];
52
+ path: string;
53
+ executable: number;
54
+ checksum: string | null;
55
+ sizeBytes: number | null;
56
+ createdAt: string;
57
+ updatedAt: string;
58
+ }
59
+
60
+ interface CredentialRow {
61
+ id: string;
62
+ serviceId: string;
63
+ name: string;
64
+ kind: Credential["kind"];
65
+ value: string;
66
+ placement: string;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ }
70
+
71
+ interface ServiceSettingRow {
72
+ id: string;
73
+ serviceId: string;
74
+ key: string;
75
+ value: string;
76
+ scope: ServiceSetting["scope"];
77
+ createdAt: string;
78
+ updatedAt: string;
79
+ }
80
+
81
+ const OAUTH_KINDS = new Set(["oauth", "oauth2"]);
82
+
83
+ function parseJson<T>(value: string): T {
84
+ return JSON.parse(value) as T;
85
+ }
86
+
87
+ function nowIso(): string {
88
+ return new Date().toISOString();
89
+ }
90
+
91
+ function createId(prefix: "svc" | "cli" | "art" | "cred" | "set"): string {
92
+ return `${prefix}_${randomUUID()}`;
93
+ }
94
+
95
+ function assertCredentialSupported(kind: string): void {
96
+ if (OAUTH_KINDS.has(kind)) {
97
+ throw new OAuthNotSupportedError(kind);
98
+ }
99
+ }
100
+
101
+ function assertPlacementConsistency(
102
+ kind: Credential["kind"] | string,
103
+ placement: Credential["placement"] | { kind?: string; header?: string; queryParam?: string },
104
+ ): void {
105
+ const placementKind = (placement as { kind?: string }).kind;
106
+ if (placementKind !== kind) {
107
+ throw new InvalidCredentialPlacementError({ credentialKind: kind, placementKind: String(placementKind) });
108
+ }
109
+ if (kind === "api_key") {
110
+ const candidate = placement as { kind?: string; header?: string; queryParam?: string };
111
+ const hasHeader = typeof candidate.header === "string" && candidate.header.trim().length > 0;
112
+ const hasQuery = typeof candidate.queryParam === "string" && candidate.queryParam.trim().length > 0;
113
+ if ((hasHeader ? 1 : 0) + (hasQuery ? 1 : 0) !== 1) {
114
+ throw new InvalidCredentialPlacementError({ credentialKind: kind, placementKind: String(placementKind) });
115
+ }
116
+ }
117
+ }
118
+
119
+ export function ensureCliPressSchema(db: Database): void {
120
+ db.exec(`
121
+ CREATE TABLE IF NOT EXISTS cli_press_services (
122
+ id TEXT PRIMARY KEY,
123
+ slug TEXT NOT NULL UNIQUE,
124
+ displayName TEXT NOT NULL,
125
+ description TEXT,
126
+ baseUrl TEXT NOT NULL,
127
+ sourceKind TEXT NOT NULL,
128
+ sourceRef TEXT,
129
+ createdAt TEXT NOT NULL,
130
+ updatedAt TEXT NOT NULL
131
+ );
132
+
133
+ CREATE TABLE IF NOT EXISTS cli_press_cli_specs (
134
+ id TEXT PRIMARY KEY,
135
+ serviceId TEXT NOT NULL,
136
+ name TEXT NOT NULL,
137
+ version TEXT NOT NULL,
138
+ generatorVersion TEXT NOT NULL,
139
+ specJson TEXT NOT NULL,
140
+ generatedAt TEXT,
141
+ status TEXT NOT NULL,
142
+ lastGenerationError TEXT,
143
+ createdAt TEXT NOT NULL,
144
+ updatedAt TEXT NOT NULL,
145
+ FOREIGN KEY (serviceId) REFERENCES cli_press_services(id) ON DELETE CASCADE,
146
+ UNIQUE(serviceId, name)
147
+ );
148
+
149
+ CREATE TABLE IF NOT EXISTS cli_press_artifacts (
150
+ id TEXT PRIMARY KEY,
151
+ cliSpecId TEXT NOT NULL,
152
+ kind TEXT NOT NULL,
153
+ path TEXT NOT NULL,
154
+ executable INTEGER NOT NULL,
155
+ checksum TEXT,
156
+ sizeBytes INTEGER,
157
+ createdAt TEXT NOT NULL,
158
+ updatedAt TEXT NOT NULL,
159
+ FOREIGN KEY (cliSpecId) REFERENCES cli_press_cli_specs(id) ON DELETE CASCADE
160
+ );
161
+
162
+ CREATE TABLE IF NOT EXISTS cli_press_credentials (
163
+ id TEXT PRIMARY KEY,
164
+ serviceId TEXT NOT NULL,
165
+ name TEXT NOT NULL,
166
+ kind TEXT NOT NULL,
167
+ value TEXT NOT NULL,
168
+ placement TEXT NOT NULL,
169
+ createdAt TEXT NOT NULL,
170
+ updatedAt TEXT NOT NULL,
171
+ FOREIGN KEY (serviceId) REFERENCES cli_press_services(id) ON DELETE CASCADE,
172
+ UNIQUE(serviceId, name)
173
+ );
174
+
175
+ CREATE TABLE IF NOT EXISTS cli_press_service_settings (
176
+ id TEXT PRIMARY KEY,
177
+ serviceId TEXT NOT NULL,
178
+ key TEXT NOT NULL,
179
+ value TEXT NOT NULL,
180
+ scope TEXT NOT NULL,
181
+ createdAt TEXT NOT NULL,
182
+ updatedAt TEXT NOT NULL,
183
+ FOREIGN KEY (serviceId) REFERENCES cli_press_services(id) ON DELETE CASCADE,
184
+ UNIQUE(serviceId, key, scope)
185
+ );
186
+
187
+ CREATE INDEX IF NOT EXISTS idx_cli_press_specs_service ON cli_press_cli_specs(serviceId);
188
+ CREATE INDEX IF NOT EXISTS idx_cli_press_artifacts_spec ON cli_press_artifacts(cliSpecId);
189
+ CREATE INDEX IF NOT EXISTS idx_cli_press_credentials_service ON cli_press_credentials(serviceId);
190
+ CREATE INDEX IF NOT EXISTS idx_cli_press_settings_service ON cli_press_service_settings(serviceId);
191
+ `);
192
+ }
193
+
194
+ export function createCliPressStore(db: Database) {
195
+ ensureCliPressSchema(db);
196
+
197
+ const mapService = (row: ServiceRow): Service => ({
198
+ id: row.id,
199
+ slug: row.slug,
200
+ displayName: row.displayName,
201
+ description: row.description ?? undefined,
202
+ baseUrl: row.baseUrl,
203
+ sourceKind: row.sourceKind,
204
+ sourceRef: row.sourceRef ?? undefined,
205
+ createdAt: row.createdAt,
206
+ updatedAt: row.updatedAt,
207
+ });
208
+
209
+ const mapSpec = (row: CliSpecRow): CliSpec => ({
210
+ id: row.id,
211
+ serviceId: row.serviceId,
212
+ name: row.name,
213
+ version: row.version,
214
+ generatorVersion: row.generatorVersion,
215
+ specJson: row.specJson,
216
+ generatedAt: row.generatedAt ?? undefined,
217
+ status: row.status,
218
+ lastGenerationError: row.lastGenerationError ?? undefined,
219
+ createdAt: row.createdAt,
220
+ updatedAt: row.updatedAt,
221
+ });
222
+
223
+ const mapArtifact = (row: CliArtifactRow): CliArtifact => ({
224
+ id: row.id,
225
+ cliSpecId: row.cliSpecId,
226
+ kind: row.kind,
227
+ path: row.path,
228
+ executable: Boolean(row.executable),
229
+ checksum: row.checksum ?? undefined,
230
+ sizeBytes: row.sizeBytes ?? undefined,
231
+ createdAt: row.createdAt,
232
+ updatedAt: row.updatedAt,
233
+ });
234
+
235
+ const mapCredential = (row: CredentialRow): Credential => ({
236
+ id: row.id,
237
+ serviceId: row.serviceId,
238
+ name: row.name,
239
+ kind: row.kind,
240
+ value: parseJson(row.value),
241
+ placement: parseJson(row.placement),
242
+ createdAt: row.createdAt,
243
+ updatedAt: row.updatedAt,
244
+ });
245
+
246
+ const mapSetting = (row: ServiceSettingRow): ServiceSetting => ({
247
+ id: row.id,
248
+ serviceId: row.serviceId,
249
+ key: row.key,
250
+ value: row.value,
251
+ scope: row.scope,
252
+ createdAt: row.createdAt,
253
+ updatedAt: row.updatedAt,
254
+ });
255
+
256
+ return {
257
+ listServices(): Service[] {
258
+ const rows = db.prepare("SELECT * FROM cli_press_services ORDER BY createdAt DESC").all() as unknown as ServiceRow[];
259
+ return rows.map(mapService);
260
+ },
261
+
262
+ getService(id: string): Service | undefined {
263
+ const row = db.prepare("SELECT * FROM cli_press_services WHERE id = ?").get(id) as unknown as ServiceRow | undefined;
264
+ return row ? mapService(row) : undefined;
265
+ },
266
+
267
+ createService(input: ServiceCreateInput): Service {
268
+ const service: Service = { id: createId("svc"), ...input, createdAt: nowIso(), updatedAt: nowIso() };
269
+ db.prepare(`INSERT INTO cli_press_services (id, slug, displayName, description, baseUrl, sourceKind, sourceRef, createdAt, updatedAt)
270
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
271
+ .run(service.id, service.slug, service.displayName, service.description ?? null, service.baseUrl, service.sourceKind, service.sourceRef ?? null, service.createdAt, service.updatedAt);
272
+ db.bumpLastModified();
273
+ return service;
274
+ },
275
+
276
+ updateService(id: string, updates: ServiceUpdateInput): Service {
277
+ const existing = this.getService(id);
278
+ if (!existing) throw new Error(`Service ${id} not found`);
279
+ const updated: Service = { ...existing, ...updates, id: existing.id, slug: existing.slug, createdAt: existing.createdAt, updatedAt: nowIso() };
280
+ db.prepare(`UPDATE cli_press_services SET displayName = ?, description = ?, baseUrl = ?, sourceKind = ?, sourceRef = ?, updatedAt = ? WHERE id = ?`)
281
+ .run(updated.displayName, updated.description ?? null, updated.baseUrl, updated.sourceKind, updated.sourceRef ?? null, updated.updatedAt, id);
282
+ db.bumpLastModified();
283
+ return updated;
284
+ },
285
+
286
+ deleteService(id: string): void {
287
+ db.transaction(() => {
288
+ db.prepare("DELETE FROM cli_press_services WHERE id = ?").run(id);
289
+ });
290
+ db.bumpLastModified();
291
+ },
292
+
293
+ listSpecs(serviceId: string): CliSpec[] {
294
+ const rows = db.prepare("SELECT * FROM cli_press_cli_specs WHERE serviceId = ? ORDER BY createdAt DESC").all(serviceId) as unknown as CliSpecRow[];
295
+ return rows.map(mapSpec);
296
+ },
297
+
298
+ getSpec(id: string): CliSpec | undefined {
299
+ const row = db.prepare("SELECT * FROM cli_press_cli_specs WHERE id = ?").get(id) as unknown as CliSpecRow | undefined;
300
+ return row ? mapSpec(row) : undefined;
301
+ },
302
+
303
+ createSpec(input: CliSpecCreateInput): CliSpec {
304
+ const spec: CliSpec = { id: createId("cli"), ...input, createdAt: nowIso(), updatedAt: nowIso() };
305
+ db.prepare(`INSERT INTO cli_press_cli_specs (id, serviceId, name, version, generatorVersion, specJson, generatedAt, status, lastGenerationError, createdAt, updatedAt)
306
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
307
+ .run(spec.id, spec.serviceId, spec.name, spec.version, spec.generatorVersion, spec.specJson, spec.generatedAt ?? null, spec.status, spec.lastGenerationError ?? null, spec.createdAt, spec.updatedAt);
308
+ db.bumpLastModified();
309
+ return spec;
310
+ },
311
+
312
+ updateSpec(id: string, updates: CliSpecUpdateInput): CliSpec {
313
+ const existing = this.getSpec(id);
314
+ if (!existing) throw new Error(`Spec ${id} not found`);
315
+ const updated: CliSpec = { ...existing, ...updates, id: existing.id, serviceId: existing.serviceId, createdAt: existing.createdAt, updatedAt: nowIso() };
316
+ db.prepare(`UPDATE cli_press_cli_specs SET name=?, version=?, generatorVersion=?, specJson=?, generatedAt=?, status=?, lastGenerationError=?, updatedAt=? WHERE id=?`)
317
+ .run(updated.name, updated.version, updated.generatorVersion, updated.specJson, updated.generatedAt ?? null, updated.status, updated.lastGenerationError ?? null, updated.updatedAt, id);
318
+ db.bumpLastModified();
319
+ return updated;
320
+ },
321
+
322
+ deleteSpec(id: string): void {
323
+ db.prepare("DELETE FROM cli_press_cli_specs WHERE id = ?").run(id);
324
+ db.bumpLastModified();
325
+ },
326
+
327
+ listArtifacts(specId: string): CliArtifact[] {
328
+ const rows = db.prepare("SELECT * FROM cli_press_artifacts WHERE cliSpecId = ? ORDER BY createdAt DESC").all(specId) as unknown as CliArtifactRow[];
329
+ return rows.map(mapArtifact);
330
+ },
331
+
332
+ createArtifact(input: CliArtifactCreateInput): CliArtifact {
333
+ const artifact: CliArtifact = { id: createId("art"), ...input, createdAt: nowIso(), updatedAt: nowIso() };
334
+ db.prepare(`INSERT INTO cli_press_artifacts (id, cliSpecId, kind, path, executable, checksum, sizeBytes, createdAt, updatedAt)
335
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
336
+ .run(artifact.id, artifact.cliSpecId, artifact.kind, artifact.path, artifact.executable ? 1 : 0, artifact.checksum ?? null, artifact.sizeBytes ?? null, artifact.createdAt, artifact.updatedAt);
337
+ db.bumpLastModified();
338
+ return artifact;
339
+ },
340
+
341
+ updateArtifact(id: string, updates: CliArtifactUpdateInput): CliArtifact {
342
+ const existing = db.prepare("SELECT * FROM cli_press_artifacts WHERE id = ?").get(id) as unknown as CliArtifactRow | undefined;
343
+ if (!existing) throw new Error(`Artifact ${id} not found`);
344
+ const updated = { ...mapArtifact(existing), ...updates, id: existing.id, cliSpecId: existing.cliSpecId, createdAt: existing.createdAt, updatedAt: nowIso() };
345
+ db.prepare("UPDATE cli_press_artifacts SET path=?, executable=?, checksum=?, sizeBytes=?, updatedAt=? WHERE id=?")
346
+ .run(updated.path, updated.executable ? 1 : 0, updated.checksum ?? null, updated.sizeBytes ?? null, updated.updatedAt, id);
347
+ db.bumpLastModified();
348
+ return updated;
349
+ },
350
+
351
+ deleteArtifact(id: string): void {
352
+ db.prepare("DELETE FROM cli_press_artifacts WHERE id = ?").run(id);
353
+ db.bumpLastModified();
354
+ },
355
+
356
+ listCredentials(serviceId: string): Credential[] {
357
+ const rows = db.prepare("SELECT * FROM cli_press_credentials WHERE serviceId = ? ORDER BY createdAt DESC").all(serviceId) as unknown as CredentialRow[];
358
+ return rows.map(mapCredential);
359
+ },
360
+
361
+ createCredential(input: CredentialCreateInput): Credential {
362
+ assertCredentialSupported(input.kind);
363
+ assertPlacementConsistency(input.kind, input.placement);
364
+ const cred: Credential = { id: createId("cred"), ...input, createdAt: nowIso(), updatedAt: nowIso() };
365
+ db.prepare(`INSERT INTO cli_press_credentials (id, serviceId, name, kind, value, placement, createdAt, updatedAt)
366
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
367
+ .run(cred.id, cred.serviceId, cred.name, cred.kind, JSON.stringify(cred.value), JSON.stringify(cred.placement), cred.createdAt, cred.updatedAt);
368
+ db.bumpLastModified();
369
+ return cred;
370
+ },
371
+
372
+ updateCredential(id: string, updates: CredentialUpdateInput): Credential {
373
+ const existing = db.prepare("SELECT * FROM cli_press_credentials WHERE id = ?").get(id) as unknown as CredentialRow | undefined;
374
+ if (!existing) throw new Error(`Credential ${id} not found`);
375
+ const mapped = mapCredential(existing);
376
+ const updated: Credential = {
377
+ ...mapped,
378
+ ...updates,
379
+ id: mapped.id,
380
+ serviceId: mapped.serviceId,
381
+ kind: mapped.kind,
382
+ createdAt: mapped.createdAt,
383
+ updatedAt: nowIso(),
384
+ };
385
+ assertCredentialSupported(updated.kind);
386
+ assertPlacementConsistency(updated.kind, updated.placement);
387
+ db.prepare("UPDATE cli_press_credentials SET name=?, value=?, placement=?, updatedAt=? WHERE id=?")
388
+ .run(updated.name, JSON.stringify(updated.value), JSON.stringify(updated.placement), updated.updatedAt, id);
389
+ db.bumpLastModified();
390
+ return updated;
391
+ },
392
+
393
+ deleteCredential(id: string): void {
394
+ db.prepare("DELETE FROM cli_press_credentials WHERE id = ?").run(id);
395
+ db.bumpLastModified();
396
+ },
397
+
398
+ listSettings(serviceId: string): ServiceSetting[] {
399
+ const rows = db.prepare("SELECT * FROM cli_press_service_settings WHERE serviceId = ? ORDER BY createdAt DESC").all(serviceId) as unknown as ServiceSettingRow[];
400
+ return rows.map(mapSetting);
401
+ },
402
+
403
+ setSetting(input: ServiceSettingCreateInput): ServiceSetting {
404
+ const existing = db.prepare("SELECT * FROM cli_press_service_settings WHERE serviceId = ? AND key = ? AND scope = ?")
405
+ .get(input.serviceId, input.key, input.scope) as unknown as ServiceSettingRow | undefined;
406
+ const now = nowIso();
407
+ if (existing) {
408
+ db.prepare("UPDATE cli_press_service_settings SET value = ?, updatedAt = ? WHERE id = ?").run(input.value, now, existing.id);
409
+ db.bumpLastModified();
410
+ return mapSetting({ ...existing, value: input.value, updatedAt: now });
411
+ }
412
+ const setting: ServiceSetting = { id: createId("set"), ...input, createdAt: now, updatedAt: now };
413
+ db.prepare(`INSERT INTO cli_press_service_settings (id, serviceId, key, value, scope, createdAt, updatedAt)
414
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
415
+ .run(setting.id, setting.serviceId, setting.key, setting.value, setting.scope, setting.createdAt, setting.updatedAt);
416
+ db.bumpLastModified();
417
+ return setting;
418
+ },
419
+
420
+ deleteSetting(id: string): void {
421
+ db.prepare("DELETE FROM cli_press_service_settings WHERE id = ?").run(id);
422
+ db.bumpLastModified();
423
+ },
424
+ };
425
+ }
426
+
427
+ export type CliPressStore = ReturnType<typeof createCliPressStore>;
@@ -0,0 +1,110 @@
1
+ export type ServiceSourceKind = "openapi" | "manual" | "other";
2
+
3
+ export type CredentialKind = "api_key" | "bearer_token" | "basic_auth" | "header" | "query_param" | "env_var";
4
+
5
+ export type CredentialPlacement =
6
+ | { kind: "header"; header: string }
7
+ | { kind: "query_param"; queryParam: string }
8
+ | { kind: "env_var"; envVar: string }
9
+ | { kind: "bearer_token"; header: string }
10
+ | { kind: "api_key"; header?: string; queryParam?: string }
11
+ | { kind: "basic_auth"; header: string };
12
+
13
+ export type ServiceSettingScope = "runtime" | "wizard" | "metadata";
14
+
15
+ export interface Service {
16
+ id: string;
17
+ slug: string;
18
+ displayName: string;
19
+ description?: string;
20
+ baseUrl: string;
21
+ sourceKind: ServiceSourceKind;
22
+ sourceRef?: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ }
26
+
27
+ export interface CliSpec {
28
+ id: string;
29
+ serviceId: string;
30
+ name: string;
31
+ version: string;
32
+ generatorVersion: string;
33
+ specJson: string;
34
+ generatedAt?: string;
35
+ status: "draft" | "generated" | "failed";
36
+ lastGenerationError?: string;
37
+ createdAt: string;
38
+ updatedAt: string;
39
+ }
40
+
41
+ export interface CliArtifact {
42
+ id: string;
43
+ cliSpecId: string;
44
+ kind: "binary" | "script" | "package";
45
+ path: string;
46
+ executable: boolean;
47
+ checksum?: string;
48
+ sizeBytes?: number;
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ }
52
+
53
+ export interface EncodedCredentialValue {
54
+ encoding: "base64";
55
+ value: string;
56
+ }
57
+
58
+ export interface Credential {
59
+ id: string;
60
+ serviceId: string;
61
+ name: string;
62
+ kind: CredentialKind;
63
+ value: EncodedCredentialValue;
64
+ placement: CredentialPlacement;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ }
68
+
69
+ export interface ServiceSetting {
70
+ id: string;
71
+ serviceId: string;
72
+ key: string;
73
+ value: string;
74
+ scope: ServiceSettingScope;
75
+ createdAt: string;
76
+ updatedAt: string;
77
+ }
78
+
79
+ export class OAuthNotSupportedError extends Error {
80
+ constructor(kind: string) {
81
+ super(`Credential kind \"${kind}\" is not supported. OAuth/OAuth2 are deferred.`);
82
+ this.name = "OAuthNotSupportedError";
83
+ }
84
+ }
85
+
86
+ export class InvalidCredentialPlacementError extends Error {
87
+ constructor(input: { credentialKind: string; placementKind: string }) {
88
+ super(
89
+ `Invalid credential placement: credential kind \"${input.credentialKind}\" does not match placement kind \"${input.placementKind}\".`,
90
+ );
91
+ this.name = "InvalidCredentialPlacementError";
92
+ }
93
+ }
94
+
95
+ export type ServiceCreateInput = Omit<Service, "id" | "createdAt" | "updatedAt">;
96
+ export type ServiceUpdateInput = Partial<Pick<Service, "displayName" | "description" | "baseUrl" | "sourceKind" | "sourceRef">>;
97
+
98
+ export type CliSpecCreateInput = Omit<CliSpec, "id" | "createdAt" | "updatedAt">;
99
+ export type CliSpecUpdateInput = Partial<
100
+ Pick<CliSpec, "name" | "version" | "generatorVersion" | "specJson" | "status" | "lastGenerationError" | "generatedAt">
101
+ >;
102
+
103
+ export type CliArtifactCreateInput = Omit<CliArtifact, "id" | "createdAt" | "updatedAt">;
104
+ export type CliArtifactUpdateInput = Partial<Pick<CliArtifact, "path" | "executable" | "checksum" | "sizeBytes">>;
105
+
106
+ export type CredentialCreateInput = Omit<Credential, "id" | "createdAt" | "updatedAt">;
107
+ export type CredentialUpdateInput = Partial<Pick<Credential, "name" | "value" | "placement">>;
108
+
109
+ export type ServiceSettingCreateInput = Omit<ServiceSetting, "id" | "createdAt" | "updatedAt">;
110
+ export type ServiceSettingUpdateInput = Pick<ServiceSetting, "value">;
@@ -0,0 +1,95 @@
1
+ import { Buffer } from "node:buffer";
2
+ import type { Credential, EncodedCredentialValue } from "./cli-press-types.js";
3
+ import { InvalidCredentialPlacementError, OAuthNotSupportedError } from "./cli-press-types.js";
4
+
5
+ export type RequestShape = {
6
+ headers: Record<string, string>;
7
+ query: Record<string, string>;
8
+ env: Record<string, string>;
9
+ };
10
+
11
+ export function encodeCredentialValue(raw: string): EncodedCredentialValue {
12
+ return {
13
+ encoding: "base64",
14
+ value: Buffer.from(raw, "utf8").toString("base64"),
15
+ };
16
+ }
17
+
18
+ export function decodeCredentialValue(encoded: EncodedCredentialValue): string {
19
+ if (encoded.encoding !== "base64") {
20
+ throw new Error(`Unsupported credential encoding: ${String((encoded as { encoding?: string }).encoding)}`);
21
+ }
22
+ return Buffer.from(encoded.value, "base64").toString("utf8");
23
+ }
24
+
25
+ function assertNoOAuth(kind: string): void {
26
+ if (kind === "oauth" || kind === "oauth2") {
27
+ throw new OAuthNotSupportedError(kind);
28
+ }
29
+ }
30
+
31
+ function assertPlacement(credential: Credential): void {
32
+ const credentialKind = credential.kind;
33
+ const placementKind = credential.placement.kind;
34
+ if (placementKind !== credentialKind) {
35
+ throw new InvalidCredentialPlacementError({ credentialKind, placementKind });
36
+ }
37
+ if (credential.kind === "api_key") {
38
+ const placement = credential.placement;
39
+ if (placement.kind !== "api_key") {
40
+ throw new InvalidCredentialPlacementError({ credentialKind, placementKind });
41
+ }
42
+ const hasHeader = typeof placement.header === "string" && placement.header.trim().length > 0;
43
+ const hasQuery = typeof placement.queryParam === "string" && placement.queryParam.trim().length > 0;
44
+ if ((hasHeader ? 1 : 0) + (hasQuery ? 1 : 0) !== 1) {
45
+ throw new InvalidCredentialPlacementError({ credentialKind, placementKind });
46
+ }
47
+ }
48
+ }
49
+
50
+ export function applyCredentialToRequest(credential: Credential, request: RequestShape): RequestShape {
51
+ assertNoOAuth((credential as { kind: string }).kind);
52
+ assertPlacement(credential);
53
+
54
+ const value = decodeCredentialValue(credential.value);
55
+
56
+ switch (credential.kind) {
57
+ case "header": {
58
+ const placement = credential.placement as { kind: "header"; header: string };
59
+ request.headers[placement.header] = value;
60
+ break;
61
+ }
62
+ case "query_param": {
63
+ const placement = credential.placement as { kind: "query_param"; queryParam: string };
64
+ request.query[placement.queryParam] = value;
65
+ break;
66
+ }
67
+ case "env_var": {
68
+ const placement = credential.placement as { kind: "env_var"; envVar: string };
69
+ request.env[placement.envVar] = value;
70
+ break;
71
+ }
72
+ case "bearer_token": {
73
+ const placement = credential.placement as { kind: "bearer_token"; header: string };
74
+ request.headers[placement.header] = `Bearer ${value}`;
75
+ break;
76
+ }
77
+ case "api_key": {
78
+ const placement = credential.placement as { kind: "api_key"; header?: string; queryParam?: string };
79
+ if (placement.header) {
80
+ request.headers[placement.header] = value;
81
+ } else if (placement.queryParam) {
82
+ request.query[placement.queryParam] = value;
83
+ }
84
+ break;
85
+ }
86
+ case "basic_auth": {
87
+ const placement = credential.placement as { kind: "basic_auth"; header: string };
88
+ const basicAuth = Buffer.from(value, "utf8").toString("base64");
89
+ request.headers[placement.header] = `Basic ${basicAuth}`;
90
+ break;
91
+ }
92
+ }
93
+
94
+ return request;
95
+ }
@@ -0,0 +1,55 @@
1
+ import type { ServiceDraft } from "./types.js";
2
+
3
+ export function BasicsStep({ draft, onChange }: { draft: ServiceDraft; onChange: (patch: Partial<ServiceDraft>) => void }) {
4
+ return (
5
+ <div className="form-group">
6
+ <label>Name</label>
7
+ <input aria-label="Name" className="input" value={draft.name} onChange={(e) => onChange({ name: e.target.value })} />
8
+ <label>Slug</label>
9
+ <input aria-label="Slug" className="input" value={draft.slug} onChange={(e) => onChange({ slug: e.target.value })} />
10
+ <label>Description</label>
11
+ <input aria-label="Description" className="input" value={draft.description} onChange={(e) => onChange({ description: e.target.value })} />
12
+ <label>Base URL</label>
13
+ <input aria-label="Base URL" className="input" value={draft.baseUrl} onChange={(e) => onChange({ baseUrl: e.target.value })} />
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export function TransportStep() {
19
+ return <div className="card"><p>Transport</p><input className="input" value="HTTP" readOnly disabled /><p>Other transports land in follow-up tasks.</p></div>;
20
+ }
21
+
22
+ export function EndpointsStep({ draft, onChange }: { draft: ServiceDraft; onChange: (endpoints: ServiceDraft["endpoints"]) => void }) {
23
+ return <div className="cli-press-endpoint-list">{draft.endpoints.map((endpoint) => <div className="card cli-press-endpoint-row" key={endpoint.id}><input className="input" value={endpoint.name} placeholder="Name" onChange={(e) => onChange(draft.endpoints.map((item) => item.id === endpoint.id ? { ...item, name: e.target.value } : item))} /><select className="input" value={endpoint.method} onChange={(e) => onChange(draft.endpoints.map((item) => item.id === endpoint.id ? { ...item, method: e.target.value as typeof endpoint.method } : item))}><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option></select><input className="input" value={endpoint.path} placeholder="/path" onChange={(e) => onChange(draft.endpoints.map((item) => item.id === endpoint.id ? { ...item, path: e.target.value } : item))} /><button className="btn btn-danger" onClick={() => onChange(draft.endpoints.filter((item) => item.id !== endpoint.id))}>Remove</button></div>) }<button className="btn" onClick={() => onChange([...draft.endpoints, { id: crypto.randomUUID(), name: "", method: "GET", path: "" }])}>Add endpoint</button></div>;
24
+ }
25
+
26
+ export function CredentialsStep({ draft, onChange }: { draft: ServiceDraft; onChange: (credential: ServiceDraft["credential"]) => void }) {
27
+ const credential = draft.credential;
28
+ return (
29
+ <div className="form-group">
30
+ <p className="card">OAuth support is deferred to FN-3762 / FN-3766.</p>
31
+ <label><input type="radio" checked={credential.kind === "none"} onChange={() => onChange({ kind: "none" })} />None</label>
32
+ <label><input type="radio" checked={credential.kind === "apiKey"} onChange={() => onChange({ kind: "apiKey", header: "", envVar: "" })} />API Key</label>
33
+ <label><input type="radio" checked={credential.kind === "bearerToken"} onChange={() => onChange({ kind: "bearerToken", envVar: "" })} />Bearer Token</label>
34
+ <label><input type="radio" checked={credential.kind === "basicAuth"} onChange={() => onChange({ kind: "basicAuth", usernameEnvVar: "", passwordEnvVar: "" })} />Basic Auth</label>
35
+
36
+ {credential.kind === "apiKey" ? (
37
+ <>
38
+ <input className="input" value={credential.header} placeholder="X-Api-Key" onChange={(e) => onChange({ kind: "apiKey", header: e.target.value, envVar: credential.envVar })} />
39
+ <input className="input" value={credential.envVar} placeholder="SERVICE_API_KEY" onChange={(e) => onChange({ kind: "apiKey", header: credential.header, envVar: e.target.value })} />
40
+ </>
41
+ ) : null}
42
+ {credential.kind === "bearerToken" ? <input className="input" value={credential.envVar} placeholder="SERVICE_TOKEN" onChange={(e) => onChange({ kind: "bearerToken", envVar: e.target.value })} /> : null}
43
+ {credential.kind === "basicAuth" ? (
44
+ <>
45
+ <input className="input" value={credential.usernameEnvVar} placeholder="SERVICE_USER" onChange={(e) => onChange({ kind: "basicAuth", usernameEnvVar: e.target.value, passwordEnvVar: credential.passwordEnvVar })} />
46
+ <input className="input" value={credential.passwordEnvVar} placeholder="SERVICE_PASS" onChange={(e) => onChange({ kind: "basicAuth", usernameEnvVar: credential.usernameEnvVar, passwordEnvVar: e.target.value })} />
47
+ </>
48
+ ) : null}
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export function ReviewStep({ draft }: { draft: ServiceDraft }) {
54
+ return <pre className="card">{JSON.stringify(draft, null, 2)}</pre>;
55
+ }