@dobby.ai/dobby 0.1.1 → 0.1.2

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 (136) hide show
  1. package/README.md +20 -7
  2. package/dist/src/agent/event-forwarder.js +185 -16
  3. package/dist/src/cli/commands/cron.js +39 -35
  4. package/dist/src/cli/program.js +0 -6
  5. package/dist/src/core/types.js +2 -0
  6. package/dist/src/cron/config.js +2 -2
  7. package/dist/src/cron/service.js +87 -23
  8. package/dist/src/cron/store.js +1 -1
  9. package/package.json +9 -3
  10. package/.env.example +0 -8
  11. package/AGENTS.md +0 -267
  12. package/ROADMAP.md +0 -34
  13. package/config/cron.example.json +0 -9
  14. package/config/gateway.example.json +0 -132
  15. package/dist/plugins/connector-discord/src/mapper.js +0 -75
  16. package/dist/src/cli/tests/config-command.test.js +0 -42
  17. package/dist/src/cli/tests/config-io.test.js +0 -64
  18. package/dist/src/cli/tests/config-mutators.test.js +0 -47
  19. package/dist/src/cli/tests/discord-mapper.test.js +0 -90
  20. package/dist/src/cli/tests/doctor.test.js +0 -252
  21. package/dist/src/cli/tests/init-catalog.test.js +0 -134
  22. package/dist/src/cli/tests/program-options.test.js +0 -78
  23. package/dist/src/cli/tests/routing-config.test.js +0 -254
  24. package/dist/src/core/tests/control-command.test.js +0 -17
  25. package/dist/src/core/tests/runtime-registry.test.js +0 -116
  26. package/dist/src/core/tests/typing-controller.test.js +0 -103
  27. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
  28. package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
  29. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
  30. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
  31. package/docs/MVP.md +0 -135
  32. package/docs/RUNBOOK.md +0 -243
  33. package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
  34. package/plugins/connector-discord/dobby.manifest.json +0 -18
  35. package/plugins/connector-discord/index.js +0 -1
  36. package/plugins/connector-discord/package-lock.json +0 -360
  37. package/plugins/connector-discord/package.json +0 -38
  38. package/plugins/connector-discord/src/connector.ts +0 -345
  39. package/plugins/connector-discord/src/contribution.ts +0 -21
  40. package/plugins/connector-discord/src/mapper.ts +0 -101
  41. package/plugins/connector-discord/tsconfig.json +0 -19
  42. package/plugins/connector-feishu/dobby.manifest.json +0 -18
  43. package/plugins/connector-feishu/index.js +0 -1
  44. package/plugins/connector-feishu/package-lock.json +0 -618
  45. package/plugins/connector-feishu/package.json +0 -38
  46. package/plugins/connector-feishu/src/connector.ts +0 -343
  47. package/plugins/connector-feishu/src/contribution.ts +0 -26
  48. package/plugins/connector-feishu/src/mapper.ts +0 -401
  49. package/plugins/connector-feishu/tsconfig.json +0 -19
  50. package/plugins/plugin-sdk/index.d.ts +0 -261
  51. package/plugins/plugin-sdk/index.js +0 -1
  52. package/plugins/plugin-sdk/package-lock.json +0 -12
  53. package/plugins/plugin-sdk/package.json +0 -22
  54. package/plugins/provider-claude/dobby.manifest.json +0 -17
  55. package/plugins/provider-claude/index.js +0 -1
  56. package/plugins/provider-claude/package-lock.json +0 -3398
  57. package/plugins/provider-claude/package.json +0 -39
  58. package/plugins/provider-claude/src/contribution.ts +0 -1018
  59. package/plugins/provider-claude/tsconfig.json +0 -19
  60. package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
  61. package/plugins/provider-claude-cli/index.js +0 -1
  62. package/plugins/provider-claude-cli/package-lock.json +0 -2898
  63. package/plugins/provider-claude-cli/package.json +0 -38
  64. package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
  65. package/plugins/provider-claude-cli/tsconfig.json +0 -19
  66. package/plugins/provider-pi/dobby.manifest.json +0 -17
  67. package/plugins/provider-pi/index.js +0 -1
  68. package/plugins/provider-pi/package-lock.json +0 -3877
  69. package/plugins/provider-pi/package.json +0 -40
  70. package/plugins/provider-pi/src/contribution.ts +0 -606
  71. package/plugins/provider-pi/tsconfig.json +0 -19
  72. package/plugins/sandbox-core/boxlite.js +0 -1
  73. package/plugins/sandbox-core/dobby.manifest.json +0 -17
  74. package/plugins/sandbox-core/docker.js +0 -1
  75. package/plugins/sandbox-core/package-lock.json +0 -136
  76. package/plugins/sandbox-core/package.json +0 -39
  77. package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
  78. package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
  79. package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
  80. package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
  81. package/plugins/sandbox-core/src/docker-executor.ts +0 -217
  82. package/plugins/sandbox-core/tsconfig.json +0 -19
  83. package/scripts/local-extensions.mjs +0 -168
  84. package/src/agent/event-forwarder.ts +0 -414
  85. package/src/cli/commands/config.ts +0 -328
  86. package/src/cli/commands/configure.ts +0 -92
  87. package/src/cli/commands/cron.ts +0 -410
  88. package/src/cli/commands/doctor.ts +0 -331
  89. package/src/cli/commands/extension.ts +0 -207
  90. package/src/cli/commands/init.ts +0 -211
  91. package/src/cli/commands/start.ts +0 -223
  92. package/src/cli/commands/topology.ts +0 -415
  93. package/src/cli/index.ts +0 -9
  94. package/src/cli/program.ts +0 -314
  95. package/src/cli/shared/config-io.ts +0 -245
  96. package/src/cli/shared/config-mutators.ts +0 -470
  97. package/src/cli/shared/config-schema.ts +0 -228
  98. package/src/cli/shared/config-types.ts +0 -129
  99. package/src/cli/shared/configure-sections.ts +0 -595
  100. package/src/cli/shared/discord-config.ts +0 -14
  101. package/src/cli/shared/init-catalog.ts +0 -249
  102. package/src/cli/shared/local-extension-specs.ts +0 -108
  103. package/src/cli/shared/runtime.ts +0 -33
  104. package/src/cli/shared/schema-prompts.ts +0 -443
  105. package/src/cli/tests/config-command.test.ts +0 -56
  106. package/src/cli/tests/config-io.test.ts +0 -92
  107. package/src/cli/tests/config-mutators.test.ts +0 -59
  108. package/src/cli/tests/discord-mapper.test.ts +0 -128
  109. package/src/cli/tests/doctor.test.ts +0 -269
  110. package/src/cli/tests/init-catalog.test.ts +0 -144
  111. package/src/cli/tests/program-options.test.ts +0 -95
  112. package/src/cli/tests/routing-config.test.ts +0 -281
  113. package/src/core/control-command.ts +0 -12
  114. package/src/core/dedup-store.ts +0 -103
  115. package/src/core/gateway.ts +0 -609
  116. package/src/core/routing.ts +0 -404
  117. package/src/core/runtime-registry.ts +0 -141
  118. package/src/core/tests/control-command.test.ts +0 -20
  119. package/src/core/tests/runtime-registry.test.ts +0 -140
  120. package/src/core/tests/typing-controller.test.ts +0 -129
  121. package/src/core/types.ts +0 -324
  122. package/src/core/typing-controller.ts +0 -119
  123. package/src/cron/config.ts +0 -154
  124. package/src/cron/schedule.ts +0 -61
  125. package/src/cron/service.ts +0 -249
  126. package/src/cron/store.ts +0 -155
  127. package/src/cron/types.ts +0 -60
  128. package/src/extension/loader.ts +0 -145
  129. package/src/extension/manager.ts +0 -355
  130. package/src/extension/manifest.ts +0 -26
  131. package/src/extension/registry.ts +0 -229
  132. package/src/main.ts +0 -8
  133. package/src/sandbox/executor.ts +0 -44
  134. package/src/sandbox/host-executor.ts +0 -118
  135. package/src/shared/dobby-repo.ts +0 -48
  136. package/tsconfig.json +0 -18
@@ -1,331 +0,0 @@
1
- import { access, mkdir } from "node:fs/promises";
2
- import { dirname, isAbsolute, join, resolve } from "node:path";
3
- import { homedir } from "node:os";
4
- import { loadGatewayConfig } from "../../core/routing.js";
5
- import { ExtensionStoreManager } from "../../extension/manager.js";
6
- import {
7
- ensureGatewayConfigShape,
8
- setDefaultProviderIfMissingOrInvalid,
9
- } from "../shared/config-mutators.js";
10
- import { DISCORD_CONNECTOR_CONTRIBUTION_ID } from "../shared/discord-config.js";
11
- import { readRawConfig, resolveConfigPath, resolveDataRootDir, writeConfigWithValidation } from "../shared/config-io.js";
12
- import { createLogger } from "../shared/runtime.js";
13
-
14
- interface DoctorIssue {
15
- level: "error" | "warning";
16
- message: string;
17
- }
18
-
19
- interface PlaceholderHit {
20
- path: string;
21
- value: string;
22
- }
23
-
24
- function isPlaceholderValue(value: unknown): value is string {
25
- if (typeof value !== "string") {
26
- return false;
27
- }
28
- const normalized = value.trim().toUpperCase();
29
- return normalized.includes("REPLACE_WITH_") || normalized.includes("YOUR_");
30
- }
31
-
32
- function isCredentialLikeKey(key: string): boolean {
33
- return /(?:token|secret|api[-_]?key|appid|appsecret)/i.test(key);
34
- }
35
-
36
- function walkPlaceholders(value: unknown, path: string): PlaceholderHit[] {
37
- if (isPlaceholderValue(value)) {
38
- return [{ path, value }];
39
- }
40
-
41
- if (Array.isArray(value)) {
42
- return value.flatMap((item, index) => walkPlaceholders(item, `${path}[${index}]`));
43
- }
44
-
45
- if (!value || typeof value !== "object") {
46
- return [];
47
- }
48
-
49
- return Object.entries(value).flatMap(([key, nested]) => walkPlaceholders(nested, `${path}.${key}`));
50
- }
51
-
52
- function lastPathSegment(path: string): string {
53
- const withoutIndexes = path.replaceAll(/\[\d+\]/g, "");
54
- const segments = withoutIndexes.split(".");
55
- return segments[segments.length - 1] ?? withoutIndexes;
56
- }
57
-
58
- function expandHome(value: string): string {
59
- if (value === "~") {
60
- return homedir();
61
- }
62
-
63
- if (value.startsWith("~/") || value.startsWith("~\\")) {
64
- return resolve(homedir(), value.slice(2));
65
- }
66
-
67
- return value;
68
- }
69
-
70
- function resolveRouteProjectRoot(configPath: string, projectRoot: string): string {
71
- const expanded = expandHome(projectRoot);
72
- if (isAbsolute(expanded)) {
73
- return resolve(expanded);
74
- }
75
-
76
- return resolve(dirname(resolve(configPath)), expanded);
77
- }
78
-
79
- export async function runDoctorCommand(options: {
80
- fix?: boolean;
81
- }): Promise<void> {
82
- const configPath = resolveConfigPath();
83
- const issues: DoctorIssue[] = [];
84
-
85
- const rawConfig = await readRawConfig(configPath);
86
- if (!rawConfig) {
87
- throw new Error(`Config '${configPath}' does not exist`);
88
- }
89
-
90
- try {
91
- await loadGatewayConfig(configPath);
92
- } catch (error) {
93
- issues.push({
94
- level: "error",
95
- message: `Config schema/reference validation failed: ${error instanceof Error ? error.message : String(error)}`,
96
- });
97
- }
98
-
99
- const normalized = ensureGatewayConfigShape(structuredClone(rawConfig));
100
- const logger = createLogger();
101
- const manager = new ExtensionStoreManager(logger, join(resolveDataRootDir(configPath, normalized), "extensions"));
102
- const installedExtensions = await manager.listInstalled();
103
-
104
- const installedPackages = new Set(installedExtensions.map((item) => item.packageName));
105
- const enabledPackages = new Set(
106
- normalized.extensions.allowList.filter((item) => item.enabled).map((item) => item.package),
107
- );
108
-
109
- for (const packageName of enabledPackages) {
110
- if (!installedPackages.has(packageName)) {
111
- issues.push({
112
- level: "error",
113
- message: `extensions.allowList enables '${packageName}' but it is not installed`,
114
- });
115
- }
116
- }
117
-
118
- const availableContributionIds = new Set<string>();
119
- for (const item of installedExtensions) {
120
- if (item.error) {
121
- issues.push({
122
- level: "warning",
123
- message: `Installed extension '${item.packageName}' has invalid manifest: ${item.error}`,
124
- });
125
- continue;
126
- }
127
-
128
- if (!enabledPackages.has(item.packageName)) {
129
- continue;
130
- }
131
-
132
- for (const contribution of item.manifest?.contributions ?? []) {
133
- availableContributionIds.add(contribution.id);
134
- }
135
- }
136
-
137
- for (const [instanceId, instance] of Object.entries(normalized.providers.items)) {
138
- if (!availableContributionIds.has(instance.type)) {
139
- issues.push({
140
- level: "error",
141
- message: `providers.items['${instanceId}'] references missing contribution '${instance.type}'`,
142
- });
143
- }
144
-
145
- for (const hit of walkPlaceholders(instance, `providers.items['${instanceId}']`)) {
146
- if (hit.path.endsWith(".type")) {
147
- continue;
148
- }
149
-
150
- issues.push({
151
- level: isCredentialLikeKey(lastPathSegment(hit.path)) ? "error" : "warning",
152
- message: `${hit.path} still uses placeholder value '${hit.value}'`,
153
- });
154
- }
155
- }
156
-
157
- for (const [instanceId, instance] of Object.entries(normalized.connectors.items)) {
158
- if (!availableContributionIds.has(instance.type)) {
159
- issues.push({
160
- level: "error",
161
- message: `connectors.items['${instanceId}'] references missing contribution '${instance.type}'`,
162
- });
163
- }
164
-
165
- for (const hit of walkPlaceholders(instance, `connectors.items['${instanceId}']`)) {
166
- if (hit.path.endsWith(".type")) {
167
- continue;
168
- }
169
-
170
- issues.push({
171
- level: isCredentialLikeKey(lastPathSegment(hit.path)) ? "error" : "warning",
172
- message: `${hit.path} still uses placeholder value '${hit.value}'`,
173
- });
174
- }
175
-
176
- if (instance.type === DISCORD_CONNECTOR_CONTRIBUTION_ID) {
177
- const botName = typeof instance.botName === "string" ? instance.botName.trim() : "";
178
- const botToken = typeof instance.botToken === "string" ? instance.botToken.trim() : "";
179
- if (botName.length === 0) {
180
- issues.push({
181
- level: "error",
182
- message: `connectors.items['${instanceId}'].botName is required`,
183
- });
184
- }
185
- if (botToken.length === 0) {
186
- issues.push({
187
- level: "error",
188
- message: `connectors.items['${instanceId}'].botToken is required`,
189
- });
190
- }
191
- }
192
- }
193
-
194
- for (const [instanceId, instance] of Object.entries(normalized.sandboxes.items)) {
195
- if (!availableContributionIds.has(instance.type)) {
196
- issues.push({
197
- level: "error",
198
- message: `sandboxes.items['${instanceId}'] references missing contribution '${instance.type}'`,
199
- });
200
- }
201
- }
202
-
203
- if (normalized.routes.default.projectRoot && isPlaceholderValue(normalized.routes.default.projectRoot)) {
204
- issues.push({
205
- level: "warning",
206
- message: `routes.default.projectRoot still uses placeholder value '${normalized.routes.default.projectRoot}'`,
207
- });
208
- }
209
-
210
- for (const [routeId, route] of Object.entries(normalized.routes.items)) {
211
- const effectiveProjectRoot = route.projectRoot ?? normalized.routes.default.projectRoot;
212
- const projectRootSource = route.projectRoot ? `routes.items['${routeId}'].projectRoot` : "routes.default.projectRoot";
213
-
214
- if (!effectiveProjectRoot) {
215
- issues.push({
216
- level: "error",
217
- message: `routes.items['${routeId}'].projectRoot is required when routes.default.projectRoot is not set`,
218
- });
219
- continue;
220
- }
221
-
222
- if (isPlaceholderValue(effectiveProjectRoot)) {
223
- issues.push({
224
- level: "warning",
225
- message: `${projectRootSource} still uses placeholder value '${effectiveProjectRoot}'`,
226
- });
227
- continue;
228
- }
229
-
230
- try {
231
- const projectRootPath = resolveRouteProjectRoot(configPath, effectiveProjectRoot);
232
- await access(projectRootPath);
233
- } catch {
234
- issues.push({
235
- level: "warning",
236
- message: `${projectRootSource} does not exist: ${effectiveProjectRoot}`,
237
- });
238
- }
239
- }
240
-
241
- if (normalized.bindings.default && !normalized.routes.items[normalized.bindings.default.route]) {
242
- issues.push({
243
- level: "error",
244
- message: `bindings.default.route references unknown route '${normalized.bindings.default.route}'`,
245
- });
246
- }
247
-
248
- const seenBindingSources = new Map<string, string>();
249
- for (const [bindingId, binding] of Object.entries(normalized.bindings.items)) {
250
- if (!normalized.connectors.items[binding.connector]) {
251
- issues.push({
252
- level: "error",
253
- message: `bindings.items['${bindingId}'].connector references unknown connector '${binding.connector}'`,
254
- });
255
- }
256
- if (!normalized.routes.items[binding.route]) {
257
- issues.push({
258
- level: "error",
259
- message: `bindings.items['${bindingId}'].route references unknown route '${binding.route}'`,
260
- });
261
- }
262
-
263
- if (isPlaceholderValue(binding.source.id)) {
264
- issues.push({
265
- level: "warning",
266
- message: `bindings.items['${bindingId}'].source.id still uses placeholder value '${binding.source.id}'`,
267
- });
268
- }
269
-
270
- const bindingKey = `${binding.connector}:${binding.source.type}:${binding.source.id}`;
271
- const existingBindingId = seenBindingSources.get(bindingKey);
272
- if (existingBindingId) {
273
- issues.push({
274
- level: "error",
275
- message:
276
- `bindings.items['${bindingId}'] duplicates source '${bindingKey}' already used by bindings.items['${existingBindingId}']`,
277
- });
278
- } else {
279
- seenBindingSources.set(bindingKey, bindingId);
280
- }
281
- }
282
-
283
- if (options.fix) {
284
- const fixTarget = ensureGatewayConfigShape(structuredClone(rawConfig));
285
- const rootDir = resolveDataRootDir(configPath, fixTarget);
286
-
287
- await mkdir(rootDir, { recursive: true });
288
- await mkdir(join(rootDir, "sessions"), { recursive: true });
289
- await mkdir(join(rootDir, "attachments"), { recursive: true });
290
- await mkdir(join(rootDir, "logs"), { recursive: true });
291
- await mkdir(join(rootDir, "state"), { recursive: true });
292
- await mkdir(join(rootDir, "extensions"), { recursive: true });
293
-
294
- const installedSet = new Set(installedExtensions.map((item) => item.packageName));
295
- for (const item of fixTarget.extensions.allowList) {
296
- if (item.enabled && !installedSet.has(item.package)) {
297
- item.enabled = false;
298
- }
299
- }
300
-
301
- setDefaultProviderIfMissingOrInvalid(fixTarget);
302
-
303
- await writeConfigWithValidation(configPath, fixTarget, {
304
- validate: true,
305
- createBackup: true,
306
- });
307
-
308
- console.log("Applied doctor --fix actions:");
309
- console.log("- ensured data directories exist");
310
- console.log("- disabled allowList entries for packages not installed");
311
- console.log("- repaired default provider when missing or invalid");
312
- }
313
-
314
- const errorCount = issues.filter((issue) => issue.level === "error").length;
315
- const warningCount = issues.filter((issue) => issue.level === "warning").length;
316
-
317
- if (issues.length === 0) {
318
- console.log(`Doctor found no issues (${configPath})`);
319
- return;
320
- }
321
-
322
- console.log(`Doctor results (${configPath}):`);
323
- for (const issue of issues) {
324
- console.log(`- [${issue.level}] ${issue.message}`);
325
- }
326
- console.log(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`);
327
-
328
- if (errorCount > 0) {
329
- throw new Error(`Doctor found ${errorCount} error(s)`);
330
- }
331
- }
@@ -1,207 +0,0 @@
1
- import { join } from "node:path";
2
- import { loadGatewayConfig } from "../../core/routing.js";
3
- import type { GatewayConfig } from "../../core/types.js";
4
- import { ExtensionStoreManager } from "../../extension/manager.js";
5
- import {
6
- applyContributionTemplates,
7
- buildContributionTemplates,
8
- ensureGatewayConfigShape,
9
- listContributionIds,
10
- setDefaultProviderIfMissingOrInvalid,
11
- upsertAllowListPackage,
12
- } from "../shared/config-mutators.js";
13
- import { readRawConfig, requireRawConfig, resolveConfigPath, resolveDataRootDir, writeConfigWithValidation } from "../shared/config-io.js";
14
- import type { RawGatewayConfig } from "../shared/config-types.js";
15
- import { resolveExtensionInstallSpecs } from "../shared/local-extension-specs.js";
16
- import { createLogger } from "../shared/runtime.js";
17
-
18
- /**
19
- * Resolves extension store directory from normalized gateway config.
20
- */
21
- function extensionStoreDir(config: GatewayConfig): string {
22
- return join(config.data.rootDir, "extensions");
23
- }
24
-
25
- /**
26
- * Resolves extension store directory directly from raw config plus config file location.
27
- */
28
- function extensionStoreDirFromRaw(configPath: string, rawConfig: RawGatewayConfig): string {
29
- const rootDir = resolveDataRootDir(configPath, rawConfig);
30
- return join(rootDir, "extensions");
31
- }
32
-
33
- /**
34
- * Installs an extension package, optionally enabling it in config and creating instance templates.
35
- */
36
- export async function runExtensionInstallCommand(options: {
37
- spec: string;
38
- enable?: boolean;
39
- json?: boolean;
40
- }): Promise<void> {
41
- const configPath = resolveConfigPath();
42
- const logger = createLogger();
43
-
44
- const rawConfig = (await readRawConfig(configPath)) ?? {};
45
- const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
46
- const [resolvedSpec] = await resolveExtensionInstallSpecs([options.spec]);
47
- const installed = await manager.install(resolvedSpec ?? options.spec);
48
-
49
- if (!options.enable) {
50
- const templates = buildContributionTemplates(installed.manifest.contributions);
51
- if (options.json) {
52
- console.log(
53
- JSON.stringify(
54
- {
55
- package: installed.packageName,
56
- version: installed.version,
57
- contributions: installed.manifest.contributions,
58
- templates,
59
- },
60
- null,
61
- 2,
62
- ),
63
- );
64
- return;
65
- }
66
-
67
- console.log(`Installed ${installed.packageName}@${installed.version}`);
68
- console.log("Contributions:");
69
- for (const contribution of installed.manifest.contributions) {
70
- console.log(`- ${contribution.kind}:${contribution.id} (${contribution.entry})`);
71
- }
72
-
73
- console.log("");
74
- console.log("allowList template:");
75
- console.log(JSON.stringify({ package: installed.packageName, enabled: true }, null, 2));
76
-
77
- console.log("");
78
- console.log("instances template:");
79
- console.log(JSON.stringify(templates, null, 2));
80
- return;
81
- }
82
-
83
- const next = ensureGatewayConfigShape(structuredClone(rawConfig));
84
- upsertAllowListPackage(next, installed.packageName, true);
85
-
86
- const templates = buildContributionTemplates(installed.manifest.contributions);
87
- const addedInstanceIds = applyContributionTemplates(next, templates);
88
- setDefaultProviderIfMissingOrInvalid(next);
89
-
90
- await writeConfigWithValidation(configPath, next, {
91
- validate: true,
92
- createBackup: true,
93
- });
94
-
95
- if (options.json) {
96
- console.log(
97
- JSON.stringify(
98
- {
99
- package: installed.packageName,
100
- version: installed.version,
101
- enabled: true,
102
- addedInstanceIds,
103
- },
104
- null,
105
- 2,
106
- ),
107
- );
108
- return;
109
- }
110
-
111
- console.log(`Installed and enabled ${installed.packageName}@${installed.version}`);
112
- console.log(`Updated ${configPath}`);
113
- if (addedInstanceIds.providers.length > 0 || addedInstanceIds.connectors.length > 0 || addedInstanceIds.sandboxes.length > 0) {
114
- console.log("Added instances:");
115
- for (const providerId of addedInstanceIds.providers) {
116
- console.log(`- provider: ${providerId}`);
117
- }
118
- for (const connectorId of addedInstanceIds.connectors) {
119
- console.log(`- connector: ${connectorId}`);
120
- }
121
- for (const sandboxId of addedInstanceIds.sandboxes) {
122
- console.log(`- sandbox: ${sandboxId}`);
123
- }
124
- }
125
- }
126
-
127
- /**
128
- * Uninstalls an extension package from store without mutating config references.
129
- */
130
- export async function runExtensionUninstallCommand(options: {
131
- packageName: string;
132
- }): Promise<void> {
133
- const configPath = resolveConfigPath();
134
- const logger = createLogger();
135
- const rawConfig = await requireRawConfig(configPath);
136
- const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
137
-
138
- await manager.uninstall(options.packageName);
139
- console.log(`Uninstalled ${options.packageName}`);
140
- console.log("Remember to remove this package from extensions.allowList and related instance references.");
141
- }
142
-
143
- /**
144
- * Lists installed extension packages with enablement and contribution reference status.
145
- */
146
- export async function runExtensionListCommand(options: {
147
- json?: boolean;
148
- }): Promise<void> {
149
- const configPath = resolveConfigPath();
150
- const logger = createLogger();
151
-
152
- const rawConfig = await requireRawConfig(configPath);
153
- const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
154
- const listed = await manager.listInstalled();
155
-
156
- const normalized = ensureGatewayConfigShape(rawConfig);
157
- const allowList = new Map((normalized.extensions?.allowList ?? []).map((item) => [item.package, item.enabled]));
158
-
159
- const configuredContributionIds = listContributionIds(normalized);
160
- const configuredContributionSet = new Set([
161
- ...configuredContributionIds.providers,
162
- ...configuredContributionIds.connectors,
163
- ...configuredContributionIds.sandboxes,
164
- ]);
165
-
166
- const items = listed.map((item) => {
167
- const contributions = item.manifest?.contributions ?? [];
168
- const referencedContributions = contributions
169
- .filter((contribution) => configuredContributionSet.has(contribution.id))
170
- .map((contribution) => `${contribution.kind}:${contribution.id}`);
171
-
172
- return {
173
- package: item.packageName,
174
- version: item.version,
175
- enabled: allowList.get(item.packageName) ?? false,
176
- contributions: contributions.map((contribution) => `${contribution.kind}:${contribution.id}`),
177
- referencedContributions,
178
- ...(item.error ? { error: item.error } : {}),
179
- };
180
- });
181
-
182
- if (options.json) {
183
- console.log(JSON.stringify({ configPath, items }, null, 2));
184
- return;
185
- }
186
-
187
- if (items.length === 0) {
188
- const config = await loadGatewayConfig(configPath);
189
- console.log(`No extensions installed in ${extensionStoreDir(config)}`);
190
- return;
191
- }
192
-
193
- console.log(`Extensions in ${extensionStoreDirFromRaw(configPath, normalized)}:`);
194
- for (const item of items) {
195
- const suffix = item.enabled ? "enabled" : "disabled";
196
- if (item.error) {
197
- console.log(`- ${item.package}@${item.version} (${suffix}, invalid: ${item.error})`);
198
- continue;
199
- }
200
-
201
- console.log(`- ${item.package}@${item.version} (${suffix})`);
202
- for (const contribution of item.contributions) {
203
- const isReferenced = item.referencedContributions.includes(contribution);
204
- console.log(` * ${contribution}${isReferenced ? " [referenced]" : ""}`);
205
- }
206
- }
207
- }