@dobby.ai/dobby 0.1.0 → 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.
- package/README.md +84 -39
- package/dist/src/agent/event-forwarder.js +185 -16
- package/dist/src/cli/commands/cron.js +39 -35
- package/dist/src/cli/commands/doctor.js +81 -2
- package/dist/src/cli/commands/extension.js +3 -1
- package/dist/src/cli/commands/init.js +43 -173
- package/dist/src/cli/commands/topology.js +38 -14
- package/dist/src/cli/program.js +15 -137
- package/dist/src/cli/shared/config-io.js +3 -31
- package/dist/src/cli/shared/config-mutators.js +33 -9
- package/dist/src/cli/shared/configure-sections.js +52 -12
- package/dist/src/cli/shared/init-catalog.js +89 -46
- package/dist/src/cli/shared/local-extension-specs.js +85 -0
- package/dist/src/cli/shared/schema-prompts.js +26 -2
- package/dist/src/core/gateway.js +3 -1
- package/dist/src/core/routing.js +53 -38
- package/dist/src/core/types.js +2 -0
- package/dist/src/cron/config.js +2 -2
- package/dist/src/cron/service.js +87 -23
- package/dist/src/cron/store.js +1 -1
- package/dist/src/main.js +0 -0
- package/dist/src/shared/dobby-repo.js +40 -0
- package/package.json +11 -4
- package/.env.example +0 -9
- package/AGENTS.md +0 -267
- package/ROADMAP.md +0 -34
- package/config/cron.example.json +0 -9
- package/config/gateway.example.json +0 -128
- package/config/models.custom.example.json +0 -27
- package/dist/src/agent/tests/event-forwarder.test.js +0 -113
- package/dist/src/cli/shared/config-path.js +0 -207
- package/dist/src/cli/shared/init-models-file.js +0 -65
- package/dist/src/cli/shared/presets.js +0 -86
- package/dist/src/cli/tests/config-command.test.js +0 -42
- package/dist/src/cli/tests/config-io.test.js +0 -64
- package/dist/src/cli/tests/config-mutators.test.js +0 -47
- package/dist/src/cli/tests/config-path.test.js +0 -21
- package/dist/src/cli/tests/discord-config.test.js +0 -23
- package/dist/src/cli/tests/doctor.test.js +0 -107
- package/dist/src/cli/tests/init-catalog.test.js +0 -87
- package/dist/src/cli/tests/presets.test.js +0 -41
- package/dist/src/cli/tests/program-options.test.js +0 -92
- package/dist/src/cli/tests/routing-config.test.js +0 -199
- package/dist/src/cli/tests/routing-legacy.test.js +0 -191
- package/dist/src/core/tests/control-command.test.js +0 -17
- package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
- package/dist/src/core/tests/runtime-registry.test.js +0 -116
- package/dist/src/core/tests/typing-controller.test.js +0 -103
- package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
- package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
- package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
- package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
- package/docs/MVP.md +0 -135
- package/docs/RUNBOOK.md +0 -242
- package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
- package/plugins/connector-discord/dobby.manifest.json +0 -18
- package/plugins/connector-discord/index.js +0 -1
- package/plugins/connector-discord/package-lock.json +0 -360
- package/plugins/connector-discord/package.json +0 -38
- package/plugins/connector-discord/src/connector.ts +0 -350
- package/plugins/connector-discord/src/contribution.ts +0 -21
- package/plugins/connector-discord/src/mapper.ts +0 -102
- package/plugins/connector-discord/tsconfig.json +0 -19
- package/plugins/connector-feishu/dobby.manifest.json +0 -18
- package/plugins/connector-feishu/index.js +0 -1
- package/plugins/connector-feishu/package-lock.json +0 -618
- package/plugins/connector-feishu/package.json +0 -38
- package/plugins/connector-feishu/src/connector.ts +0 -343
- package/plugins/connector-feishu/src/contribution.ts +0 -26
- package/plugins/connector-feishu/src/mapper.ts +0 -401
- package/plugins/connector-feishu/tsconfig.json +0 -19
- package/plugins/plugin-sdk/index.d.ts +0 -261
- package/plugins/plugin-sdk/index.js +0 -1
- package/plugins/plugin-sdk/package-lock.json +0 -12
- package/plugins/plugin-sdk/package.json +0 -22
- package/plugins/provider-claude/dobby.manifest.json +0 -17
- package/plugins/provider-claude/index.js +0 -1
- package/plugins/provider-claude/package-lock.json +0 -3398
- package/plugins/provider-claude/package.json +0 -39
- package/plugins/provider-claude/src/contribution.ts +0 -1018
- package/plugins/provider-claude/tsconfig.json +0 -19
- package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
- package/plugins/provider-claude-cli/index.js +0 -1
- package/plugins/provider-claude-cli/package-lock.json +0 -2898
- package/plugins/provider-claude-cli/package.json +0 -38
- package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
- package/plugins/provider-claude-cli/tsconfig.json +0 -19
- package/plugins/provider-pi/dobby.manifest.json +0 -17
- package/plugins/provider-pi/index.js +0 -1
- package/plugins/provider-pi/package-lock.json +0 -3877
- package/plugins/provider-pi/package.json +0 -40
- package/plugins/provider-pi/src/contribution.ts +0 -476
- package/plugins/provider-pi/tsconfig.json +0 -19
- package/plugins/sandbox-core/boxlite.js +0 -1
- package/plugins/sandbox-core/dobby.manifest.json +0 -17
- package/plugins/sandbox-core/docker.js +0 -1
- package/plugins/sandbox-core/package-lock.json +0 -136
- package/plugins/sandbox-core/package.json +0 -39
- package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
- package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
- package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
- package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
- package/plugins/sandbox-core/src/docker-executor.ts +0 -217
- package/plugins/sandbox-core/tsconfig.json +0 -19
- package/scripts/local-extensions.mjs +0 -168
- package/src/agent/event-forwarder.ts +0 -414
- package/src/cli/commands/config.ts +0 -328
- package/src/cli/commands/configure.ts +0 -92
- package/src/cli/commands/cron.ts +0 -410
- package/src/cli/commands/doctor.ts +0 -230
- package/src/cli/commands/extension.ts +0 -205
- package/src/cli/commands/init.ts +0 -396
- package/src/cli/commands/start.ts +0 -223
- package/src/cli/commands/topology.ts +0 -383
- package/src/cli/index.ts +0 -9
- package/src/cli/program.ts +0 -465
- package/src/cli/shared/config-io.ts +0 -277
- package/src/cli/shared/config-mutators.ts +0 -440
- package/src/cli/shared/config-schema.ts +0 -228
- package/src/cli/shared/config-types.ts +0 -121
- package/src/cli/shared/configure-sections.ts +0 -551
- package/src/cli/shared/discord-config.ts +0 -14
- package/src/cli/shared/init-catalog.ts +0 -189
- package/src/cli/shared/init-models-file.ts +0 -77
- package/src/cli/shared/runtime.ts +0 -33
- package/src/cli/shared/schema-prompts.ts +0 -414
- package/src/cli/tests/config-command.test.ts +0 -56
- package/src/cli/tests/config-io.test.ts +0 -92
- package/src/cli/tests/config-mutators.test.ts +0 -59
- package/src/cli/tests/doctor.test.ts +0 -120
- package/src/cli/tests/init-catalog.test.ts +0 -96
- package/src/cli/tests/program-options.test.ts +0 -113
- package/src/cli/tests/routing-config.test.ts +0 -209
- package/src/core/control-command.ts +0 -12
- package/src/core/dedup-store.ts +0 -103
- package/src/core/gateway.ts +0 -607
- package/src/core/routing.ts +0 -379
- package/src/core/runtime-registry.ts +0 -141
- package/src/core/tests/control-command.test.ts +0 -20
- package/src/core/tests/runtime-registry.test.ts +0 -140
- package/src/core/tests/typing-controller.test.ts +0 -129
- package/src/core/types.ts +0 -318
- package/src/core/typing-controller.ts +0 -119
- package/src/cron/config.ts +0 -154
- package/src/cron/schedule.ts +0 -61
- package/src/cron/service.ts +0 -249
- package/src/cron/store.ts +0 -155
- package/src/cron/types.ts +0 -60
- package/src/extension/loader.ts +0 -145
- package/src/extension/manager.ts +0 -355
- package/src/extension/manifest.ts +0 -26
- package/src/extension/registry.ts +0 -229
- package/src/main.ts +0 -8
- package/src/sandbox/executor.ts +0 -44
- package/src/sandbox/host-executor.ts +0 -118
- package/tsconfig.json +0 -18
|
@@ -1,205 +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 { createLogger } from "../shared/runtime.js";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resolves extension store directory from normalized gateway config.
|
|
19
|
-
*/
|
|
20
|
-
function extensionStoreDir(config: GatewayConfig): string {
|
|
21
|
-
return join(config.data.rootDir, "extensions");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Resolves extension store directory directly from raw config plus config file location.
|
|
26
|
-
*/
|
|
27
|
-
function extensionStoreDirFromRaw(configPath: string, rawConfig: RawGatewayConfig): string {
|
|
28
|
-
const rootDir = resolveDataRootDir(configPath, rawConfig);
|
|
29
|
-
return join(rootDir, "extensions");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Installs an extension package, optionally enabling it in config and creating instance templates.
|
|
34
|
-
*/
|
|
35
|
-
export async function runExtensionInstallCommand(options: {
|
|
36
|
-
spec: string;
|
|
37
|
-
enable?: boolean;
|
|
38
|
-
json?: boolean;
|
|
39
|
-
}): Promise<void> {
|
|
40
|
-
const configPath = resolveConfigPath();
|
|
41
|
-
const logger = createLogger();
|
|
42
|
-
|
|
43
|
-
const rawConfig = (await readRawConfig(configPath)) ?? {};
|
|
44
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
45
|
-
const installed = await manager.install(options.spec);
|
|
46
|
-
|
|
47
|
-
if (!options.enable) {
|
|
48
|
-
const templates = buildContributionTemplates(installed.manifest.contributions);
|
|
49
|
-
if (options.json) {
|
|
50
|
-
console.log(
|
|
51
|
-
JSON.stringify(
|
|
52
|
-
{
|
|
53
|
-
package: installed.packageName,
|
|
54
|
-
version: installed.version,
|
|
55
|
-
contributions: installed.manifest.contributions,
|
|
56
|
-
templates,
|
|
57
|
-
},
|
|
58
|
-
null,
|
|
59
|
-
2,
|
|
60
|
-
),
|
|
61
|
-
);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log(`Installed ${installed.packageName}@${installed.version}`);
|
|
66
|
-
console.log("Contributions:");
|
|
67
|
-
for (const contribution of installed.manifest.contributions) {
|
|
68
|
-
console.log(`- ${contribution.kind}:${contribution.id} (${contribution.entry})`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log("");
|
|
72
|
-
console.log("allowList template:");
|
|
73
|
-
console.log(JSON.stringify({ package: installed.packageName, enabled: true }, null, 2));
|
|
74
|
-
|
|
75
|
-
console.log("");
|
|
76
|
-
console.log("instances template:");
|
|
77
|
-
console.log(JSON.stringify(templates, null, 2));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const next = ensureGatewayConfigShape(structuredClone(rawConfig));
|
|
82
|
-
upsertAllowListPackage(next, installed.packageName, true);
|
|
83
|
-
|
|
84
|
-
const templates = buildContributionTemplates(installed.manifest.contributions);
|
|
85
|
-
const addedInstanceIds = applyContributionTemplates(next, templates);
|
|
86
|
-
setDefaultProviderIfMissingOrInvalid(next);
|
|
87
|
-
|
|
88
|
-
await writeConfigWithValidation(configPath, next, {
|
|
89
|
-
validate: true,
|
|
90
|
-
createBackup: true,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
if (options.json) {
|
|
94
|
-
console.log(
|
|
95
|
-
JSON.stringify(
|
|
96
|
-
{
|
|
97
|
-
package: installed.packageName,
|
|
98
|
-
version: installed.version,
|
|
99
|
-
enabled: true,
|
|
100
|
-
addedInstanceIds,
|
|
101
|
-
},
|
|
102
|
-
null,
|
|
103
|
-
2,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log(`Installed and enabled ${installed.packageName}@${installed.version}`);
|
|
110
|
-
console.log(`Updated ${configPath}`);
|
|
111
|
-
if (addedInstanceIds.providers.length > 0 || addedInstanceIds.connectors.length > 0 || addedInstanceIds.sandboxes.length > 0) {
|
|
112
|
-
console.log("Added instances:");
|
|
113
|
-
for (const providerId of addedInstanceIds.providers) {
|
|
114
|
-
console.log(`- provider: ${providerId}`);
|
|
115
|
-
}
|
|
116
|
-
for (const connectorId of addedInstanceIds.connectors) {
|
|
117
|
-
console.log(`- connector: ${connectorId}`);
|
|
118
|
-
}
|
|
119
|
-
for (const sandboxId of addedInstanceIds.sandboxes) {
|
|
120
|
-
console.log(`- sandbox: ${sandboxId}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Uninstalls an extension package from store without mutating config references.
|
|
127
|
-
*/
|
|
128
|
-
export async function runExtensionUninstallCommand(options: {
|
|
129
|
-
packageName: string;
|
|
130
|
-
}): Promise<void> {
|
|
131
|
-
const configPath = resolveConfigPath();
|
|
132
|
-
const logger = createLogger();
|
|
133
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
134
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
135
|
-
|
|
136
|
-
await manager.uninstall(options.packageName);
|
|
137
|
-
console.log(`Uninstalled ${options.packageName}`);
|
|
138
|
-
console.log("Remember to remove this package from extensions.allowList and related instance references.");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Lists installed extension packages with enablement and contribution reference status.
|
|
143
|
-
*/
|
|
144
|
-
export async function runExtensionListCommand(options: {
|
|
145
|
-
json?: boolean;
|
|
146
|
-
}): Promise<void> {
|
|
147
|
-
const configPath = resolveConfigPath();
|
|
148
|
-
const logger = createLogger();
|
|
149
|
-
|
|
150
|
-
const rawConfig = await requireRawConfig(configPath);
|
|
151
|
-
const manager = new ExtensionStoreManager(logger, extensionStoreDirFromRaw(configPath, rawConfig));
|
|
152
|
-
const listed = await manager.listInstalled();
|
|
153
|
-
|
|
154
|
-
const normalized = ensureGatewayConfigShape(rawConfig);
|
|
155
|
-
const allowList = new Map((normalized.extensions?.allowList ?? []).map((item) => [item.package, item.enabled]));
|
|
156
|
-
|
|
157
|
-
const configuredContributionIds = listContributionIds(normalized);
|
|
158
|
-
const configuredContributionSet = new Set([
|
|
159
|
-
...configuredContributionIds.providers,
|
|
160
|
-
...configuredContributionIds.connectors,
|
|
161
|
-
...configuredContributionIds.sandboxes,
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
const items = listed.map((item) => {
|
|
165
|
-
const contributions = item.manifest?.contributions ?? [];
|
|
166
|
-
const referencedContributions = contributions
|
|
167
|
-
.filter((contribution) => configuredContributionSet.has(contribution.id))
|
|
168
|
-
.map((contribution) => `${contribution.kind}:${contribution.id}`);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
package: item.packageName,
|
|
172
|
-
version: item.version,
|
|
173
|
-
enabled: allowList.get(item.packageName) ?? false,
|
|
174
|
-
contributions: contributions.map((contribution) => `${contribution.kind}:${contribution.id}`),
|
|
175
|
-
referencedContributions,
|
|
176
|
-
...(item.error ? { error: item.error } : {}),
|
|
177
|
-
};
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
if (options.json) {
|
|
181
|
-
console.log(JSON.stringify({ configPath, items }, null, 2));
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (items.length === 0) {
|
|
186
|
-
const config = await loadGatewayConfig(configPath);
|
|
187
|
-
console.log(`No extensions installed in ${extensionStoreDir(config)}`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
console.log(`Extensions in ${extensionStoreDirFromRaw(configPath, normalized)}:`);
|
|
192
|
-
for (const item of items) {
|
|
193
|
-
const suffix = item.enabled ? "enabled" : "disabled";
|
|
194
|
-
if (item.error) {
|
|
195
|
-
console.log(`- ${item.package}@${item.version} (${suffix}, invalid: ${item.error})`);
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
console.log(`- ${item.package}@${item.version} (${suffix})`);
|
|
200
|
-
for (const contribution of item.contributions) {
|
|
201
|
-
const isReferenced = item.referencedContributions.includes(contribution);
|
|
202
|
-
console.log(` * ${contribution}${isReferenced ? " [referenced]" : ""}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
package/src/cli/commands/init.ts
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cancel,
|
|
3
|
-
confirm,
|
|
4
|
-
intro,
|
|
5
|
-
isCancel,
|
|
6
|
-
multiselect,
|
|
7
|
-
note,
|
|
8
|
-
outro,
|
|
9
|
-
password,
|
|
10
|
-
select,
|
|
11
|
-
spinner,
|
|
12
|
-
text,
|
|
13
|
-
} from "@clack/prompts";
|
|
14
|
-
import { ExtensionStoreManager } from "../../extension/manager.js";
|
|
15
|
-
import {
|
|
16
|
-
ensureGatewayConfigShape,
|
|
17
|
-
upsertAllowListPackage,
|
|
18
|
-
upsertBinding,
|
|
19
|
-
upsertConnectorInstance,
|
|
20
|
-
upsertProviderInstance,
|
|
21
|
-
upsertRoute,
|
|
22
|
-
} from "../shared/config-mutators.js";
|
|
23
|
-
import { DEFAULT_DISCORD_BOT_NAME } from "../shared/discord-config.js";
|
|
24
|
-
import { applyAndValidateContributionSchemas, loadContributionSchemaCatalog } from "../shared/config-schema.js";
|
|
25
|
-
import {
|
|
26
|
-
readRawConfig,
|
|
27
|
-
resolveConfigPath,
|
|
28
|
-
resolveDataRootDir,
|
|
29
|
-
writeConfigWithValidation,
|
|
30
|
-
} from "../shared/config-io.js";
|
|
31
|
-
import {
|
|
32
|
-
createInitSelectionConfig,
|
|
33
|
-
isInitConnectorChoiceId,
|
|
34
|
-
isInitProviderChoiceId,
|
|
35
|
-
listInitConnectorChoices,
|
|
36
|
-
listInitProviderChoices,
|
|
37
|
-
type InitConnectorChoiceId,
|
|
38
|
-
type InitProviderChoiceId,
|
|
39
|
-
} from "../shared/init-catalog.js";
|
|
40
|
-
import { ensureProviderPiModelsFile } from "../shared/init-models-file.js";
|
|
41
|
-
import { createLogger } from "../shared/runtime.js";
|
|
42
|
-
import { promptConfigFromSchema } from "../shared/schema-prompts.js";
|
|
43
|
-
|
|
44
|
-
interface InitInput {
|
|
45
|
-
providerChoiceIds: InitProviderChoiceId[];
|
|
46
|
-
routeProviderChoiceId: InitProviderChoiceId;
|
|
47
|
-
connectorChoiceId: InitConnectorChoiceId;
|
|
48
|
-
projectRoot: string;
|
|
49
|
-
channelId: string;
|
|
50
|
-
routeId: string;
|
|
51
|
-
botName: string;
|
|
52
|
-
botToken: string;
|
|
53
|
-
allowAllMessages: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Repeatedly prompts for non-empty text input and aborts cleanly on cancel.
|
|
58
|
-
*/
|
|
59
|
-
async function promptRequiredText(params: {
|
|
60
|
-
message: string;
|
|
61
|
-
placeholder?: string;
|
|
62
|
-
initialValue?: string;
|
|
63
|
-
}): Promise<string> {
|
|
64
|
-
while (true) {
|
|
65
|
-
const promptOptions = {
|
|
66
|
-
message: params.message,
|
|
67
|
-
...(params.placeholder !== undefined ? { placeholder: params.placeholder } : {}),
|
|
68
|
-
...(params.initialValue !== undefined ? { initialValue: params.initialValue } : {}),
|
|
69
|
-
};
|
|
70
|
-
const result = await text(promptOptions);
|
|
71
|
-
if (isCancel(result)) {
|
|
72
|
-
cancel("Initialization cancelled.");
|
|
73
|
-
throw new Error("Initialization cancelled.");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const value = String(result ?? "").trim();
|
|
77
|
-
if (value.length > 0) {
|
|
78
|
-
return value;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
await note("This field is required.", "Validation");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Collects init inputs from interactive prompts.
|
|
87
|
-
*/
|
|
88
|
-
async function collectInitInput(): Promise<InitInput> {
|
|
89
|
-
intro("dobby init");
|
|
90
|
-
|
|
91
|
-
const providerChoices = listInitProviderChoices();
|
|
92
|
-
const providerChoiceResult = await multiselect({
|
|
93
|
-
message: "Choose provider(s) (space to select multiple)",
|
|
94
|
-
options: providerChoices.map((item) => ({
|
|
95
|
-
value: item.id,
|
|
96
|
-
label: item.label,
|
|
97
|
-
})),
|
|
98
|
-
initialValues: ["provider.pi"],
|
|
99
|
-
required: true,
|
|
100
|
-
});
|
|
101
|
-
if (isCancel(providerChoiceResult)) {
|
|
102
|
-
cancel("Initialization cancelled.");
|
|
103
|
-
throw new Error("Initialization cancelled.");
|
|
104
|
-
}
|
|
105
|
-
const providerChoiceIds = (providerChoiceResult as unknown[]).map((value) => String(value));
|
|
106
|
-
if (providerChoiceIds.length === 0) {
|
|
107
|
-
throw new Error("At least one provider must be selected");
|
|
108
|
-
}
|
|
109
|
-
if (!providerChoiceIds.every((providerChoiceId) => isInitProviderChoiceId(providerChoiceId))) {
|
|
110
|
-
const invalidChoice = providerChoiceIds.find((providerChoiceId) => !isInitProviderChoiceId(providerChoiceId));
|
|
111
|
-
throw new Error(`Unsupported provider choice '${invalidChoice}'`);
|
|
112
|
-
}
|
|
113
|
-
const providerChoicesById = new Map(providerChoices.map((choice) => [choice.id, choice]));
|
|
114
|
-
|
|
115
|
-
let routeProviderChoiceId = providerChoiceIds[0] as InitProviderChoiceId;
|
|
116
|
-
if (providerChoiceIds.length > 1) {
|
|
117
|
-
const routeProviderChoiceResult = await select({
|
|
118
|
-
message: "Choose provider for the default route",
|
|
119
|
-
options: providerChoiceIds.map((providerChoiceId) => ({
|
|
120
|
-
value: providerChoiceId,
|
|
121
|
-
label: providerChoicesById.get(providerChoiceId as InitProviderChoiceId)?.label ?? providerChoiceId,
|
|
122
|
-
})),
|
|
123
|
-
initialValue: providerChoiceIds[0],
|
|
124
|
-
});
|
|
125
|
-
if (isCancel(routeProviderChoiceResult)) {
|
|
126
|
-
cancel("Initialization cancelled.");
|
|
127
|
-
throw new Error("Initialization cancelled.");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const routeProviderCandidate = String(routeProviderChoiceResult);
|
|
131
|
-
if (!isInitProviderChoiceId(routeProviderCandidate) || !providerChoiceIds.includes(routeProviderCandidate)) {
|
|
132
|
-
throw new Error(`Unsupported route provider choice '${routeProviderCandidate}'`);
|
|
133
|
-
}
|
|
134
|
-
routeProviderChoiceId = routeProviderCandidate;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const connectorChoices = listInitConnectorChoices();
|
|
138
|
-
const connectorChoiceResult = await select({
|
|
139
|
-
message: "Choose connector",
|
|
140
|
-
options: connectorChoices.map((item) => ({
|
|
141
|
-
value: item.id,
|
|
142
|
-
label: item.label,
|
|
143
|
-
})),
|
|
144
|
-
initialValue: "connector.discord",
|
|
145
|
-
});
|
|
146
|
-
if (isCancel(connectorChoiceResult)) {
|
|
147
|
-
cancel("Initialization cancelled.");
|
|
148
|
-
throw new Error("Initialization cancelled.");
|
|
149
|
-
}
|
|
150
|
-
const connectorChoiceId = String(connectorChoiceResult);
|
|
151
|
-
if (!isInitConnectorChoiceId(connectorChoiceId)) {
|
|
152
|
-
throw new Error(`Unsupported connector choice '${connectorChoiceId}'`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const projectRoot = await promptRequiredText({
|
|
156
|
-
message: "Project root",
|
|
157
|
-
initialValue: process.cwd(),
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const channelId = await promptRequiredText({
|
|
161
|
-
message: "Discord channel ID",
|
|
162
|
-
placeholder: "1234567890",
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const routeIdResult = await text({
|
|
166
|
-
message: "Route ID",
|
|
167
|
-
initialValue: "main",
|
|
168
|
-
});
|
|
169
|
-
if (isCancel(routeIdResult)) {
|
|
170
|
-
cancel("Initialization cancelled.");
|
|
171
|
-
throw new Error("Initialization cancelled.");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const botNameResult = await text({
|
|
175
|
-
message: "Discord bot name",
|
|
176
|
-
initialValue: DEFAULT_DISCORD_BOT_NAME,
|
|
177
|
-
});
|
|
178
|
-
if (isCancel(botNameResult)) {
|
|
179
|
-
cancel("Initialization cancelled.");
|
|
180
|
-
throw new Error("Initialization cancelled.");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const botTokenResult = await password({
|
|
184
|
-
message: "Discord bot token",
|
|
185
|
-
mask: "*",
|
|
186
|
-
validate: (value) => (value.trim().length > 0 ? undefined : "Token is required"),
|
|
187
|
-
});
|
|
188
|
-
if (isCancel(botTokenResult)) {
|
|
189
|
-
cancel("Initialization cancelled.");
|
|
190
|
-
throw new Error("Initialization cancelled.");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const allowAllMessagesResult = await confirm({
|
|
194
|
-
message: "Allow all group messages (not mention-only)?",
|
|
195
|
-
initialValue: false,
|
|
196
|
-
});
|
|
197
|
-
if (isCancel(allowAllMessagesResult)) {
|
|
198
|
-
cancel("Initialization cancelled.");
|
|
199
|
-
throw new Error("Initialization cancelled.");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
providerChoiceIds: providerChoiceIds as InitProviderChoiceId[],
|
|
204
|
-
routeProviderChoiceId,
|
|
205
|
-
connectorChoiceId,
|
|
206
|
-
projectRoot,
|
|
207
|
-
channelId,
|
|
208
|
-
routeId: String(routeIdResult ?? "").trim() || "main",
|
|
209
|
-
botName: String(botNameResult ?? "").trim() || DEFAULT_DISCORD_BOT_NAME,
|
|
210
|
-
botToken: String(botTokenResult ?? "").trim(),
|
|
211
|
-
allowAllMessages: allowAllMessagesResult === true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Executes first-time initialization: install required extensions, write config, then validate.
|
|
217
|
-
*/
|
|
218
|
-
export async function runInitCommand(): Promise<void> {
|
|
219
|
-
const configPath = resolveConfigPath();
|
|
220
|
-
const existingConfig = await readRawConfig(configPath);
|
|
221
|
-
if (existingConfig) {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`Config '${configPath}' already exists. Use 'dobby config edit' or 'dobby configure' to update existing values.`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const input = await collectInitInput();
|
|
228
|
-
const selected = createInitSelectionConfig(input.providerChoiceIds, input.connectorChoiceId, {
|
|
229
|
-
routeId: input.routeId,
|
|
230
|
-
projectRoot: input.projectRoot,
|
|
231
|
-
allowAllMessages: input.allowAllMessages,
|
|
232
|
-
botName: input.botName,
|
|
233
|
-
botToken: input.botToken,
|
|
234
|
-
channelId: input.channelId,
|
|
235
|
-
routeProviderChoiceId: input.routeProviderChoiceId,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const next = ensureGatewayConfigShape({});
|
|
239
|
-
|
|
240
|
-
const rootDir = resolveDataRootDir(configPath, next);
|
|
241
|
-
const manager = new ExtensionStoreManager(createLogger(), `${rootDir}/extensions`);
|
|
242
|
-
|
|
243
|
-
const installSpinner = spinner();
|
|
244
|
-
installSpinner.start(`Installing required extensions (${selected.extensionPackages.length} packages)`);
|
|
245
|
-
try {
|
|
246
|
-
const installedPackages = await manager.installMany(selected.extensionPackages);
|
|
247
|
-
for (const installed of installedPackages) {
|
|
248
|
-
upsertAllowListPackage(next, installed.packageName, true);
|
|
249
|
-
}
|
|
250
|
-
installSpinner.stop("Extensions installed");
|
|
251
|
-
} catch (error) {
|
|
252
|
-
installSpinner.stop("Extension installation failed");
|
|
253
|
-
throw error;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const catalog = await loadContributionSchemaCatalog(configPath, next);
|
|
257
|
-
const schemaByContributionId = new Map(
|
|
258
|
-
catalog
|
|
259
|
-
.filter((item) => item.configSchema)
|
|
260
|
-
.map((item) => [item.contributionId, item.configSchema!] as const),
|
|
261
|
-
);
|
|
262
|
-
const schemaStateByContributionId = new Map(
|
|
263
|
-
catalog.map((item) => [item.contributionId, item.configSchema ? "with_schema" : "without_schema"] as const),
|
|
264
|
-
);
|
|
265
|
-
const warnedSchemaFallback = new Set<string>();
|
|
266
|
-
const noteSchemaFallback = async (contributionId: string): Promise<"without_schema" | "not_loaded"> => {
|
|
267
|
-
if (warnedSchemaFallback.has(contributionId)) {
|
|
268
|
-
const existingState = schemaStateByContributionId.get(contributionId);
|
|
269
|
-
return existingState === "without_schema" ? "without_schema" : "not_loaded";
|
|
270
|
-
}
|
|
271
|
-
warnedSchemaFallback.add(contributionId);
|
|
272
|
-
|
|
273
|
-
const state = schemaStateByContributionId.get(contributionId);
|
|
274
|
-
if (state === "without_schema") {
|
|
275
|
-
await note(
|
|
276
|
-
`Contribution '${contributionId}' is loaded but does not expose configSchema. Falling back to built-in defaults/JSON.`,
|
|
277
|
-
"Schema",
|
|
278
|
-
);
|
|
279
|
-
return "without_schema";
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
await note(
|
|
283
|
-
`No loaded schema for contribution '${contributionId}'. The extension may be disabled or not installed.`,
|
|
284
|
-
"Schema",
|
|
285
|
-
);
|
|
286
|
-
return "not_loaded";
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const resolveFallbackConfig = async (
|
|
290
|
-
kind: "provider" | "connector",
|
|
291
|
-
instanceId: string,
|
|
292
|
-
contributionId: string,
|
|
293
|
-
fallbackConfig: Record<string, unknown>,
|
|
294
|
-
): Promise<Record<string, unknown>> => {
|
|
295
|
-
const state = await noteSchemaFallback(contributionId);
|
|
296
|
-
if (state === "not_loaded") {
|
|
297
|
-
throw new Error(
|
|
298
|
-
`Cannot initialize ${kind} '${instanceId}' because schema for contribution '${contributionId}' is not loaded. ` +
|
|
299
|
-
`Ensure the extension is installed and enabled, then retry.`,
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
return fallbackConfig;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
for (const provider of selected.providerInstances) {
|
|
306
|
-
const schema = schemaByContributionId.get(provider.contributionId);
|
|
307
|
-
const providerConfig = schema
|
|
308
|
-
? await promptConfigFromSchema(schema, provider.config, {
|
|
309
|
-
title: `Provider '${provider.instanceId}' (${provider.contributionId})`,
|
|
310
|
-
})
|
|
311
|
-
: await resolveFallbackConfig("provider", provider.instanceId, provider.contributionId, provider.config);
|
|
312
|
-
upsertProviderInstance(next, provider.instanceId, provider.contributionId, providerConfig);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const connectorSchema = schemaByContributionId.get(selected.connectorContributionId);
|
|
316
|
-
const connectorConfig = selected.connectorContributionId === "connector.discord"
|
|
317
|
-
? selected.connectorConfig
|
|
318
|
-
: connectorSchema
|
|
319
|
-
? await promptConfigFromSchema(connectorSchema, selected.connectorConfig, {
|
|
320
|
-
title: `Connector '${selected.connectorInstanceId}' (${selected.connectorContributionId})`,
|
|
321
|
-
})
|
|
322
|
-
: await resolveFallbackConfig(
|
|
323
|
-
"connector",
|
|
324
|
-
selected.connectorInstanceId,
|
|
325
|
-
selected.connectorContributionId,
|
|
326
|
-
selected.connectorConfig,
|
|
327
|
-
);
|
|
328
|
-
upsertConnectorInstance(next, selected.connectorInstanceId, selected.connectorContributionId, connectorConfig);
|
|
329
|
-
|
|
330
|
-
next.providers = {
|
|
331
|
-
...next.providers,
|
|
332
|
-
default: selected.providerInstanceId,
|
|
333
|
-
items: next.providers.items,
|
|
334
|
-
};
|
|
335
|
-
next.routes = {
|
|
336
|
-
...next.routes,
|
|
337
|
-
defaults: {
|
|
338
|
-
...next.routes.defaults,
|
|
339
|
-
provider: selected.providerInstanceId,
|
|
340
|
-
},
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
upsertRoute(next, input.routeId, {
|
|
344
|
-
...selected.routeProfile,
|
|
345
|
-
projectRoot: input.projectRoot,
|
|
346
|
-
});
|
|
347
|
-
upsertBinding(next, selected.bindingId, selected.bindingConfig);
|
|
348
|
-
|
|
349
|
-
const validatedConfig = await applyAndValidateContributionSchemas(configPath, next);
|
|
350
|
-
|
|
351
|
-
const createdModelsFiles: string[] = [];
|
|
352
|
-
for (const provider of selected.providerInstances) {
|
|
353
|
-
if (provider.contributionId !== "provider.pi") {
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const resolvedProvider = validatedConfig.providers?.items?.[provider.instanceId];
|
|
358
|
-
const { type: _type, ...providerConfig } = resolvedProvider ?? {};
|
|
359
|
-
const ensured = await ensureProviderPiModelsFile(configPath, Object.keys(providerConfig).length > 0 ? providerConfig : provider.config);
|
|
360
|
-
if (ensured.created) {
|
|
361
|
-
createdModelsFiles.push(ensured.path);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
await writeConfigWithValidation(configPath, validatedConfig, {
|
|
366
|
-
validate: true,
|
|
367
|
-
createBackup: false,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
outro("Initialization completed.");
|
|
371
|
-
|
|
372
|
-
console.log(`Config written: ${configPath}`);
|
|
373
|
-
if (createdModelsFiles.length > 0) {
|
|
374
|
-
console.log("Generated model files:");
|
|
375
|
-
for (const path of createdModelsFiles) {
|
|
376
|
-
console.log(`- ${path}`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
console.log("Next steps:");
|
|
380
|
-
console.log("1. dobby start");
|
|
381
|
-
|
|
382
|
-
const showHint = await confirm({
|
|
383
|
-
message: "Show quick validation commands?",
|
|
384
|
-
initialValue: true,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (!isCancel(showHint) && showHint) {
|
|
388
|
-
await note(
|
|
389
|
-
[
|
|
390
|
-
"dobby extension list",
|
|
391
|
-
"dobby doctor",
|
|
392
|
-
].join("\n"),
|
|
393
|
-
"Validation",
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
}
|