wizards 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -14
- package/apps/runtime/dist/agents/studio-assistant.js +67 -20
- package/apps/runtime/dist/auth/account.js +36 -4
- package/apps/runtime/dist/auth/device.js +101 -0
- package/apps/runtime/dist/auth/index.js +4 -4
- package/apps/runtime/dist/cli/account.js +289 -0
- package/apps/runtime/dist/cli/install-env.js +33 -0
- package/apps/runtime/dist/cli/main.js +17 -3
- package/apps/runtime/dist/cli/menu.js +19 -5
- package/apps/runtime/dist/cli/setup.js +9 -16
- package/apps/runtime/dist/cli/status.js +4 -1
- package/apps/runtime/dist/cli/ui.js +14 -0
- package/apps/runtime/dist/docs/dev/plugins/reference.md +7 -4
- package/apps/runtime/dist/docs/dev/plugins/server.md +3 -1
- package/apps/runtime/dist/docs/dev/plugins/space.md +19 -1
- package/apps/runtime/dist/docs/dev/plugins/studio.md +43 -6
- package/apps/runtime/dist/docs/dev/plugins/styling.md +8 -1
- package/apps/runtime/dist/docs/user/command-line.md +3 -2
- package/apps/runtime/dist/docs/user/create-account.md +47 -0
- package/apps/runtime/dist/docs/user/first-start.md +9 -1
- package/apps/runtime/dist/docs/user/index.md +16 -12
- package/apps/runtime/dist/docs/user/install.md +3 -1
- package/apps/runtime/dist/docs/user/local-only.md +38 -0
- package/apps/runtime/dist/docs/user/meta.json +3 -1
- package/apps/runtime/dist/docs/user/models.md +3 -2
- package/apps/runtime/dist/docs/user/space.md +4 -4
- package/apps/runtime/dist/engine/steps.js +3 -2
- package/apps/runtime/dist/engine/template.js +14 -10
- package/apps/runtime/dist/film/project.js +15 -3
- package/apps/runtime/dist/manage.js +12 -0
- package/apps/runtime/dist/plugins/loader.js +38 -2
- package/apps/runtime/dist/plugins/pro.js +16 -16
- package/apps/runtime/dist/routes/studio.js +69 -3
- package/apps/runtime/dist/services/brand.js +61 -1
- package/apps/runtime/dist/services/cloud.js +10 -10
- package/apps/runtime/dist/services/onboarding.js +152 -0
- package/apps/runtime/dist/services/projects.js +19 -10
- package/apps/runtime/dist/tools/plugin.js +11 -3
- package/apps/web/dist/assets/AboutDialog-CXEpsI6L.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-DsSEkayM.js → ChangelogDialog-Cc2APUGI.js} +1 -1
- package/apps/web/dist/assets/ChatView-B2r4dW_m.js +10 -0
- package/apps/web/dist/assets/EditorPage-CNAYlSms.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-C_x5Kc2j.js → FlowPreview-DzU1nGDb.js} +1 -1
- package/apps/web/dist/assets/{Harness-CvvtSTZC.js → Harness-ALQ9Csm6.js} +2 -2
- package/apps/web/dist/assets/NewWizardChannels-DMPqHkCF.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-pi053e8N.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-TYNX6Umd.js +1 -0
- package/apps/web/dist/assets/ProjectPage-CvEkx_Ux.js +1 -0
- package/apps/web/dist/assets/PublicRunner-BrivKNHo.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-C1UcqOlS.js → ReadOnly-DQPHmvS0.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-3osugizk.js +4 -0
- package/apps/web/dist/assets/{RunnerView-ii-PP5c-.js → RunnerView-Ci4_WzVv.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-DKntf7-9.js +1 -0
- package/apps/web/dist/assets/SetupPage-DZQxEQyk.js +1 -0
- package/apps/web/dist/assets/SharePage-B9l4KthU.js +1 -0
- package/apps/web/dist/assets/changelog-CxdXZo3V.js +1 -0
- package/apps/web/dist/assets/{circle-alert-D-Q6DK7b.js → circle-alert-DCBV7n5k.js} +1 -1
- package/apps/web/dist/assets/{esm-CTna4iL0.js → esm-CuqXcPS3.js} +1 -1
- package/apps/web/dist/assets/index-CmxVazOf.css +1 -0
- package/apps/web/dist/assets/index-j09nLtEs.js +14 -0
- package/apps/web/dist/assets/{lock-DwGN_KOC.js → lock-DlzTfwxP.js} +1 -1
- package/apps/web/dist/assets/{phone-XIVIe17d.js → phone-BFD815nc.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-JuSRq6NP.js → settings-menu-C7VsRpe9.js} +1 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +134 -0
- package/apps/web/dist/assets/{upload-CDxTpAqx.js → upload-BEf5-d6X.js} +1 -1
- package/apps/web/dist/index.html +4 -5
- package/apps/web/dist/install.sh +17 -7
- package/apps/web/dist/mcp/flow.html +2 -2
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +36 -2
- package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +44 -1
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +18 -0
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +18 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +22 -6
- package/node_modules/@engenty-wizards/shared/dist/projects.js +20 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/AboutDialog-ClV4gNiW.js +0 -1
- package/apps/web/dist/assets/ChatView-BdYCnDLk.js +0 -10
- package/apps/web/dist/assets/EditorPage-7PZzkNBT.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-g1qCd_tQ.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-B3hswj5r.js +0 -7
- package/apps/web/dist/assets/ProjectPage-CFDncRxT.js +0 -1
- package/apps/web/dist/assets/PublicRunner-C9N5r-sT.js +0 -1
- package/apps/web/dist/assets/RunDrawer-B8YzFvgz.js +0 -4
- package/apps/web/dist/assets/SettingsPage-ClEP5Q0t.js +0 -1
- package/apps/web/dist/assets/SetupPage-CDFzo6LT.js +0 -1
- package/apps/web/dist/assets/SharePage-BV2S0g84.js +0 -1
- package/apps/web/dist/assets/changelog-wZI-UaUi.js +0 -1
- package/apps/web/dist/assets/index-DUjJx-uk.css +0 -1
- package/apps/web/dist/assets/index-DnQgOKOQ.js +0 -15
- package/apps/web/dist/assets/logo-C4V2_3Ar.js +0 -1
- package/apps/web/dist/assets/ui-DrjXmHdO.js +0 -133
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { accountOverview, knownTeam, linkedAccount, unlink, } from "../auth/account.js";
|
|
3
|
+
import { DeviceSignInError, requestDeviceCode, waitForDeviceSignIn } from "../auth/device.js";
|
|
4
|
+
import { migrateControlDb } from "../db/client.js";
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { featuresFile, fileFeatures, writeFeature } from "../features.js";
|
|
7
|
+
import { command, readInstallNote, writeInstallNote } from "./home.js";
|
|
8
|
+
import { openBrowser, runningRuntime, settings } from "./start.js";
|
|
9
|
+
import { bold, busy, cyan, dim, tilde } from "./ui.js";
|
|
10
|
+
/**
|
|
11
|
+
* The account of this install, from the terminal. `wizards account` says where it stands,
|
|
12
|
+
* `sign-in` runs the device flow (auth/device.ts), `sign-out` ends the link the way the
|
|
13
|
+
* studio does. The setup's first step is the same sign-in. `--local` keeps an install off the
|
|
14
|
+
* account altogether: `account=off` in the install's `.features` hides every account surface
|
|
15
|
+
* (packages/shared/src/feature-flags.ts), and a note in install.json says so.
|
|
16
|
+
*
|
|
17
|
+
* Everything here reads and writes the install's settings and vault through the runtime's own
|
|
18
|
+
* modules, with or without a runtime running (install-env.ts puts this process where the
|
|
19
|
+
* runtime is). A runtime that runs reads the account from the settings as it goes; what it
|
|
20
|
+
* holds from its start is brought in line by a restart, which is offered.
|
|
21
|
+
*/
|
|
22
|
+
export const INTRO = "Your team, its space and your wizards live in the account. This computer joins the space.";
|
|
23
|
+
let prepared = null;
|
|
24
|
+
/** The control database as the runtime keeps it: a fresh install has none until something opens it. */
|
|
25
|
+
function ready() {
|
|
26
|
+
prepared ??= migrateControlDb();
|
|
27
|
+
return prepared;
|
|
28
|
+
}
|
|
29
|
+
/** Set up with `--local`: the install's `.features` says `account=off`. */
|
|
30
|
+
export function isLocalOnly(file = featuresFile()) {
|
|
31
|
+
return fileFeatures(file).account === false;
|
|
32
|
+
}
|
|
33
|
+
/** `--local`: the switch into the file, the note into install.json. */
|
|
34
|
+
export function localOnly(paths, file = featuresFile()) {
|
|
35
|
+
writeFeature("account", false, file);
|
|
36
|
+
writeInstallNote({ ...readInstallNote(paths), local: true }, paths);
|
|
37
|
+
}
|
|
38
|
+
/** A sign-in ends local-only: the switch follows the default again, the note goes. */
|
|
39
|
+
export function joinedAccount(paths, file = featuresFile()) {
|
|
40
|
+
if (isLocalOnly(file)) {
|
|
41
|
+
writeFeature("account", null, file);
|
|
42
|
+
}
|
|
43
|
+
const note = readInstallNote(paths);
|
|
44
|
+
if (note.local) {
|
|
45
|
+
const { local: _, ...rest } = note;
|
|
46
|
+
writeInstallNote(rest, paths);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Where the account stands, from this machine alone: no round trip. */
|
|
50
|
+
export async function accountState() {
|
|
51
|
+
if (isLocalOnly()) {
|
|
52
|
+
return { kind: "local" };
|
|
53
|
+
}
|
|
54
|
+
await ready();
|
|
55
|
+
const account = await linkedAccount();
|
|
56
|
+
if (!account) {
|
|
57
|
+
return { kind: "none" };
|
|
58
|
+
}
|
|
59
|
+
return { kind: "linked", account, team: await knownTeam() };
|
|
60
|
+
}
|
|
61
|
+
/** "Matthias · team Platzer Fenster & Türen · 120 credits", as far as it is known. */
|
|
62
|
+
export function described(account, team, credits = true) {
|
|
63
|
+
const parts = [account.name || account.email];
|
|
64
|
+
if (team) {
|
|
65
|
+
parts.push(`team ${team.name}`);
|
|
66
|
+
if (credits) {
|
|
67
|
+
parts.push(`${team.balanceCredits} credits`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return parts.join(" · ");
|
|
71
|
+
}
|
|
72
|
+
/** One line for the menu and `wizards status`, after the symbol. */
|
|
73
|
+
export function describeAccount(state) {
|
|
74
|
+
switch (state.kind) {
|
|
75
|
+
case "local":
|
|
76
|
+
return "local only";
|
|
77
|
+
case "none":
|
|
78
|
+
return "not signed in";
|
|
79
|
+
default:
|
|
80
|
+
return described(state.account, state.team, false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** The account's site, as people know it: engenty.ai for account.engenty.ai. */
|
|
84
|
+
const host = () => new URL(env.local.accountUrl).host.replace(/^account\./, "");
|
|
85
|
+
/** `wizards account`: where it stands, with the Manage-App's word when it answers. */
|
|
86
|
+
async function status() {
|
|
87
|
+
const state = await accountState();
|
|
88
|
+
if (state.kind === "local") {
|
|
89
|
+
console.log(`Local only (no account). ${dim(`\`${command()} account sign-in\` connects one.`)}`);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
if (state.kind === "none") {
|
|
93
|
+
console.log(`Not signed in. ${dim(`\`${command()} account sign-in\``)}`);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
const overview = await accountOverview();
|
|
97
|
+
const team = overview?.tenant ?? state.team;
|
|
98
|
+
console.log(`Signed in as ${described(state.account, team)} · ${host()}${overview ? "" : dim(` (${host()} did not answer${team ? "; this is from last time" : ""})`)}`);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The device flow on the terminal: the link and the code, the browser opened where there is
|
|
103
|
+
* one, then a wait while the person allows it. Null when it ended without an account.
|
|
104
|
+
*/
|
|
105
|
+
async function deviceSignIn() {
|
|
106
|
+
let code;
|
|
107
|
+
try {
|
|
108
|
+
code = await requestDeviceCode();
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
p.log.error(err.message);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const link = code.verificationUriComplete ?? code.verificationUri;
|
|
115
|
+
p.log.message(`Open ${cyan(link)} and type ${bold(code.userCode)}`);
|
|
116
|
+
if (openBrowser(link)) {
|
|
117
|
+
p.log.message(dim("The page opens in your browser."));
|
|
118
|
+
}
|
|
119
|
+
const spinner = busy("Waiting for you to allow it in the browser");
|
|
120
|
+
try {
|
|
121
|
+
const account = await waitForDeviceSignIn(code);
|
|
122
|
+
const overview = await accountOverview();
|
|
123
|
+
spinner.stop(`Signed in as ${described(account, overview?.tenant ?? null)}`);
|
|
124
|
+
return account;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
spinner.fail(err instanceof DeviceSignInError
|
|
128
|
+
? err.message
|
|
129
|
+
: `The sign-in did not work. ${err.message}`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A runtime that runs reads the account from the settings as it goes, but holds a few things
|
|
135
|
+
* from its start: the Pro modules it loaded, which source the models run on, what was sent to
|
|
136
|
+
* the cloud. A restart brings it in line; the person is asked, or told how.
|
|
137
|
+
*/
|
|
138
|
+
async function pickUp(paths, interactive, why) {
|
|
139
|
+
if (!(await runningRuntime(settings(paths).dataDir))) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const how = `${command()} restart`;
|
|
143
|
+
if (!interactive) {
|
|
144
|
+
p.log.info(`The studio that runs ${why} after ${cyan(how)}.`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const answer = await p.confirm({
|
|
148
|
+
message: `Restart the studio that runs, so it ${why}?`,
|
|
149
|
+
initialValue: true,
|
|
150
|
+
});
|
|
151
|
+
if (p.isCancel(answer) || !answer) {
|
|
152
|
+
p.log.info(dim(`Later: ${how}`));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// status.ts shows the account; loaded here when needed, so neither module waits on the other.
|
|
156
|
+
const { restart } = await import("./status.js");
|
|
157
|
+
await restart(paths);
|
|
158
|
+
}
|
|
159
|
+
const SIGNED_IN = "fetches the Pro modules of your plan and sends what is published to the cloud";
|
|
160
|
+
async function signIn(paths, interactive) {
|
|
161
|
+
await ready();
|
|
162
|
+
const linked = await linkedAccount();
|
|
163
|
+
if (linked) {
|
|
164
|
+
p.log.info(`Already signed in as ${described(linked, await knownTeam(), false)}. ${dim(`To switch: \`${command()} account sign-out\` first.`)}`);
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
const account = await deviceSignIn();
|
|
168
|
+
if (!account) {
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
joinedAccount(paths);
|
|
172
|
+
p.log.message(dim("What you publish here runs in the cloud too."));
|
|
173
|
+
await pickUp(paths, interactive, SIGNED_IN);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
async function signOut(paths, interactive) {
|
|
177
|
+
const state = await accountState();
|
|
178
|
+
if (state.kind !== "linked") {
|
|
179
|
+
console.log(state.kind === "local" ? "Local only: there is no account to sign out." : "Not signed in.");
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
const spinner = busy(`Signing out ${state.account.name || state.account.email}`);
|
|
183
|
+
await unlink();
|
|
184
|
+
// The same as the studio's sign-out (DELETE /api/studio/account): what was sent to the cloud
|
|
185
|
+
// is forgotten, the entitlement goes with the account's plan, and models that ran on its
|
|
186
|
+
// credits run on own keys again. This process never loaded Pro modules or models, so only the
|
|
187
|
+
// settings change here; a runtime that runs lets go of its modules on restart.
|
|
188
|
+
const [{ forgetCloud }, { refreshPro }, models] = await Promise.all([
|
|
189
|
+
import("../services/cloud.js"),
|
|
190
|
+
import("../plugins/pro.js"),
|
|
191
|
+
import("../models.js"),
|
|
192
|
+
]);
|
|
193
|
+
await forgetCloud();
|
|
194
|
+
await refreshPro();
|
|
195
|
+
await models.loadLocalModels();
|
|
196
|
+
if (models.localModelSettings().source === "account") {
|
|
197
|
+
await models.saveLocalModels({ source: "own" });
|
|
198
|
+
}
|
|
199
|
+
spinner.stop("Signed out. Your wizards and results stay on this computer.");
|
|
200
|
+
await pickUp(paths, interactive, "lets go of the account");
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
/** `wizards account [sign-in|sign-out]`. */
|
|
204
|
+
export async function accountCommand(paths, value, options) {
|
|
205
|
+
const interactive = !options.yes && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
206
|
+
switch (value) {
|
|
207
|
+
case "":
|
|
208
|
+
return status();
|
|
209
|
+
case "sign-in":
|
|
210
|
+
case "signin":
|
|
211
|
+
case "login":
|
|
212
|
+
return signIn(paths, interactive);
|
|
213
|
+
case "sign-out":
|
|
214
|
+
case "signout":
|
|
215
|
+
case "logout":
|
|
216
|
+
return signOut(paths, interactive);
|
|
217
|
+
default:
|
|
218
|
+
console.error(`${command()} account [sign-in|sign-out]`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** The menu's "Account": where it stands, and the way in or out. */
|
|
223
|
+
export async function accountMenu(paths) {
|
|
224
|
+
await status();
|
|
225
|
+
const state = await accountState();
|
|
226
|
+
const action = await p.select({
|
|
227
|
+
message: "Account",
|
|
228
|
+
options: [
|
|
229
|
+
state.kind === "linked"
|
|
230
|
+
? { value: "sign-out", label: "Sign out", hint: "your wizards and results stay here" }
|
|
231
|
+
: { value: "sign-in", label: "Sign in", hint: "with a code, in your browser" },
|
|
232
|
+
{ value: "back", label: "Back" },
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
if (p.isCancel(action) || action === "back") {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (action === "sign-in") {
|
|
239
|
+
await signIn(paths, true);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
await signOut(paths, true);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The setup's first step. `--local` keeps the install off the account; else the person is
|
|
247
|
+
* asked to sign in, unless the setup runs without a terminal (`--yes`), which says how to do
|
|
248
|
+
* it later.
|
|
249
|
+
*/
|
|
250
|
+
export async function accountStep(paths, options, interactive) {
|
|
251
|
+
if (options.local) {
|
|
252
|
+
localOnly(paths);
|
|
253
|
+
p.log.warn(`Local only. No account, nothing leaves this computer. Wizards, the space and results stay in ${tilde(settings(paths).dataDir)}. Links answer on this computer only.`);
|
|
254
|
+
p.log.message(dim(`Connect an account any time: ${command()} account`));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const state = await accountState();
|
|
258
|
+
if (state.kind === "local") {
|
|
259
|
+
p.log.info(`Local only (no account). ${dim(`\`${command()} account sign-in\` connects one.`)}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (state.kind === "linked") {
|
|
263
|
+
const overview = await accountOverview();
|
|
264
|
+
p.log.success(`Signed in as ${described(state.account, overview?.tenant ?? state.team)}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
p.note(INTRO, "Your engenty account");
|
|
268
|
+
if (!interactive) {
|
|
269
|
+
p.log.info(dim(`Sign in later: ${command()} account sign-in`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const answer = await p.confirm({ message: "Sign in now?", initialValue: true });
|
|
273
|
+
if (p.isCancel(answer)) {
|
|
274
|
+
p.cancel(`Setup stopped. Run it again with \`${command()} setup\`.`);
|
|
275
|
+
return "cancelled";
|
|
276
|
+
}
|
|
277
|
+
if (!answer) {
|
|
278
|
+
p.log.info(dim(`Later: ${command()} account`));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const account = await deviceSignIn();
|
|
282
|
+
if (!account) {
|
|
283
|
+
p.log.info(dim(`Later: ${command()} account sign-in`));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
joinedAccount(paths);
|
|
287
|
+
p.log.message(dim("What you publish here runs in the cloud too."));
|
|
288
|
+
await pickUp(paths, interactive, SIGNED_IN);
|
|
289
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { layout, readEnvFile } from "./home.js";
|
|
4
|
+
/**
|
|
5
|
+
* Imported first by the command: before the runtime's modules load, this process sees the
|
|
6
|
+
* install the way its runtime does. env.ts reads the process once, when it is first imported,
|
|
7
|
+
* and `.env` from the working directory; the runtime starts in the install's folder (start.ts)
|
|
8
|
+
* and so reads the install's `.env`, with its data folder beside it. The command goes there
|
|
9
|
+
* too — else `wizards account` or `wizards status` from another folder would open a `data/`
|
|
10
|
+
* of its own there, with a secret of its own, and a vault the runtime cannot read.
|
|
11
|
+
*
|
|
12
|
+
* What the terminal exports for other projects never reaches the runtime (environment.ts); a
|
|
13
|
+
* few keys that would make this process another runtime are taken from the install's `.env`
|
|
14
|
+
* alone. The rest the runtime inherits, so the terminal's word stands.
|
|
15
|
+
*/
|
|
16
|
+
const paths = layout();
|
|
17
|
+
const fileEnv = readEnvFile(paths.envFile);
|
|
18
|
+
/** The install's data folder, as start.ts hands it to the runtime: the terminal's word first. */
|
|
19
|
+
const given = process.env.DATA_DIR?.trim() || fileEnv.DATA_DIR?.trim();
|
|
20
|
+
process.env.DATA_DIR = given ? resolve(paths.home, given) : paths.data;
|
|
21
|
+
for (const key of ["MANAGE_URL", "APP_SECRET", "DATABASE_URL", "DATABASE_AUTH_TOKEN"]) {
|
|
22
|
+
process.env[key] = fileEnv[key] ?? "";
|
|
23
|
+
}
|
|
24
|
+
// Keys and the account's sign-in live in the Keychain on a Mac, as the runtime keeps them.
|
|
25
|
+
if (process.platform === "darwin" &&
|
|
26
|
+
process.env.SECRETS === undefined &&
|
|
27
|
+
fileEnv.SECRETS === undefined) {
|
|
28
|
+
process.env.SECRETS = "keychain";
|
|
29
|
+
}
|
|
30
|
+
/** Where the command was typed: `wizards validate ./my.wizard` means a path from there. */
|
|
31
|
+
export const startedIn = process.cwd();
|
|
32
|
+
mkdirSync(paths.home, { recursive: true });
|
|
33
|
+
process.chdir(paths.home);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import "./install-env.js";
|
|
2
|
+
import { accountCommand } from "./account.js";
|
|
1
3
|
import { apps, connectCommand } from "./connect.js";
|
|
2
4
|
import { featuresCommand } from "./features.js";
|
|
3
5
|
import { command, layout, packageVersion, readInstallNote } from "./home.js";
|
|
6
|
+
import { startedIn } from "./install-env.js";
|
|
4
7
|
import { CLIENTS, nodeIsCurrent } from "./machine.js";
|
|
5
8
|
import { mcp } from "./mcp.js";
|
|
6
9
|
import { menu } from "./menu.js";
|
|
@@ -16,7 +19,9 @@ const HELP = `engenty wizards ${packageVersion()}
|
|
|
16
19
|
(needs a terminal)
|
|
17
20
|
${command()} start start it and open the studio; it runs in this terminal until Ctrl-C
|
|
18
21
|
${command()} studio open the studio; when it does not run, it starts as a service first
|
|
19
|
-
${command()} setup guided setup: an AI client to think with, ffmpeg,
|
|
22
|
+
${command()} setup guided setup: your engenty account, an AI client to think with, ffmpeg,
|
|
23
|
+
start at login
|
|
24
|
+
${command()} account your engenty account: status, sign-in, sign-out
|
|
20
25
|
${command()} open let this browser into the running studio (--print: only show the link)
|
|
21
26
|
${command()} status what is installed and what runs
|
|
22
27
|
${command()} doctor status, and what is wrong
|
|
@@ -37,6 +42,7 @@ const HELP = `engenty wizards ${packageVersion()}
|
|
|
37
42
|
checked on import
|
|
38
43
|
|
|
39
44
|
--yes, -y ask nothing and install nothing optional
|
|
45
|
+
--local setup without an account: nothing leaves this computer
|
|
40
46
|
--client <name> install this AI client without asking: ${CLIENTS.map((c) => c.id).join(", ")}
|
|
41
47
|
--no-open start without opening the browser
|
|
42
48
|
--service, -s start as a service, detached from this terminal; \`${command()} stop\` stops it
|
|
@@ -52,6 +58,7 @@ function parse(argv) {
|
|
|
52
58
|
paths: [],
|
|
53
59
|
json: false,
|
|
54
60
|
yes: false,
|
|
61
|
+
local: false,
|
|
55
62
|
open: true,
|
|
56
63
|
service: false,
|
|
57
64
|
print: false,
|
|
@@ -62,6 +69,9 @@ function parse(argv) {
|
|
|
62
69
|
if (arg === "--yes" || arg === "-y") {
|
|
63
70
|
args.yes = true;
|
|
64
71
|
}
|
|
72
|
+
else if (arg === "--local") {
|
|
73
|
+
args.local = true;
|
|
74
|
+
}
|
|
65
75
|
else if (arg === "--no-open") {
|
|
66
76
|
args.open = false;
|
|
67
77
|
}
|
|
@@ -90,7 +100,7 @@ function parse(argv) {
|
|
|
90
100
|
else if (arg.startsWith("-")) {
|
|
91
101
|
return `Unknown option ${arg}.`;
|
|
92
102
|
}
|
|
93
|
-
else if (
|
|
103
|
+
else if (["autostart", "logs", "account"].includes(args.command) && !args.value) {
|
|
94
104
|
args.value = arg;
|
|
95
105
|
}
|
|
96
106
|
else if (["connect", "disconnect", "features"].includes(args.command)) {
|
|
@@ -115,7 +125,7 @@ async function main(argv) {
|
|
|
115
125
|
return 2;
|
|
116
126
|
}
|
|
117
127
|
const paths = layout();
|
|
118
|
-
const setupOptions = { yes: args.yes, clients: args.clients };
|
|
128
|
+
const setupOptions = { yes: args.yes, clients: args.clients, local: args.local };
|
|
119
129
|
if (!args.command) {
|
|
120
130
|
// Without a command: the menu, which needs a terminal. Starting is \`start\`.
|
|
121
131
|
if (args.service || !args.open || args.print) {
|
|
@@ -168,6 +178,8 @@ async function main(argv) {
|
|
|
168
178
|
return update(paths);
|
|
169
179
|
case "autostart":
|
|
170
180
|
return autostart(paths, args.value);
|
|
181
|
+
case "account":
|
|
182
|
+
return accountCommand(paths, args.value, { yes: args.yes });
|
|
171
183
|
case "connect":
|
|
172
184
|
case "disconnect":
|
|
173
185
|
return connectCommand(paths, args.apps, args.command === "disconnect");
|
|
@@ -176,6 +188,8 @@ async function main(argv) {
|
|
|
176
188
|
case "features":
|
|
177
189
|
return featuresCommand(args.apps);
|
|
178
190
|
case "validate":
|
|
191
|
+
// The paths are the person's, from where they typed the command (install-env.ts).
|
|
192
|
+
process.chdir(startedIn);
|
|
179
193
|
return validateCommand(args.paths, args.json);
|
|
180
194
|
default:
|
|
181
195
|
console.error(`Unknown command ${args.command}.\n\n${HELP}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { updateStatus } from "../update.js";
|
|
3
|
+
import { accountMenu, accountState, describeAccount } from "./account.js";
|
|
3
4
|
import { autostartState, setAutostart } from "./autostart.js";
|
|
4
5
|
import { connectCommand } from "./connect.js";
|
|
5
6
|
import { command, packageVersion, readInstallNote } from "./home.js";
|
|
@@ -10,14 +11,15 @@ import { badge, cyan, dim, no, ok } from "./ui.js";
|
|
|
10
11
|
import { update } from "./update.js";
|
|
11
12
|
async function look(paths) {
|
|
12
13
|
const { dataDir } = settings(paths);
|
|
13
|
-
const [running, atLogin, release] = await Promise.all([
|
|
14
|
+
const [running, atLogin, release, account] = await Promise.all([
|
|
14
15
|
runningRuntime(dataDir),
|
|
15
16
|
autostartState(paths),
|
|
16
17
|
updateStatus().catch(() => null),
|
|
18
|
+
accountState(),
|
|
17
19
|
]);
|
|
18
|
-
return { running, atLogin, release };
|
|
20
|
+
return { running, atLogin, release, account };
|
|
19
21
|
}
|
|
20
|
-
function report({ running, atLogin, release }) {
|
|
22
|
+
function report({ running, atLogin, release, account }) {
|
|
21
23
|
const lines = [
|
|
22
24
|
running
|
|
23
25
|
? `${ok} running ${cyan(running.url)} ${dim(`pid ${running.pid}, since ${new Date(running.startedAt).toLocaleString()}`)}`
|
|
@@ -26,12 +28,15 @@ function report({ running, atLogin, release }) {
|
|
|
26
28
|
if (!("unavailable" in atLogin)) {
|
|
27
29
|
lines.push(atLogin.on ? `${ok} starts at login` : `${no} does not start at login`);
|
|
28
30
|
}
|
|
31
|
+
lines.push(account.kind === "linked"
|
|
32
|
+
? `${ok} signed in as ${describeAccount(account)}`
|
|
33
|
+
: `${no} ${describeAccount(account)}`);
|
|
29
34
|
if (release?.newer) {
|
|
30
35
|
lines.push(`${cyan("↑")} ${release.latest} is out ${dim(`(you have ${release.current})`)}`);
|
|
31
36
|
}
|
|
32
37
|
return lines.join("\n");
|
|
33
38
|
}
|
|
34
|
-
function choices({ running, atLogin, release }) {
|
|
39
|
+
function choices({ running, atLogin, release, account }) {
|
|
35
40
|
const options = running
|
|
36
41
|
? [
|
|
37
42
|
{ value: "open", label: "Open the studio", hint: "in your browser" },
|
|
@@ -58,7 +63,13 @@ function choices({ running, atLogin, release }) {
|
|
|
58
63
|
hint: atLogin.on ? "starts at login" : "does not start at login",
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
|
-
options.push({ value: "connect", label: "Connect AI apps", hint: "your wizards in Claude, Cursor, Codex …" }, {
|
|
66
|
+
options.push({ value: "connect", label: "Connect AI apps", hint: "your wizards in Claude, Cursor, Codex …" }, {
|
|
67
|
+
value: "account",
|
|
68
|
+
label: "Account",
|
|
69
|
+
hint: account.kind === "linked"
|
|
70
|
+
? `signed in as ${describeAccount(account)}`
|
|
71
|
+
: describeAccount(account),
|
|
72
|
+
}, { value: "status", label: "Status", hint: "what is installed, and what is wrong" }, { value: "setup", label: "Setup", hint: "account, AI client, ffmpeg, start at login" }, { value: "help", label: "Help", hint: "every command" }, { value: "quit", label: "Quit", hint: running ? "it keeps running" : undefined });
|
|
62
73
|
return options;
|
|
63
74
|
}
|
|
64
75
|
/** How the service runs: whether it starts at login. Esc goes back to the menu. */
|
|
@@ -153,6 +164,9 @@ export async function menu(paths, setupOptions, help) {
|
|
|
153
164
|
case "connect":
|
|
154
165
|
await connectCommand(paths, [], false);
|
|
155
166
|
break;
|
|
167
|
+
case "account":
|
|
168
|
+
await accountMenu(paths);
|
|
169
|
+
break;
|
|
156
170
|
case "status":
|
|
157
171
|
await status(paths, true);
|
|
158
172
|
break;
|
|
@@ -2,6 +2,7 @@ import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
|
+
import { accountStep } from "./account.js";
|
|
5
6
|
import { autostartState, setAutostart } from "./autostart.js";
|
|
6
7
|
import { installClient, installCommand } from "./clients.js";
|
|
7
8
|
import { appStates, connectApps } from "./connect.js";
|
|
@@ -9,21 +10,9 @@ import { runLogged } from "./exec.js";
|
|
|
9
10
|
import { command, installedByScript, isCheckout, packageVersion, readInstallNote, writeInstallNote, } from "./home.js";
|
|
10
11
|
import { CLIENTS, detectClients, findChrome, findFfmpeg, findOnPath, suggestedClient, vendorApp, } from "./machine.js";
|
|
11
12
|
import { runningRuntime, settings } from "./start.js";
|
|
12
|
-
import { badge, cyan, dim, tilde } from "./ui.js";
|
|
13
|
+
import { badge, busy, cyan, dim, tilde } from "./ui.js";
|
|
13
14
|
/** The studio as an app of its own: Chrome (or Edge) installs it from the page. */
|
|
14
15
|
const CHROME_APP = "Tip: in Chrome or Edge the studio installs as an app, in a window of its own with its own icon. The studio offers it at the top; or use the install icon in the address bar.";
|
|
15
|
-
/** A spinner on a terminal; where nobody watches, only what came of it. */
|
|
16
|
-
function busy(message) {
|
|
17
|
-
if (!process.stdout.isTTY) {
|
|
18
|
-
return { stop: (text) => p.log.step(text), fail: (text) => p.log.warn(text) };
|
|
19
|
-
}
|
|
20
|
-
const spinner = p.spinner();
|
|
21
|
-
spinner.start(message);
|
|
22
|
-
return {
|
|
23
|
-
stop: (text) => spinner.stop(text),
|
|
24
|
-
fail: (text) => spinner.error(text),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
16
|
function cancelled(value) {
|
|
28
17
|
if (p.isCancel(value)) {
|
|
29
18
|
p.cancel(`Setup stopped. Run it again with \`${command()} setup\`.`);
|
|
@@ -298,9 +287,10 @@ async function howToUse(paths) {
|
|
|
298
287
|
].join("\n"));
|
|
299
288
|
}
|
|
300
289
|
/**
|
|
301
|
-
* The guided setup:
|
|
302
|
-
* to think with, ffmpeg, starting at login. It
|
|
303
|
-
* start, stop and check it. Returns false
|
|
290
|
+
* The guided setup: the account this computer joins, then what this machine has, what is
|
|
291
|
+
* missing, and what to install — an AI client to think with, ffmpeg, starting at login. It
|
|
292
|
+
* starts nothing; it ends with what runs and how to start, stop and check it. Returns false
|
|
293
|
+
* when it was stopped.
|
|
304
294
|
*/
|
|
305
295
|
export async function setup(paths, options) {
|
|
306
296
|
const interactive = !options.yes && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -310,6 +300,9 @@ export async function setup(paths, options) {
|
|
|
310
300
|
`Install ${tilde(paths.home)}${installedByScript() ? "" : dim(` (data; the code runs from ${isCheckout() ? "this checkout" : "npm"})`)}`,
|
|
311
301
|
`Node ${process.version} ${dim(tilde(process.execPath))}`,
|
|
312
302
|
].join("\n"));
|
|
303
|
+
if ((await accountStep(paths, options, interactive)) === "cancelled") {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
313
306
|
if ((await clientsStep(paths, options, interactive)) === "cancelled") {
|
|
314
307
|
return false;
|
|
315
308
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { accessSync, constants, existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { accountState, describeAccount } from "./account.js";
|
|
3
4
|
import { autostartState, setAutostart } from "./autostart.js";
|
|
4
5
|
import { installedByScript, isCheckout, packageRoot, packageVersion } from "./home.js";
|
|
5
6
|
import { detectClients, findChrome, findFfmpeg, nodeIsCurrent } from "./machine.js";
|
|
@@ -11,11 +12,12 @@ import { bad, badge, cyan, dim, no, ok, tilde } from "./ui.js";
|
|
|
11
12
|
*/
|
|
12
13
|
export async function status(paths, checks) {
|
|
13
14
|
const { dataDir } = settings(paths);
|
|
14
|
-
const [running, clients, ffmpeg, atLogin] = await Promise.all([
|
|
15
|
+
const [running, clients, ffmpeg, atLogin, account] = await Promise.all([
|
|
15
16
|
runningRuntime(dataDir),
|
|
16
17
|
detectClients(paths),
|
|
17
18
|
findFfmpeg(),
|
|
18
19
|
autostartState(paths),
|
|
20
|
+
accountState(),
|
|
19
21
|
]);
|
|
20
22
|
const chrome = findChrome();
|
|
21
23
|
const row = (label, value) => console.log(` ${label.padEnd(9)} ${value}`);
|
|
@@ -31,6 +33,7 @@ export async function status(paths, checks) {
|
|
|
31
33
|
if (!("unavailable" in atLogin)) {
|
|
32
34
|
row("At login", atLogin.on ? `${ok} starts ${dim(`(${atLogin.how})`)}` : `${no} does not start`);
|
|
33
35
|
}
|
|
36
|
+
row("Account", `${account.kind === "linked" ? ok : no} ${describeAccount(account)}`);
|
|
34
37
|
for (const { client, path, version } of clients) {
|
|
35
38
|
row(client.name.split(" ")[0], path ? `${ok} ${version} ${dim(tilde(path))}` : `${no} not installed`);
|
|
36
39
|
}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { styleText } from "node:util";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
3
4
|
const colours = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
4
5
|
/** The name on engenty's orange. */
|
|
5
6
|
export const badge = (text) => colours ? `\x1b[48;2;224;83;27m\x1b[97m\x1b[1m ${text} \x1b[0m` : text;
|
|
6
7
|
export const dim = (text) => styleText("dim", text);
|
|
8
|
+
export const bold = (text) => styleText("bold", text);
|
|
7
9
|
export const cyan = (text) => styleText("cyan", text);
|
|
8
10
|
export const ok = styleText("green", "✓");
|
|
9
11
|
export const no = styleText("yellow", "–");
|
|
10
12
|
export const bad = styleText("red", "✗");
|
|
11
13
|
/** A path as the person would write it. */
|
|
12
14
|
export const tilde = (path) => path.startsWith(homedir()) ? `~${path.slice(homedir().length)}` : path;
|
|
15
|
+
/** A spinner on a terminal; where nobody watches, only what came of it. */
|
|
16
|
+
export function busy(message) {
|
|
17
|
+
if (!process.stdout.isTTY) {
|
|
18
|
+
return { stop: (text) => p.log.step(text), fail: (text) => p.log.warn(text) };
|
|
19
|
+
}
|
|
20
|
+
const spinner = p.spinner();
|
|
21
|
+
spinner.start(message);
|
|
22
|
+
return {
|
|
23
|
+
stop: (text) => spinner.stop(text),
|
|
24
|
+
fail: (text) => spinner.error(text),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Reference
|
|
3
3
|
description: Every call, type, address and setting of the plugin framework on one page.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Server half
|
|
@@ -32,7 +32,8 @@ The default export is a `WizardsPluginFactory`: `(wizards: WizardsPluginApi) =>
|
|
|
32
32
|
| `registerRunner(runner: PluginRunner)` | [Runners](./runners.md). A door a wizard runs through; `page` runners answer at `/w/<token>/<id>` |
|
|
33
33
|
| `runs` | [Runners](./runners.md#drive-a-run). `start`, `report`, `wizards`, `answerPage`, `review`, `answerAsk`, `control`, `subscribe`, `handoffUrl`, `upload`, `transcribe`: the functions behind the MCP tools, for the plugin's door |
|
|
34
34
|
| `speech.conversation()` | [Runners](./runners.md#a-call). `{ provider: "openai", apiKey, model }`: the tenant's realtime speech model, for a call a plugin drives itself. Throws `503` / `no_model` while none is set up |
|
|
35
|
-
| `registerSpaceContext({ block, tools? })` | [The space](./space.md). `block(space): string \| null` |
|
|
35
|
+
| `registerSpaceContext({ block, tools?, steps? })` | [The space](./space.md). `block(space): string \| null`; `steps`: `agent` (default), `html` (steps that write a document or a dashboard, without the tools) |
|
|
36
|
+
| `spaces.brand(space)` | [The space](./space.md#the-spaces-brand). `{ name, colors, fonts }` of the space, or `null` |
|
|
36
37
|
| `registerAssistantTool(tool: PluginAssistantTool)` | [The space](./space.md#tools-of-the-space-assistant) |
|
|
37
38
|
| `index.put({ space, key, title, text, link? })`, `index.remove(space, key?)` | [The space](./space.md#the-search-index) |
|
|
38
39
|
| `spaceData.putCategories`, `.putPage`, `.putTable`, `.putFile`, `.putDocument`, `.list`, `.readPages`, `.files`, `.readFile`, `.remove`, `.summarize` | [The space](./space.md#knowledge-a-plugin-writes). Knowledge of a space: pages, tables and files under keys of the plugin's own, with their Kategorien; and the space's own pages and files to read |
|
|
@@ -45,7 +46,7 @@ The default export is a `WizardsPluginFactory`: `(wizards: WizardsPluginApi) =>
|
|
|
45
46
|
| `web.fetch(url, init?)`, `web.read(url, { signal? })` | [The web](./server.md#the-web) |
|
|
46
47
|
| `documents.parse({ data, name, mime }, { signal? })` | [Documents](./server.md#documents). `{ markdown, pages }` |
|
|
47
48
|
| `getTenantDb(): PluginDb` | The current tenant's database, a Drizzle libSQL database |
|
|
48
|
-
| `generate(request): Promise<{ text, object }>` | Asks a model of the current tenant: `prompt`, `system?`, `schema?`, `model?`, `maxOutputTokens?`, `signal
|
|
49
|
+
| `generate(request): Promise<{ text, object }>` | Asks a model of the current tenant: `prompt`, `system?`, `schema?`, `model?`, `maxOutputTokens?`, `signal?`, `files?` (pictures and PDFs: `{ data, mediaType, name? }[]`). See [The server half](./server.md#asking-a-model) |
|
|
49
50
|
| `connections.available(ids)`, `.start({ space, connector, actions })`, `.connect({ space, connector, actions, credentials })`, `.list(space)`, `.get(id)`, `.call(id, action, input)`, `.remove(id)` | [Accounts](./connections.md) |
|
|
50
51
|
| `secrets.seal(value): string`, `secrets.unseal(sealed)` | [Keys of the plugin's own](./server.md#keys-of-the-plugins-own). `unseal` gives null for a value sealed on another install |
|
|
51
52
|
| `registerAssistantContext({ block })` | [The space](./space.md#the-assistant-on-the-plugins-own-page). `block(space): string \| null` |
|
|
@@ -240,11 +241,13 @@ The default export is a `StudioPlugin`: `(studio: StudioPluginContext) => void`.
|
|
|
240
241
|
| `studio.registerShareSection({ runner, component })` | [Runners](./runners.md#the-runners-part-of-the-share-dialog). The runner's own settings for a wizard (a keyword), under the share dialog's gear once it is switched on and set up. `component` gets `{ wizard: { id, title } }` |
|
|
241
242
|
| `studio.registerAssistantCard({ tool, component })` | Draws a tool's result in the space assistant's chat. `component` gets `{ data, send }` |
|
|
242
243
|
| `studio.registerSpaceSection({ id, group?, label, hint?, component })` | A section of a part of the space page at `/studio/space/<group>#<id>`; `group`: `info`, `knowledge` (default), `data` or `results` |
|
|
244
|
+
| `studio.registerSpacePart({ id, label, icon, after?, component })` | [A part of the plugin's own](./studio.md#a-part-of-the-plugins-own) at `/studio/space/<id>`, after `info`, `knowledge` or `data` (default) in the menu |
|
|
245
|
+
| `studio.useSpaceMenu(entries)` | A hook in a plugin's part: its menu, `{ id, label, icon? }[]`, each jumping to the element with that id |
|
|
243
246
|
| `studio.useCrumbs(items)` | A hook: the trail in the top bar for the page shown, `{ label, to? }[]`, instead of a heading of the page's own |
|
|
244
247
|
| `studio.Assistant` | Tells the studio's assistant what to say on a page of the plugin: `title?`, `hello?`, `placeholder?`, `suggestions?: { label, message?, fill? }[]`, `onChanged?`. Draws nothing |
|
|
245
248
|
| `studio.useAssistantPage(page)` | A hook: what the page shows, for the assistant: `open?: { kind, id?, label }`, `selection?`, `summary?`, `fields?: { id, label, value }[]`, `compact?`, and the words of `studio.Assistant` |
|
|
246
249
|
| `studio.useAssistantTool(name, { description, parameters? }, run)` | A hook: a tool the assistant runs in the page while it is shown, as `<plugin>_<name>`. `parameters`: JSON Schema; `run(args)` returns what the assistant learns |
|
|
247
|
-
| `studio.useSpace()` | A hook: `{ id, name, readOnly }` of the space the studio shows, or `null` |
|
|
250
|
+
| `studio.useSpace()` | A hook: `{ id, name, readOnly, brand }` of the space the studio shows, or `null`; `brand` is `{ name, colors, fonts }` |
|
|
248
251
|
| `studio.i18n.register({ de, en })` | Gives `t(key, vars?)` |
|
|
249
252
|
| `studio.i18n.lang()` | `"de"` or `"en"` |
|
|
250
253
|
| `studio.api.get`, `.post`, `.put`, `.patch`, `.del` | The plugin's own routes |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The server half
|
|
3
3
|
description: The function a plugin exports, what it is handed, and the rules it runs under.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The server half is a file whose default export is a function. The runtime calls it once when the
|
|
@@ -39,6 +39,7 @@ export default definePlugin((wizards) => {
|
|
|
39
39
|
| `registerAssistantTool(tool)` | A tool of the space assistant | [The space](./space.md#tools-of-the-space-assistant) |
|
|
40
40
|
| `registerAssistantContext(context)` | What the space assistant is told on the plugin's own page | [The space](./space.md#the-assistant-on-the-plugins-own-page) |
|
|
41
41
|
| `spaceData.putPage(input)`, `putTable`, `putFile`, `putDocument`, `putCategories`, … | Knowledge of a space: pages, tables, files and Kategorien | [The space](./space.md#knowledge-a-plugin-writes) |
|
|
42
|
+
| `spaces.brand(space)` | The space's look: its title, colours and fonts | [The space](./space.md#the-spaces-brand) |
|
|
42
43
|
| `index.put(entry)`, `index.remove(space, key?)` | Texts in the space's search index | [The space](./space.md#the-search-index) |
|
|
43
44
|
| `registerHttpRoute(route)` | A route below `/api/studio/plugins/<id>` | [Routes](./routes.md) |
|
|
44
45
|
| `registerPublicRoute(route)`, `publicUrl(path)` | A route anyone may call, and its address | [Routes](./routes.md#public-routes) |
|
|
@@ -106,6 +107,7 @@ const { object } = await server.generate({
|
|
|
106
107
|
| `model` | `classifier` (fast, cheap), `standard`, `high`, `highest` | `standard` |
|
|
107
108
|
| `maxOutputTokens` | A limit on the answer | the model's |
|
|
108
109
|
| `signal` | Aborts the call | two minutes |
|
|
110
|
+
| `files` | Pictures (PNG, JPEG, WebP, GIF) and PDFs the model looks at with the prompt: `{ data, mediaType, name? }[]`, `data` a `Uint8Array` | none |
|
|
109
111
|
|
|
110
112
|
It answers `{ text, object }`. Without a model set up it throws an error with the status `503`
|
|
111
113
|
and the code `no_model`: a route that lets it through answers just that, and the studio half
|