@apicircle/core 1.0.6 → 1.0.8

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.
@@ -0,0 +1,312 @@
1
+ import { RequestAuth, GlobalSchema, GlobalGraphQL, GlobalFileAsset, Folder, Request, WorkspaceSynced, WorkspaceLocal, Environment, EnvPriorityRef, SecretKeyMeta, Assertion, MockServer, ExecutionPlan, WorkspaceSnapshotTrigger } from '@apicircle/shared';
2
+
3
+ /**
4
+ * One credential-bearing field discovered inside an export envelope.
5
+ *
6
+ * `id` is a stable composite that survives ordering / re-serialization
7
+ * — UIs can use it as the React key and as the `include` set member.
8
+ *
9
+ * Format:
10
+ * - Root folder auth: `folder:<envelope.source.folderId>.<authType>.<field>`
11
+ * - Subfolder auth: `folder:<subfolder.id>.<authType>.<field>`
12
+ * - Request auth: `request:<request.id>.<authType>.<field>`
13
+ */
14
+ interface FolderExportCredential {
15
+ id: string;
16
+ /** Where the credential lives in the envelope. */
17
+ scope: 'root-folder' | 'subfolder' | 'request';
18
+ /** Discriminator of the auth variant that owns the field. */
19
+ authType: RequestAuth['type'];
20
+ /** Field name on the auth object (e.g. "token", "password", "clientSecret"). */
21
+ field: string;
22
+ /** Human-readable label for the UI ("Bearer · token"). */
23
+ label: string;
24
+ /**
25
+ * Where this credential belongs to. For requests this is the
26
+ * request name; for folders it's the folder name. UIs use this to
27
+ * group rows so the user can see which entity is leaking what.
28
+ */
29
+ ownerName: string;
30
+ /** Source-workspace id of the request/folder that owns this field. */
31
+ ownerId: string;
32
+ }
33
+ /**
34
+ * Sorted, stable list of every credential-bearing field in the envelope.
35
+ *
36
+ * Determinism: rows are ordered by `(scope-rank, ownerName, field)` so
37
+ * the same envelope always produces the same UI row order. Re-running
38
+ * the detector after the user toggles include-checkboxes returns the
39
+ * same list with the same ids — the UI never needs to remap state.
40
+ *
41
+ * Pure — does not mutate the envelope.
42
+ */
43
+ declare function collectFolderExportCredentials(envelope: ApicircleFolderExportV1): FolderExportCredential[];
44
+ /**
45
+ * Return a new envelope with every credential-bearing field blanked,
46
+ * except for fields whose `id` appears in `includeIds`. The default
47
+ * (empty `includeIds`) redacts everything — that's the safe default
48
+ * the modal uses when the user hasn't explicitly opted any credential
49
+ * in.
50
+ *
51
+ * The redaction shape mirrors `redactForGit`: credential FIELDS go to
52
+ * `''`, identity fields (`clientId`, `username`, `tokenUrl`, …) stay so
53
+ * the importer still knows which IdP the request originally talked to.
54
+ */
55
+ declare function redactFolderExportCredentials(envelope: ApicircleFolderExportV1, includeIds?: ReadonlySet<string>): ApicircleFolderExportV1;
56
+
57
+ /** Envelope discriminator. Bump the version suffix on a breaking shape change. */
58
+ declare const APICIRCLE_FOLDER_EXPORT_FORMAT = "apicircle.folder/v1";
59
+ interface ApicircleFolderExportV1 {
60
+ format: typeof APICIRCLE_FOLDER_EXPORT_FORMAT;
61
+ /** ISO timestamp of when the export was generated. */
62
+ exportedAt: string;
63
+ /** App version that produced the export (free-form string). */
64
+ appVersion: string;
65
+ /** Loose breadcrumb back to the source — never required by importers. */
66
+ source: {
67
+ workspaceId: string;
68
+ folderId: string;
69
+ folderName: string;
70
+ };
71
+ folder: {
72
+ /** Display name of the exported root folder. */
73
+ name: string;
74
+ /** Folder-level auth, if any was set on the source root folder. */
75
+ auth?: Folder['auth'];
76
+ /**
77
+ * Descendant folders (NOT including the root). `parentId` is the
78
+ * source workspace's id of the parent — when it equals `source.folderId`
79
+ * the folder lives directly under the exported root.
80
+ */
81
+ subfolders: Folder[];
82
+ /**
83
+ * All requests inside the exported subtree. `folderId` is the source
84
+ * id of the immediate parent folder; the importer remaps it onto the
85
+ * destination workspace's freshly-minted ids.
86
+ */
87
+ requests: Request[];
88
+ };
89
+ /**
90
+ * Captured global-asset dependencies referenced by the exported
91
+ * requests. Schemas/GraphQL travel embedded; files travel
92
+ * metadata-only.
93
+ */
94
+ dependencies: ApicircleFolderExportDependencies;
95
+ }
96
+ interface ApicircleFolderExportDependencies {
97
+ schemas: GlobalSchema[];
98
+ graphql: GlobalGraphQL[];
99
+ /**
100
+ * File-asset METADATA only. The `slotId` is preserved so the importer
101
+ * can correlate against an existing slot on the destination side if
102
+ * one happens to match; otherwise the user is prompted to re-attach.
103
+ */
104
+ files: GlobalFileAsset[];
105
+ }
106
+ /**
107
+ * Plain-language summary of what the export contains, surfaced inside the
108
+ * Export Folder modal so the user knows exactly what's leaving the
109
+ * workspace before they click Download.
110
+ */
111
+ interface FolderExportReport {
112
+ folderName: string;
113
+ requestCount: number;
114
+ subfolderCount: number;
115
+ /** Total folder count INCLUDING the exported root. */
116
+ totalFolderCount: number;
117
+ dependencies: {
118
+ schemas: Array<{
119
+ id: string;
120
+ name: string;
121
+ }>;
122
+ graphql: Array<{
123
+ id: string;
124
+ name: string;
125
+ kind: GlobalGraphQL['kind'];
126
+ }>;
127
+ files: Array<{
128
+ id: string;
129
+ name: string;
130
+ filename: string;
131
+ size: number;
132
+ mimeType: string;
133
+ }>;
134
+ };
135
+ /** Convenience flag — `true` when any dependency was captured. */
136
+ hasDependencies: boolean;
137
+ /**
138
+ * Every credential-bearing field detected inside the envelope's auth
139
+ * blocks (root folder, subfolders, requests). Surfaced by the export
140
+ * modal so the user can opt-in per-field before the file leaves the
141
+ * workspace. Defaults to "redact everything" — see
142
+ * `redactFolderExportCredentials`.
143
+ */
144
+ credentials: FolderExportCredential[];
145
+ /** Convenience flag — `true` when any credential was detected. */
146
+ hasCredentials: boolean;
147
+ }
148
+ interface CollectFolderExportArgs {
149
+ synced: WorkspaceSynced;
150
+ folderId: string;
151
+ /** Defaults to `new Date().toISOString()` — overridable for deterministic tests. */
152
+ now?: string;
153
+ /** Defaults to `'apicircle-studio'` — overridable for deterministic tests. */
154
+ appVersion?: string;
155
+ }
156
+ interface CollectFolderExportResult {
157
+ envelope: ApicircleFolderExportV1;
158
+ report: FolderExportReport;
159
+ }
160
+ /**
161
+ * Walk the subtree rooted at `folderId`, collect its requests + descendant
162
+ * folders, gather every referenced global-asset dependency, and assemble
163
+ * a self-describing export envelope.
164
+ *
165
+ * Returns `null` when `folderId` doesn't exist — UI callers should treat
166
+ * that as a no-op (the source folder was deleted between menu open and
167
+ * click).
168
+ */
169
+ declare function collectFolderExport(args: CollectFolderExportArgs): CollectFolderExportResult | null;
170
+ /** JSON-stringify an envelope with stable, human-friendly formatting (2-space indent). */
171
+ declare function serializeFolderExport(envelope: ApicircleFolderExportV1): string;
172
+ /** Filename the UI uses for the downloaded file. Slugifies the folder name. */
173
+ declare function suggestFolderExportFilename(envelope: ApicircleFolderExportV1): string;
174
+
175
+ interface ParsedApicircleFolderExport {
176
+ /** The exported root folder, already assigned a fresh id. */
177
+ rootFolder: {
178
+ id: string;
179
+ name: string;
180
+ auth?: Folder['auth'];
181
+ };
182
+ /** Descendant folders with fresh ids + remapped parentIds. */
183
+ subfolders: Folder[];
184
+ /** Requests with fresh ids + remapped folderIds + remapped asset refs. */
185
+ requests: Request[];
186
+ /** Dependencies, ids freshly minted. */
187
+ dependencies: {
188
+ schemas: GlobalSchema[];
189
+ graphql: GlobalGraphQL[];
190
+ files: GlobalFileAsset[];
191
+ };
192
+ /** Source envelope's `source.folderName` — used for display copy. */
193
+ sourceFolderName: string;
194
+ /**
195
+ * Notes the parser surfaced about the import — e.g. a stale dependency
196
+ * reference that no longer existed in the source envelope. Importers
197
+ * forward these to the UI as soft warnings.
198
+ */
199
+ warnings: string[];
200
+ }
201
+ /** Lightweight discriminator — `true` when `doc.format === 'apicircle.folder/v1'`. */
202
+ declare function isApicircleFolderExport(doc: unknown): doc is ApicircleFolderExportV1;
203
+ /**
204
+ * Parse + validate a raw JSON string. Throws with a single, user-readable
205
+ * message when the input is malformed; otherwise returns a parsed shape
206
+ * ready for the store to graft in.
207
+ *
208
+ * `idGenerator` is overridable for deterministic tests; defaults to
209
+ * `generateId()` from `@apicircle/shared`.
210
+ */
211
+ declare function parseApicircleFolderExport(input: string, options?: {
212
+ idGenerator?: () => string;
213
+ }): ParsedApicircleFolderExport;
214
+ /**
215
+ * Same as `parseApicircleFolderExport` but skips the JSON.parse step —
216
+ * used by callers that already deserialized the document. Splitting the
217
+ * entry points keeps the validation logic identical.
218
+ */
219
+ declare function parseApicircleFolderExportDoc(doc: unknown, options?: {
220
+ idGenerator?: () => string;
221
+ }): ParsedApicircleFolderExport;
222
+
223
+ type WorkspacePatch = {
224
+ kind: 'request.create';
225
+ request: Request;
226
+ } | {
227
+ kind: 'request.update';
228
+ id: string;
229
+ patch: Partial<Omit<Request, 'id' | 'createdAt'>>;
230
+ } | {
231
+ kind: 'request.delete';
232
+ id: string;
233
+ } | {
234
+ kind: 'folder.create';
235
+ folder: Folder;
236
+ } | {
237
+ kind: 'folder.delete';
238
+ id: string;
239
+ } | {
240
+ kind: 'folder.move';
241
+ id: string;
242
+ newParentId: string | null;
243
+ } | {
244
+ kind: 'folder.import_apicircle';
245
+ parsed: ParsedApicircleFolderExport;
246
+ parentFolderId: string | null;
247
+ } | {
248
+ kind: 'environment.upsert';
249
+ environment: Environment;
250
+ } | {
251
+ kind: 'environment.delete';
252
+ name: string;
253
+ } | {
254
+ kind: 'environment.setActive';
255
+ name: string | null;
256
+ } | {
257
+ kind: 'environment.setPriority';
258
+ order: EnvPriorityRef[];
259
+ } | {
260
+ kind: 'secretKey.upsert';
261
+ meta: SecretKeyMeta;
262
+ } | {
263
+ kind: 'assertion.upsert';
264
+ requestId: string;
265
+ assertion: Assertion;
266
+ } | {
267
+ kind: 'assertion.delete';
268
+ requestId: string;
269
+ assertionId: string;
270
+ } | {
271
+ kind: 'mock.upsert';
272
+ mock: MockServer;
273
+ } | {
274
+ kind: 'mock.delete';
275
+ id: string;
276
+ } | {
277
+ kind: 'plan.upsert';
278
+ plan: ExecutionPlan;
279
+ } | {
280
+ kind: 'plan.delete';
281
+ id: string;
282
+ } | {
283
+ kind: 'history.delete_run';
284
+ runId: string;
285
+ } | {
286
+ kind: 'history.delete_plan_run';
287
+ planRunId: string;
288
+ } | {
289
+ kind: 'history.purge';
290
+ olderThanMs: number;
291
+ } | {
292
+ kind: 'snapshot.capture';
293
+ trigger: WorkspaceSnapshotTrigger;
294
+ note?: string;
295
+ id?: string;
296
+ } | {
297
+ kind: 'snapshot.delete';
298
+ id: string;
299
+ } | {
300
+ kind: 'snapshot.restore';
301
+ id: string;
302
+ } | {
303
+ kind: 'snapshot.set_max_bytes';
304
+ maxBytes: number;
305
+ };
306
+ type WorkspacePatchKind = WorkspacePatch['kind'];
307
+ interface WorkspaceState {
308
+ synced: WorkspaceSynced;
309
+ local: WorkspaceLocal;
310
+ }
311
+
312
+ export { APICIRCLE_FOLDER_EXPORT_FORMAT as A, type CollectFolderExportArgs as C, type FolderExportCredential as F, type ParsedApicircleFolderExport as P, type WorkspaceState as W, type WorkspacePatch as a, type ApicircleFolderExportDependencies as b, type ApicircleFolderExportV1 as c, type CollectFolderExportResult as d, type FolderExportReport as e, type WorkspacePatchKind as f, collectFolderExport as g, collectFolderExportCredentials as h, isApicircleFolderExport as i, parseApicircleFolderExportDoc as j, suggestFolderExportFilename as k, parseApicircleFolderExport as p, redactFolderExportCredentials as r, serializeFolderExport as s };
@@ -0,0 +1,312 @@
1
+ import { RequestAuth, GlobalSchema, GlobalGraphQL, GlobalFileAsset, Folder, Request, WorkspaceSynced, WorkspaceLocal, Environment, EnvPriorityRef, SecretKeyMeta, Assertion, MockServer, ExecutionPlan, WorkspaceSnapshotTrigger } from '@apicircle/shared';
2
+
3
+ /**
4
+ * One credential-bearing field discovered inside an export envelope.
5
+ *
6
+ * `id` is a stable composite that survives ordering / re-serialization
7
+ * — UIs can use it as the React key and as the `include` set member.
8
+ *
9
+ * Format:
10
+ * - Root folder auth: `folder:<envelope.source.folderId>.<authType>.<field>`
11
+ * - Subfolder auth: `folder:<subfolder.id>.<authType>.<field>`
12
+ * - Request auth: `request:<request.id>.<authType>.<field>`
13
+ */
14
+ interface FolderExportCredential {
15
+ id: string;
16
+ /** Where the credential lives in the envelope. */
17
+ scope: 'root-folder' | 'subfolder' | 'request';
18
+ /** Discriminator of the auth variant that owns the field. */
19
+ authType: RequestAuth['type'];
20
+ /** Field name on the auth object (e.g. "token", "password", "clientSecret"). */
21
+ field: string;
22
+ /** Human-readable label for the UI ("Bearer · token"). */
23
+ label: string;
24
+ /**
25
+ * Where this credential belongs to. For requests this is the
26
+ * request name; for folders it's the folder name. UIs use this to
27
+ * group rows so the user can see which entity is leaking what.
28
+ */
29
+ ownerName: string;
30
+ /** Source-workspace id of the request/folder that owns this field. */
31
+ ownerId: string;
32
+ }
33
+ /**
34
+ * Sorted, stable list of every credential-bearing field in the envelope.
35
+ *
36
+ * Determinism: rows are ordered by `(scope-rank, ownerName, field)` so
37
+ * the same envelope always produces the same UI row order. Re-running
38
+ * the detector after the user toggles include-checkboxes returns the
39
+ * same list with the same ids — the UI never needs to remap state.
40
+ *
41
+ * Pure — does not mutate the envelope.
42
+ */
43
+ declare function collectFolderExportCredentials(envelope: ApicircleFolderExportV1): FolderExportCredential[];
44
+ /**
45
+ * Return a new envelope with every credential-bearing field blanked,
46
+ * except for fields whose `id` appears in `includeIds`. The default
47
+ * (empty `includeIds`) redacts everything — that's the safe default
48
+ * the modal uses when the user hasn't explicitly opted any credential
49
+ * in.
50
+ *
51
+ * The redaction shape mirrors `redactForGit`: credential FIELDS go to
52
+ * `''`, identity fields (`clientId`, `username`, `tokenUrl`, …) stay so
53
+ * the importer still knows which IdP the request originally talked to.
54
+ */
55
+ declare function redactFolderExportCredentials(envelope: ApicircleFolderExportV1, includeIds?: ReadonlySet<string>): ApicircleFolderExportV1;
56
+
57
+ /** Envelope discriminator. Bump the version suffix on a breaking shape change. */
58
+ declare const APICIRCLE_FOLDER_EXPORT_FORMAT = "apicircle.folder/v1";
59
+ interface ApicircleFolderExportV1 {
60
+ format: typeof APICIRCLE_FOLDER_EXPORT_FORMAT;
61
+ /** ISO timestamp of when the export was generated. */
62
+ exportedAt: string;
63
+ /** App version that produced the export (free-form string). */
64
+ appVersion: string;
65
+ /** Loose breadcrumb back to the source — never required by importers. */
66
+ source: {
67
+ workspaceId: string;
68
+ folderId: string;
69
+ folderName: string;
70
+ };
71
+ folder: {
72
+ /** Display name of the exported root folder. */
73
+ name: string;
74
+ /** Folder-level auth, if any was set on the source root folder. */
75
+ auth?: Folder['auth'];
76
+ /**
77
+ * Descendant folders (NOT including the root). `parentId` is the
78
+ * source workspace's id of the parent — when it equals `source.folderId`
79
+ * the folder lives directly under the exported root.
80
+ */
81
+ subfolders: Folder[];
82
+ /**
83
+ * All requests inside the exported subtree. `folderId` is the source
84
+ * id of the immediate parent folder; the importer remaps it onto the
85
+ * destination workspace's freshly-minted ids.
86
+ */
87
+ requests: Request[];
88
+ };
89
+ /**
90
+ * Captured global-asset dependencies referenced by the exported
91
+ * requests. Schemas/GraphQL travel embedded; files travel
92
+ * metadata-only.
93
+ */
94
+ dependencies: ApicircleFolderExportDependencies;
95
+ }
96
+ interface ApicircleFolderExportDependencies {
97
+ schemas: GlobalSchema[];
98
+ graphql: GlobalGraphQL[];
99
+ /**
100
+ * File-asset METADATA only. The `slotId` is preserved so the importer
101
+ * can correlate against an existing slot on the destination side if
102
+ * one happens to match; otherwise the user is prompted to re-attach.
103
+ */
104
+ files: GlobalFileAsset[];
105
+ }
106
+ /**
107
+ * Plain-language summary of what the export contains, surfaced inside the
108
+ * Export Folder modal so the user knows exactly what's leaving the
109
+ * workspace before they click Download.
110
+ */
111
+ interface FolderExportReport {
112
+ folderName: string;
113
+ requestCount: number;
114
+ subfolderCount: number;
115
+ /** Total folder count INCLUDING the exported root. */
116
+ totalFolderCount: number;
117
+ dependencies: {
118
+ schemas: Array<{
119
+ id: string;
120
+ name: string;
121
+ }>;
122
+ graphql: Array<{
123
+ id: string;
124
+ name: string;
125
+ kind: GlobalGraphQL['kind'];
126
+ }>;
127
+ files: Array<{
128
+ id: string;
129
+ name: string;
130
+ filename: string;
131
+ size: number;
132
+ mimeType: string;
133
+ }>;
134
+ };
135
+ /** Convenience flag — `true` when any dependency was captured. */
136
+ hasDependencies: boolean;
137
+ /**
138
+ * Every credential-bearing field detected inside the envelope's auth
139
+ * blocks (root folder, subfolders, requests). Surfaced by the export
140
+ * modal so the user can opt-in per-field before the file leaves the
141
+ * workspace. Defaults to "redact everything" — see
142
+ * `redactFolderExportCredentials`.
143
+ */
144
+ credentials: FolderExportCredential[];
145
+ /** Convenience flag — `true` when any credential was detected. */
146
+ hasCredentials: boolean;
147
+ }
148
+ interface CollectFolderExportArgs {
149
+ synced: WorkspaceSynced;
150
+ folderId: string;
151
+ /** Defaults to `new Date().toISOString()` — overridable for deterministic tests. */
152
+ now?: string;
153
+ /** Defaults to `'apicircle-studio'` — overridable for deterministic tests. */
154
+ appVersion?: string;
155
+ }
156
+ interface CollectFolderExportResult {
157
+ envelope: ApicircleFolderExportV1;
158
+ report: FolderExportReport;
159
+ }
160
+ /**
161
+ * Walk the subtree rooted at `folderId`, collect its requests + descendant
162
+ * folders, gather every referenced global-asset dependency, and assemble
163
+ * a self-describing export envelope.
164
+ *
165
+ * Returns `null` when `folderId` doesn't exist — UI callers should treat
166
+ * that as a no-op (the source folder was deleted between menu open and
167
+ * click).
168
+ */
169
+ declare function collectFolderExport(args: CollectFolderExportArgs): CollectFolderExportResult | null;
170
+ /** JSON-stringify an envelope with stable, human-friendly formatting (2-space indent). */
171
+ declare function serializeFolderExport(envelope: ApicircleFolderExportV1): string;
172
+ /** Filename the UI uses for the downloaded file. Slugifies the folder name. */
173
+ declare function suggestFolderExportFilename(envelope: ApicircleFolderExportV1): string;
174
+
175
+ interface ParsedApicircleFolderExport {
176
+ /** The exported root folder, already assigned a fresh id. */
177
+ rootFolder: {
178
+ id: string;
179
+ name: string;
180
+ auth?: Folder['auth'];
181
+ };
182
+ /** Descendant folders with fresh ids + remapped parentIds. */
183
+ subfolders: Folder[];
184
+ /** Requests with fresh ids + remapped folderIds + remapped asset refs. */
185
+ requests: Request[];
186
+ /** Dependencies, ids freshly minted. */
187
+ dependencies: {
188
+ schemas: GlobalSchema[];
189
+ graphql: GlobalGraphQL[];
190
+ files: GlobalFileAsset[];
191
+ };
192
+ /** Source envelope's `source.folderName` — used for display copy. */
193
+ sourceFolderName: string;
194
+ /**
195
+ * Notes the parser surfaced about the import — e.g. a stale dependency
196
+ * reference that no longer existed in the source envelope. Importers
197
+ * forward these to the UI as soft warnings.
198
+ */
199
+ warnings: string[];
200
+ }
201
+ /** Lightweight discriminator — `true` when `doc.format === 'apicircle.folder/v1'`. */
202
+ declare function isApicircleFolderExport(doc: unknown): doc is ApicircleFolderExportV1;
203
+ /**
204
+ * Parse + validate a raw JSON string. Throws with a single, user-readable
205
+ * message when the input is malformed; otherwise returns a parsed shape
206
+ * ready for the store to graft in.
207
+ *
208
+ * `idGenerator` is overridable for deterministic tests; defaults to
209
+ * `generateId()` from `@apicircle/shared`.
210
+ */
211
+ declare function parseApicircleFolderExport(input: string, options?: {
212
+ idGenerator?: () => string;
213
+ }): ParsedApicircleFolderExport;
214
+ /**
215
+ * Same as `parseApicircleFolderExport` but skips the JSON.parse step —
216
+ * used by callers that already deserialized the document. Splitting the
217
+ * entry points keeps the validation logic identical.
218
+ */
219
+ declare function parseApicircleFolderExportDoc(doc: unknown, options?: {
220
+ idGenerator?: () => string;
221
+ }): ParsedApicircleFolderExport;
222
+
223
+ type WorkspacePatch = {
224
+ kind: 'request.create';
225
+ request: Request;
226
+ } | {
227
+ kind: 'request.update';
228
+ id: string;
229
+ patch: Partial<Omit<Request, 'id' | 'createdAt'>>;
230
+ } | {
231
+ kind: 'request.delete';
232
+ id: string;
233
+ } | {
234
+ kind: 'folder.create';
235
+ folder: Folder;
236
+ } | {
237
+ kind: 'folder.delete';
238
+ id: string;
239
+ } | {
240
+ kind: 'folder.move';
241
+ id: string;
242
+ newParentId: string | null;
243
+ } | {
244
+ kind: 'folder.import_apicircle';
245
+ parsed: ParsedApicircleFolderExport;
246
+ parentFolderId: string | null;
247
+ } | {
248
+ kind: 'environment.upsert';
249
+ environment: Environment;
250
+ } | {
251
+ kind: 'environment.delete';
252
+ name: string;
253
+ } | {
254
+ kind: 'environment.setActive';
255
+ name: string | null;
256
+ } | {
257
+ kind: 'environment.setPriority';
258
+ order: EnvPriorityRef[];
259
+ } | {
260
+ kind: 'secretKey.upsert';
261
+ meta: SecretKeyMeta;
262
+ } | {
263
+ kind: 'assertion.upsert';
264
+ requestId: string;
265
+ assertion: Assertion;
266
+ } | {
267
+ kind: 'assertion.delete';
268
+ requestId: string;
269
+ assertionId: string;
270
+ } | {
271
+ kind: 'mock.upsert';
272
+ mock: MockServer;
273
+ } | {
274
+ kind: 'mock.delete';
275
+ id: string;
276
+ } | {
277
+ kind: 'plan.upsert';
278
+ plan: ExecutionPlan;
279
+ } | {
280
+ kind: 'plan.delete';
281
+ id: string;
282
+ } | {
283
+ kind: 'history.delete_run';
284
+ runId: string;
285
+ } | {
286
+ kind: 'history.delete_plan_run';
287
+ planRunId: string;
288
+ } | {
289
+ kind: 'history.purge';
290
+ olderThanMs: number;
291
+ } | {
292
+ kind: 'snapshot.capture';
293
+ trigger: WorkspaceSnapshotTrigger;
294
+ note?: string;
295
+ id?: string;
296
+ } | {
297
+ kind: 'snapshot.delete';
298
+ id: string;
299
+ } | {
300
+ kind: 'snapshot.restore';
301
+ id: string;
302
+ } | {
303
+ kind: 'snapshot.set_max_bytes';
304
+ maxBytes: number;
305
+ };
306
+ type WorkspacePatchKind = WorkspacePatch['kind'];
307
+ interface WorkspaceState {
308
+ synced: WorkspaceSynced;
309
+ local: WorkspaceLocal;
310
+ }
311
+
312
+ export { APICIRCLE_FOLDER_EXPORT_FORMAT as A, type CollectFolderExportArgs as C, type FolderExportCredential as F, type ParsedApicircleFolderExport as P, type WorkspaceState as W, type WorkspacePatch as a, type ApicircleFolderExportDependencies as b, type ApicircleFolderExportV1 as c, type CollectFolderExportResult as d, type FolderExportReport as e, type WorkspacePatchKind as f, collectFolderExport as g, collectFolderExportCredentials as h, isApicircleFolderExport as i, parseApicircleFolderExportDoc as j, suggestFolderExportFilename as k, parseApicircleFolderExport as p, redactFolderExportCredentials as r, serializeFolderExport as s };
@@ -151,8 +151,8 @@ function createEmptyLocalForSynced(synced) {
151
151
  ui: {
152
152
  activeRequestId: null,
153
153
  sidebarExpandedSections: [],
154
- themeId: "command-center",
155
- fontId: "cascadia-code",
154
+ themeId: "one-dark-pro",
155
+ fontId: "system-sans",
156
156
  fontSizePercent: import_shared.FONT_SIZE_PERCENT_DEFAULT
157
157
  },
158
158
  settings: { validateOnSend: true, monacoConsumesWheel: false },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/workspace/fileBackedWorkspace.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { FONT_SIZE_PERCENT_DEFAULT } from '@apicircle/shared';\nimport type { WorkspaceLocal, WorkspaceSynced } from '@apicircle/shared';\nimport lockfile from 'proper-lockfile';\nimport type { WorkspaceState } from './patches';\n\n// =============================================================================\n// fileBackedWorkspace — load/save a `{ synced, local }` pair as two JSON\n// files on disk, with a `proper-lockfile` advisory lock so concurrent CLI /\n// MCP writers can't corrupt the document.\n//\n// Layout (relative to the directory passed in):\n// workspace.synced.json ← matches WorkspaceSynced exactly, push-to-git target\n// workspace.local.json ← WorkspaceLocal, host-private (CLI/MCP doesn't push)\n//\n// The lock is held on `workspace.synced.json` because that's the file the\n// editor races against. Stale locks are released after 30s.\n// =============================================================================\n\nconst SYNCED_FILE = 'workspace.synced.json';\nconst LOCAL_FILE = 'workspace.local.json';\n\nexport interface LoadFromFileOptions {\n /** When true, return `null` instead of throwing if the synced file is missing. */\n allowMissing?: boolean;\n}\n\nexport interface SaveToFileOptions {\n /** Lock timeout (ms). Defaults to 30000. */\n lockTimeoutMs?: number;\n}\n\n/**\n * Load both workspace documents from `dir`. The synced file is required;\n * the local file is optional and falls back to a minimal empty shape so a\n * CLI on a fresh machine can still operate (it just won't have history /\n * overrides until the desktop app runs once).\n */\nexport async function loadFromFile(\n dir: string,\n options: LoadFromFileOptions = {},\n): Promise<WorkspaceState | null> {\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n\n let syncedRaw: string;\n try {\n syncedRaw = await fs.readFile(syncedPath, 'utf-8');\n } catch (err) {\n if (options.allowMissing && isENOENT(err)) return null;\n throw err;\n }\n const synced = JSON.parse(syncedRaw) as WorkspaceSynced;\n\n let local: WorkspaceLocal;\n try {\n local = JSON.parse(await fs.readFile(localPath, 'utf-8')) as WorkspaceLocal;\n local = { ...local, attachmentCache: local.attachmentCache ?? {} };\n } catch (err) {\n if (!isENOENT(err)) throw err;\n local = createEmptyLocalForSynced(synced);\n }\n\n return { synced, local };\n}\n\n/**\n * Atomically write both documents back to disk. Acquires an advisory lock\n * on the synced file for the duration of the write so a parallel CLI /\n * MCP / desktop save can't interleave.\n *\n * Both files are written via `<file>.tmp` + rename so a crash mid-write\n * never leaves a partial JSON document on disk.\n */\nexport async function saveToFile(\n dir: string,\n state: WorkspaceState,\n options: SaveToFileOptions = {},\n): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n\n // proper-lockfile requires the target file to exist. Touch it on first save.\n await ensureFile(syncedPath);\n\n const release = await lockfile.lock(syncedPath, {\n retries: { retries: 5, minTimeout: 50, maxTimeout: 500 },\n stale: options.lockTimeoutMs ?? 30000,\n });\n try {\n await writeJsonAtomic(syncedPath, state.synced);\n await writeJsonAtomic(localPath, state.local);\n } finally {\n await release();\n }\n}\n\n/**\n * Run a load → mutate → save cycle under one lock so a single mutation\n * can't be clobbered by a racing reader-then-writer.\n */\nexport async function withWorkspace<T>(\n dir: string,\n fn: (state: WorkspaceState) => Promise<{ next: WorkspaceState; result?: T }>,\n options: SaveToFileOptions = {},\n): Promise<T | undefined> {\n await fs.mkdir(dir, { recursive: true });\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n await ensureFile(syncedPath);\n\n const release = await lockfile.lock(syncedPath, {\n retries: { retries: 5, minTimeout: 50, maxTimeout: 500 },\n stale: options.lockTimeoutMs ?? 30000,\n });\n try {\n const syncedRaw = await fs.readFile(syncedPath, 'utf-8');\n const synced = JSON.parse(syncedRaw) as WorkspaceSynced;\n let local: WorkspaceLocal;\n try {\n local = JSON.parse(await fs.readFile(localPath, 'utf-8')) as WorkspaceLocal;\n local = { ...local, attachmentCache: local.attachmentCache ?? {} };\n } catch (err) {\n if (!isENOENT(err)) throw err;\n local = createEmptyLocalForSynced(synced);\n }\n const out = await fn({ synced, local });\n await writeJsonAtomic(syncedPath, out.next.synced);\n await writeJsonAtomic(localPath, out.next.local);\n return out.result;\n } finally {\n await release();\n }\n}\n\n// ---------------------------------------------------------------------------\n// internals\n// ---------------------------------------------------------------------------\n\n// File mode for workspace JSON: owner read/write only. Default `fs.writeFile`\n// uses 0o666 minus umask (typically 0o644 — world-readable). The workspace\n// docs carry the synced state (which after redaction is mostly safe to read\n// but still includes per-workspace metadata) and the local state (which\n// holds the encrypted Secret Vault payload table, session metadata, and the\n// vault entries themselves). On multi-user POSIX hosts (CI runners,\n// classroom VMs, shared dev servers) the default would leak both. 0o600\n// keeps the file owner-only. Windows ignores POSIX modes — the inherited\n// per-user ACL under %USERPROFILE% is what protects it there.\nconst WORKSPACE_FILE_MODE = 0o600;\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath);\n } catch (err) {\n if (!isENOENT(err)) throw err;\n await fs.writeFile(filePath, '{}', { encoding: 'utf-8', mode: WORKSPACE_FILE_MODE });\n }\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n const tmp = `${filePath}.tmp`;\n await fs.writeFile(tmp, JSON.stringify(value, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: WORKSPACE_FILE_MODE,\n });\n await fs.rename(tmp, filePath);\n}\n\nfunction isENOENT(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nfunction createEmptyLocalForSynced(synced: WorkspaceSynced): WorkspaceLocal {\n return {\n schemaVersion: 1,\n workspaceId: synced.workspaceId,\n executionPlans: {},\n history: { requestRuns: [], planRuns: [] },\n secretIndex: { entries: {} },\n sessions: { github: { workspace: null, links: {} } },\n connectedRepo: null,\n workingBranch: null,\n seededWorkspaceSha: null,\n retiredBranch: null,\n sync: {\n lastPulledSnapshot: null,\n lastPulledSha: null,\n lastPulledAt: null,\n dirtyKeys: [],\n },\n linkedCollections: {},\n attachmentCache: {},\n globalContext: {},\n mockRuntime: { active: {} },\n ui: {\n activeRequestId: null,\n sidebarExpandedSections: [],\n themeId: 'command-center',\n fontId: 'cascadia-code',\n fontSizePercent: FONT_SIZE_PERCENT_DEFAULT,\n },\n settings: { validateOnSend: true, monacoConsumesWheel: false },\n snapshots: { entries: [], maxBytes: 50 * 1024 * 1024 },\n };\n}\n"],"mappings":"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","names":["fs","lockfile"]}
1
+ {"version":3,"sources":["../../src/workspace/fileBackedWorkspace.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { FONT_SIZE_PERCENT_DEFAULT } from '@apicircle/shared';\nimport type { WorkspaceLocal, WorkspaceSynced } from '@apicircle/shared';\nimport lockfile from 'proper-lockfile';\nimport type { WorkspaceState } from './patches';\n\n// =============================================================================\n// fileBackedWorkspace — load/save a `{ synced, local }` pair as two JSON\n// files on disk, with a `proper-lockfile` advisory lock so concurrent CLI /\n// MCP writers can't corrupt the document.\n//\n// Layout (relative to the directory passed in):\n// workspace.synced.json ← matches WorkspaceSynced exactly, push-to-git target\n// workspace.local.json ← WorkspaceLocal, host-private (CLI/MCP doesn't push)\n//\n// The lock is held on `workspace.synced.json` because that's the file the\n// editor races against. Stale locks are released after 30s.\n// =============================================================================\n\nconst SYNCED_FILE = 'workspace.synced.json';\nconst LOCAL_FILE = 'workspace.local.json';\n\nexport interface LoadFromFileOptions {\n /** When true, return `null` instead of throwing if the synced file is missing. */\n allowMissing?: boolean;\n}\n\nexport interface SaveToFileOptions {\n /** Lock timeout (ms). Defaults to 30000. */\n lockTimeoutMs?: number;\n}\n\n/**\n * Load both workspace documents from `dir`. The synced file is required;\n * the local file is optional and falls back to a minimal empty shape so a\n * CLI on a fresh machine can still operate (it just won't have history /\n * overrides until the desktop app runs once).\n */\nexport async function loadFromFile(\n dir: string,\n options: LoadFromFileOptions = {},\n): Promise<WorkspaceState | null> {\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n\n let syncedRaw: string;\n try {\n syncedRaw = await fs.readFile(syncedPath, 'utf-8');\n } catch (err) {\n if (options.allowMissing && isENOENT(err)) return null;\n throw err;\n }\n const synced = JSON.parse(syncedRaw) as WorkspaceSynced;\n\n let local: WorkspaceLocal;\n try {\n local = JSON.parse(await fs.readFile(localPath, 'utf-8')) as WorkspaceLocal;\n local = { ...local, attachmentCache: local.attachmentCache ?? {} };\n } catch (err) {\n if (!isENOENT(err)) throw err;\n local = createEmptyLocalForSynced(synced);\n }\n\n return { synced, local };\n}\n\n/**\n * Atomically write both documents back to disk. Acquires an advisory lock\n * on the synced file for the duration of the write so a parallel CLI /\n * MCP / desktop save can't interleave.\n *\n * Both files are written via `<file>.tmp` + rename so a crash mid-write\n * never leaves a partial JSON document on disk.\n */\nexport async function saveToFile(\n dir: string,\n state: WorkspaceState,\n options: SaveToFileOptions = {},\n): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n\n // proper-lockfile requires the target file to exist. Touch it on first save.\n await ensureFile(syncedPath);\n\n const release = await lockfile.lock(syncedPath, {\n retries: { retries: 5, minTimeout: 50, maxTimeout: 500 },\n stale: options.lockTimeoutMs ?? 30000,\n });\n try {\n await writeJsonAtomic(syncedPath, state.synced);\n await writeJsonAtomic(localPath, state.local);\n } finally {\n await release();\n }\n}\n\n/**\n * Run a load → mutate → save cycle under one lock so a single mutation\n * can't be clobbered by a racing reader-then-writer.\n */\nexport async function withWorkspace<T>(\n dir: string,\n fn: (state: WorkspaceState) => Promise<{ next: WorkspaceState; result?: T }>,\n options: SaveToFileOptions = {},\n): Promise<T | undefined> {\n await fs.mkdir(dir, { recursive: true });\n const syncedPath = path.join(dir, SYNCED_FILE);\n const localPath = path.join(dir, LOCAL_FILE);\n await ensureFile(syncedPath);\n\n const release = await lockfile.lock(syncedPath, {\n retries: { retries: 5, minTimeout: 50, maxTimeout: 500 },\n stale: options.lockTimeoutMs ?? 30000,\n });\n try {\n const syncedRaw = await fs.readFile(syncedPath, 'utf-8');\n const synced = JSON.parse(syncedRaw) as WorkspaceSynced;\n let local: WorkspaceLocal;\n try {\n local = JSON.parse(await fs.readFile(localPath, 'utf-8')) as WorkspaceLocal;\n local = { ...local, attachmentCache: local.attachmentCache ?? {} };\n } catch (err) {\n if (!isENOENT(err)) throw err;\n local = createEmptyLocalForSynced(synced);\n }\n const out = await fn({ synced, local });\n await writeJsonAtomic(syncedPath, out.next.synced);\n await writeJsonAtomic(localPath, out.next.local);\n return out.result;\n } finally {\n await release();\n }\n}\n\n// ---------------------------------------------------------------------------\n// internals\n// ---------------------------------------------------------------------------\n\n// File mode for workspace JSON: owner read/write only. Default `fs.writeFile`\n// uses 0o666 minus umask (typically 0o644 — world-readable). The workspace\n// docs carry the synced state (which after redaction is mostly safe to read\n// but still includes per-workspace metadata) and the local state (which\n// holds the encrypted Secret Vault payload table, session metadata, and the\n// vault entries themselves). On multi-user POSIX hosts (CI runners,\n// classroom VMs, shared dev servers) the default would leak both. 0o600\n// keeps the file owner-only. Windows ignores POSIX modes — the inherited\n// per-user ACL under %USERPROFILE% is what protects it there.\nconst WORKSPACE_FILE_MODE = 0o600;\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath);\n } catch (err) {\n if (!isENOENT(err)) throw err;\n await fs.writeFile(filePath, '{}', { encoding: 'utf-8', mode: WORKSPACE_FILE_MODE });\n }\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n const tmp = `${filePath}.tmp`;\n await fs.writeFile(tmp, JSON.stringify(value, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: WORKSPACE_FILE_MODE,\n });\n await fs.rename(tmp, filePath);\n}\n\nfunction isENOENT(err: unknown): boolean {\n return typeof err === 'object' && err !== null && 'code' in err && err.code === 'ENOENT';\n}\n\nfunction createEmptyLocalForSynced(synced: WorkspaceSynced): WorkspaceLocal {\n return {\n schemaVersion: 1,\n workspaceId: synced.workspaceId,\n executionPlans: {},\n history: { requestRuns: [], planRuns: [] },\n secretIndex: { entries: {} },\n sessions: { github: { workspace: null, links: {} } },\n connectedRepo: null,\n workingBranch: null,\n seededWorkspaceSha: null,\n retiredBranch: null,\n sync: {\n lastPulledSnapshot: null,\n lastPulledSha: null,\n lastPulledAt: null,\n dirtyKeys: [],\n },\n linkedCollections: {},\n attachmentCache: {},\n globalContext: {},\n mockRuntime: { active: {} },\n ui: {\n activeRequestId: null,\n sidebarExpandedSections: [],\n themeId: 'one-dark-pro',\n fontId: 'system-sans',\n fontSizePercent: FONT_SIZE_PERCENT_DEFAULT,\n },\n settings: { validateOnSend: true, monacoConsumesWheel: false },\n snapshots: { entries: [], maxBytes: 50 * 1024 * 1024 },\n };\n}\n"],"mappings":"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","names":["fs","lockfile"]}