@pikku/inspector 0.12.112 → 0.12.114
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/dist/add/add-pikku-studio.d.ts +5 -3
- package/dist/add/add-pikku-studio.js +133 -14
- package/dist/add/add-wire-addon.d.ts +1 -0
- package/dist/add/add-wire-addon.js +1 -1
- package/dist/add/add-wire-remote-addon.js +39 -0
- package/dist/error-codes.d.ts +2 -0
- package/dist/error-codes.js +2 -0
- package/dist/types.d.ts +16 -0
- package/dist/utils/load-addon-functions-meta.js +32 -24
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/package.json +3 -3
|
@@ -8,8 +8,10 @@ import type { InspectorState, InspectorLogger } from '../types.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function addPikkuStudio(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
|
|
10
10
|
/**
|
|
11
|
-
* The checks on `definePlugin` that need the whole package: every title
|
|
12
|
-
*
|
|
13
|
-
* a
|
|
11
|
+
* The checks on `definePlugin` that need the whole package: every title, and
|
|
12
|
+
* every agent's frontmatter title and description, is a key in the addon's
|
|
13
|
+
* `messages/en.json` (a key missing from another locale is a warning), every
|
|
14
|
+
* nav badge names one of the addon's functions, and a `files` list in the
|
|
15
|
+
* package.json publishes the agents/ and skills/ the addon has.
|
|
14
16
|
*/
|
|
15
17
|
export declare function validatePikkuStudio(logger: InspectorLogger, state: InspectorState): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
const SCOPES = ['studio', 'team', 'project'];
|
|
@@ -110,6 +110,8 @@ const readScreen = (element, fail) => {
|
|
|
110
110
|
let scopes;
|
|
111
111
|
let component;
|
|
112
112
|
let app;
|
|
113
|
+
let agent;
|
|
114
|
+
let defaultChat;
|
|
113
115
|
let ok = true;
|
|
114
116
|
for (const field of element.properties) {
|
|
115
117
|
const key = propName(field);
|
|
@@ -136,6 +138,10 @@ const readScreen = (element, fail) => {
|
|
|
136
138
|
read = replaces = booleanLiteral(value);
|
|
137
139
|
else if (key === 'needsApp')
|
|
138
140
|
read = needsApp = booleanLiteral(value);
|
|
141
|
+
else if (key === 'defaultChat')
|
|
142
|
+
read = defaultChat = booleanLiteral(value);
|
|
143
|
+
else if (key === 'agent')
|
|
144
|
+
read = agent = ts.isStringLiteral(value) ? value.text : undefined;
|
|
139
145
|
else if (key === 'layout')
|
|
140
146
|
read = layout = oneOf(value, ['bare']);
|
|
141
147
|
else if (key === 'scopes') {
|
|
@@ -182,6 +188,8 @@ const readScreen = (element, fail) => {
|
|
|
182
188
|
...(needsApp !== undefined ? { needsApp } : {}),
|
|
183
189
|
...(layout ? { layout } : {}),
|
|
184
190
|
...(scopes ? { scopes } : {}),
|
|
191
|
+
...(agent !== undefined ? { agent } : {}),
|
|
192
|
+
...(defaultChat !== undefined ? { defaultChat } : {}),
|
|
185
193
|
...(component ? { component } : { app: app }),
|
|
186
194
|
};
|
|
187
195
|
};
|
|
@@ -195,7 +203,8 @@ export function addPikkuStudio(node, state, logger) {
|
|
|
195
203
|
if (!ts.isCallExpression(node))
|
|
196
204
|
return;
|
|
197
205
|
const { expression, arguments: args } = node;
|
|
198
|
-
if (!ts.isIdentifier(expression) ||
|
|
206
|
+
if (!ts.isIdentifier(expression) ||
|
|
207
|
+
(expression.text !== 'definePlugin' && expression.text !== 'pikkuStudio'))
|
|
199
208
|
return;
|
|
200
209
|
const [firstArg] = args;
|
|
201
210
|
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
@@ -259,6 +268,55 @@ const packageRoot = (file) => {
|
|
|
259
268
|
dir = parent;
|
|
260
269
|
}
|
|
261
270
|
};
|
|
271
|
+
const AGENT_NAME = /^[a-z][a-z0-9-]*$/;
|
|
272
|
+
const STUDIO_AGENT = /^studio:[a-z][a-z0-9-]*$/;
|
|
273
|
+
/** The text of a file that exists and is not blank, else undefined. */
|
|
274
|
+
const readNonEmpty = (file) => {
|
|
275
|
+
try {
|
|
276
|
+
if (!statSync(file).isFile())
|
|
277
|
+
return undefined;
|
|
278
|
+
const text = readFileSync(file, 'utf-8');
|
|
279
|
+
return text.trim() === '' ? undefined : text;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
/** The `key: value` lines of a leading `---` block; undefined when there is none. */
|
|
286
|
+
const readFrontmatter = (text) => {
|
|
287
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
288
|
+
if (!match)
|
|
289
|
+
return undefined;
|
|
290
|
+
const fields = {};
|
|
291
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
292
|
+
const pair = /^([A-Za-z][\w-]*):\s*(.*?)\s*$/.exec(line);
|
|
293
|
+
if (!pair)
|
|
294
|
+
continue;
|
|
295
|
+
fields[pair[1]] = pair[2].replace(/^(['"])(.*)\1$/, '$2');
|
|
296
|
+
}
|
|
297
|
+
return fields;
|
|
298
|
+
};
|
|
299
|
+
/**
|
|
300
|
+
* The package's `files` list, or undefined when it has none: what npm then
|
|
301
|
+
* publishes depends on `.npmignore` or `.gitignore`, which are not read here.
|
|
302
|
+
*/
|
|
303
|
+
const packageFiles = (root) => {
|
|
304
|
+
const files = readCatalog(join(root, 'package.json'))?.files;
|
|
305
|
+
return Array.isArray(files)
|
|
306
|
+
? files.filter((entry) => typeof entry === 'string')
|
|
307
|
+
: undefined;
|
|
308
|
+
};
|
|
309
|
+
const filesInclude = (files, dir) => files.some((entry) => entry === dir || entry === `${dir}/` || entry.startsWith(`${dir}/`));
|
|
310
|
+
/** Whether the package has a `skills/<name>/SKILL.md`. */
|
|
311
|
+
const hasSkills = (root) => {
|
|
312
|
+
const dir = join(root, 'skills');
|
|
313
|
+
try {
|
|
314
|
+
return readdirSync(dir, { withFileTypes: true }).some((entry) => entry.isDirectory() && existsSync(join(dir, entry.name, 'SKILL.md')));
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
262
320
|
const readCatalog = (file) => {
|
|
263
321
|
try {
|
|
264
322
|
return JSON.parse(readFileSync(file, 'utf-8'));
|
|
@@ -268,40 +326,101 @@ const readCatalog = (file) => {
|
|
|
268
326
|
}
|
|
269
327
|
};
|
|
270
328
|
/**
|
|
271
|
-
* The checks on `definePlugin` that need the whole package: every title
|
|
272
|
-
*
|
|
273
|
-
* a
|
|
329
|
+
* The checks on `definePlugin` that need the whole package: every title, and
|
|
330
|
+
* every agent's frontmatter title and description, is a key in the addon's
|
|
331
|
+
* `messages/en.json` (a key missing from another locale is a warning), every
|
|
332
|
+
* nav badge names one of the addon's functions, and a `files` list in the
|
|
333
|
+
* package.json publishes the agents/ and skills/ the addon has.
|
|
274
334
|
*/
|
|
275
335
|
export function validatePikkuStudio(logger, state) {
|
|
276
336
|
const studio = state.studio;
|
|
277
337
|
if (!studio)
|
|
278
338
|
return;
|
|
279
|
-
const keys =
|
|
339
|
+
const keys = new Set([
|
|
340
|
+
studio.title,
|
|
341
|
+
...studio.screens.map((screen) => screen.title),
|
|
342
|
+
]);
|
|
280
343
|
const root = packageRoot(studio.file);
|
|
344
|
+
const files = root ? packageFiles(root) : undefined;
|
|
281
345
|
const messagesDir = root ? join(root, 'messages') : undefined;
|
|
282
346
|
const en = messagesDir ? readCatalog(join(messagesDir, 'en.json')) : undefined;
|
|
283
|
-
for (const key of
|
|
347
|
+
for (const key of keys) {
|
|
284
348
|
if (!en || !Object.hasOwn(en, key)) {
|
|
285
349
|
logger.critical(ErrorCode.STUDIO_MESSAGE_KEY_MISSING, `definePlugin uses the message key '${key}', which is missing from the addon's messages/en.json`);
|
|
286
350
|
}
|
|
287
351
|
}
|
|
352
|
+
for (const screen of studio.screens) {
|
|
353
|
+
const agent = screen.agent;
|
|
354
|
+
if (agent !== undefined) {
|
|
355
|
+
let agentValid = true;
|
|
356
|
+
const invalid = (reason) => {
|
|
357
|
+
agentValid = false;
|
|
358
|
+
logger.critical(ErrorCode.STUDIO_SCREEN_AGENT_INVALID, `Screen '${screen.path}' has the agent '${agent}', ${reason}`);
|
|
359
|
+
};
|
|
360
|
+
if (agent.startsWith('studio:')) {
|
|
361
|
+
// An agent Studio ships: pikku cannot see it, so only the format is checked.
|
|
362
|
+
if (!STUDIO_AGENT.test(agent)) {
|
|
363
|
+
invalid("which is not a valid agent: use a name for an agent the addon ships (lowercase letters, digits and hyphens, starting with a letter) or 'studio:<name>' for one Studio ships");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (!AGENT_NAME.test(agent)) {
|
|
367
|
+
invalid("which is not a valid agent: use a name for an agent the addon ships (lowercase letters, digits and hyphens, starting with a letter) or 'studio:<name>' for one Studio ships");
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
const text = root
|
|
371
|
+
? readNonEmpty(join(root, 'agents', `${agent}.md`))
|
|
372
|
+
: undefined;
|
|
373
|
+
const fields = text === undefined ? undefined : readFrontmatter(text);
|
|
374
|
+
if (text === undefined) {
|
|
375
|
+
invalid(`but the addon has no non-empty agents/${agent}.md`);
|
|
376
|
+
}
|
|
377
|
+
else if (!fields) {
|
|
378
|
+
invalid(`whose agents/${agent}.md has no frontmatter; it needs a title and a description`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
for (const field of ['title', 'description']) {
|
|
382
|
+
const key = fields[field];
|
|
383
|
+
if (!key) {
|
|
384
|
+
invalid(`whose agents/${agent}.md frontmatter has no ${field}; it must be a message key`);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
keys.add(key);
|
|
388
|
+
if (!en || !Object.hasOwn(en, key)) {
|
|
389
|
+
logger.critical(ErrorCode.STUDIO_MESSAGE_KEY_MISSING, `definePlugin uses the message key '${key}', which is missing from the addon's messages/en.json`);
|
|
390
|
+
}
|
|
391
|
+
else if (field === 'title')
|
|
392
|
+
screen.agentTitle = key;
|
|
393
|
+
else
|
|
394
|
+
screen.agentDescription = key;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (agentValid &&
|
|
399
|
+
!agent.startsWith('studio:') &&
|
|
400
|
+
files &&
|
|
401
|
+
!filesInclude(files, 'agents')) {
|
|
402
|
+
logger.critical(ErrorCode.STUDIO_PACKAGE_FILES_MISSING, `Screen '${screen.path}' has the agent '${agent}', but package.json "files" does not list agents/; add "agents" to it so the published addon ships the agent`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const badge = screen.nav?.badge;
|
|
406
|
+
if (badge !== undefined && !state.functions.meta[badge]) {
|
|
407
|
+
logger.critical(ErrorCode.STUDIO_BADGE_FUNCTION_NOT_FOUND, `Screen '${screen.path}' has the nav badge '${badge}', which names no function in this addon`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (root && files && !filesInclude(files, 'skills') && hasSkills(root)) {
|
|
411
|
+
logger.critical(ErrorCode.STUDIO_PACKAGE_FILES_MISSING, `The addon ships skills/ but package.json "files" does not list it; add "skills" to it so the published addon ships its skills`);
|
|
412
|
+
}
|
|
288
413
|
if (messagesDir && existsSync(messagesDir)) {
|
|
289
414
|
const locales = readdirSync(messagesDir)
|
|
290
415
|
.filter((name) => name.endsWith('.json') && name !== 'en.json')
|
|
291
416
|
.sort();
|
|
292
417
|
for (const name of locales) {
|
|
293
418
|
const catalog = readCatalog(join(messagesDir, name)) ?? {};
|
|
294
|
-
for (const key of
|
|
419
|
+
for (const key of keys) {
|
|
295
420
|
if (!Object.hasOwn(catalog, key)) {
|
|
296
421
|
logger.warn(`definePlugin uses the message key '${key}', which is missing from the addon's messages/${name}`);
|
|
297
422
|
}
|
|
298
423
|
}
|
|
299
424
|
}
|
|
300
425
|
}
|
|
301
|
-
for (const screen of studio.screens) {
|
|
302
|
-
const badge = screen.nav?.badge;
|
|
303
|
-
if (badge !== undefined && !state.functions.meta[badge]) {
|
|
304
|
-
logger.critical(ErrorCode.STUDIO_BADGE_FUNCTION_NOT_FOUND, `Screen '${screen.path}' has the nav badge '${badge}', which names no function in this addon`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
426
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
3
|
+
export declare function parseStringArray(node: ts.Expression): string[] | undefined;
|
|
3
4
|
/**
|
|
4
5
|
* Detect wireAddon({ name: '...', package: '...' }) call expressions and
|
|
5
6
|
* populate state.rpc.wireAddonDeclarations and state.rpc.usedAddons.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { parseStringArray } from './add-wire-addon.js';
|
|
3
4
|
import { parseRemoteAddonPackage } from '../utils/remote-addon-sdk.js';
|
|
4
5
|
/**
|
|
5
6
|
* Detect wireRemoteAddon({ name: '...', package: '...' }) call expressions and
|
|
@@ -27,6 +28,7 @@ export function addWireRemoteAddon(node, state, logger) {
|
|
|
27
28
|
let hasAuth = false;
|
|
28
29
|
let authCredentialId;
|
|
29
30
|
let authSecretId;
|
|
31
|
+
let local;
|
|
30
32
|
for (const prop of firstArg.properties) {
|
|
31
33
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
32
34
|
continue;
|
|
@@ -40,6 +42,42 @@ export function addWireRemoteAddon(node, state, logger) {
|
|
|
40
42
|
pkg = prop.initializer.text;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
else if (key === 'local' &&
|
|
46
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
47
|
+
let localPackage;
|
|
48
|
+
let ui;
|
|
49
|
+
let contributors;
|
|
50
|
+
for (const localProp of prop.initializer.properties) {
|
|
51
|
+
if (!ts.isPropertyAssignment(localProp) ||
|
|
52
|
+
!ts.isIdentifier(localProp.name))
|
|
53
|
+
continue;
|
|
54
|
+
const localKey = localProp.name.text;
|
|
55
|
+
if (localKey === 'package' &&
|
|
56
|
+
ts.isStringLiteral(localProp.initializer)) {
|
|
57
|
+
localPackage = localProp.initializer.text;
|
|
58
|
+
}
|
|
59
|
+
else if (localKey === 'ui' &&
|
|
60
|
+
localProp.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
61
|
+
ui = true;
|
|
62
|
+
}
|
|
63
|
+
else if (localKey === 'contributors') {
|
|
64
|
+
contributors = parseStringArray(localProp.initializer);
|
|
65
|
+
if (contributors === undefined) {
|
|
66
|
+
logger.critical(ErrorCode.ADDON_CONTRIBUTORS_NOT_STATIC, `wireRemoteAddon's local.contributors must be an array of string literals, got: ${localProp.initializer.getText()}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (localPackage) {
|
|
71
|
+
local = {
|
|
72
|
+
package: localPackage,
|
|
73
|
+
...(ui ? { ui } : {}),
|
|
74
|
+
...(contributors ? { contributors } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
logger.critical(ErrorCode.REMOTE_ADDON_PACKAGE_NOT_STATIC, `wireRemoteAddon's local.package must be a string literal, got: ${prop.initializer.getText()}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
43
81
|
else if (key === 'auth' &&
|
|
44
82
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
45
83
|
hasAuth = true;
|
|
@@ -83,6 +121,7 @@ export function addWireRemoteAddon(node, state, logger) {
|
|
|
83
121
|
hasAuth,
|
|
84
122
|
authCredentialId,
|
|
85
123
|
authSecretId,
|
|
124
|
+
...(local ? { local } : {}),
|
|
86
125
|
});
|
|
87
126
|
state.rpc.usedAddons.add(name);
|
|
88
127
|
state.rpc.wireAddonFiles.add(node.getSourceFile().fileName);
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export declare enum ErrorCode {
|
|
|
57
57
|
ADDON_CONTRIBUTORS_NOT_STATIC = "PKU362",
|
|
58
58
|
ADDON_CONTRIBUTOR_UNKNOWN = "PKU363",
|
|
59
59
|
ADDON_LEGACY_CONTRIBUTORS = "PKU364",
|
|
60
|
+
STUDIO_SCREEN_AGENT_INVALID = "PKU365",
|
|
61
|
+
STUDIO_PACKAGE_FILES_MISSING = "PKU366",
|
|
60
62
|
DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
|
|
61
63
|
DB_JSON_COLUMN_UNTYPED = "PKU481",
|
|
62
64
|
DB_FORMAT_ON_NON_STRING = "PKU482",
|
package/dist/error-codes.js
CHANGED
|
@@ -60,6 +60,8 @@ export var ErrorCode;
|
|
|
60
60
|
ErrorCode["ADDON_CONTRIBUTORS_NOT_STATIC"] = "PKU362";
|
|
61
61
|
ErrorCode["ADDON_CONTRIBUTOR_UNKNOWN"] = "PKU363";
|
|
62
62
|
ErrorCode["ADDON_LEGACY_CONTRIBUTORS"] = "PKU364";
|
|
63
|
+
ErrorCode["STUDIO_SCREEN_AGENT_INVALID"] = "PKU365";
|
|
64
|
+
ErrorCode["STUDIO_PACKAGE_FILES_MISSING"] = "PKU366";
|
|
63
65
|
// Database schema codegen warnings
|
|
64
66
|
ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
|
|
65
67
|
ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
|
package/dist/types.d.ts
CHANGED
|
@@ -548,6 +548,12 @@ export type StudioScreenMeta = {
|
|
|
548
548
|
needsApp?: boolean;
|
|
549
549
|
layout?: 'bare';
|
|
550
550
|
scopes?: string[];
|
|
551
|
+
agent?: string;
|
|
552
|
+
/** Message key from the agent file's frontmatter, filled by validation. */
|
|
553
|
+
agentTitle?: string;
|
|
554
|
+
/** Message key from the agent file's frontmatter, filled by validation. */
|
|
555
|
+
agentDescription?: string;
|
|
556
|
+
defaultChat?: boolean;
|
|
551
557
|
} & ({
|
|
552
558
|
component: string;
|
|
553
559
|
app?: undefined;
|
|
@@ -767,6 +773,16 @@ export interface InspectorState {
|
|
|
767
773
|
auth?: boolean;
|
|
768
774
|
tags?: string[];
|
|
769
775
|
scopes?: string[];
|
|
776
|
+
/**
|
|
777
|
+
* wireRemoteAddon: a package installed in this project that carries the
|
|
778
|
+
* screens and contributors for this name, when they live apart from the
|
|
779
|
+
* contract package. `ui` and `contributors` are read from it.
|
|
780
|
+
*/
|
|
781
|
+
local?: {
|
|
782
|
+
package: string;
|
|
783
|
+
ui?: boolean;
|
|
784
|
+
contributors?: string[];
|
|
785
|
+
};
|
|
770
786
|
/** True when declared via `wireRemoteAddon` — import the addon's `.remote.gen` map, not `.internal.gen` */
|
|
771
787
|
remote?: boolean;
|
|
772
788
|
/**
|
|
@@ -279,6 +279,27 @@ const recordDeclaredUses = (logger, namespace, decl, dirs) => {
|
|
|
279
279
|
: `${decl.package} needs ${needed}, but wireAddon('${namespace}') does not map it. Wire ${needed} and add uses: { '${needed}': '<name>' } to this wireAddon.`);
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
|
+
const recordAddonScreens = async (logger, state, require, namespace, packageName, scopes) => {
|
|
283
|
+
let manifest;
|
|
284
|
+
try {
|
|
285
|
+
manifest = await loadStudioMeta(require, packageName);
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but the screens manifest of ${packageName} could not be read: ${e instanceof Error ? e.message : e}`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (!manifest || manifest.screens.length === 0) {
|
|
292
|
+
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${packageName} declares no screens. Add a definePlugin call to the package, or drop ui.`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
;
|
|
296
|
+
(state.addonStudio ??= {})[namespace] = {
|
|
297
|
+
...manifest,
|
|
298
|
+
scopes: [
|
|
299
|
+
...new Set([...(manifest.scopes ?? []), ...(scopes ?? [])]),
|
|
300
|
+
].sort(),
|
|
301
|
+
};
|
|
302
|
+
};
|
|
282
303
|
export async function loadAddonFunctionsMeta(logger, state) {
|
|
283
304
|
const { wireAddonDeclarations } = state.rpc;
|
|
284
305
|
if (wireAddonDeclarations.size === 0)
|
|
@@ -291,6 +312,16 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
291
312
|
if (decl.range) {
|
|
292
313
|
await loadRemoteAddonSdkMeta(state, namespace, decl.package, decl.range);
|
|
293
314
|
}
|
|
315
|
+
if (decl.local) {
|
|
316
|
+
const dirs = addonResolutionDirs(state.rootDir, decl.file);
|
|
317
|
+
const require = createAddonResolver(dirs);
|
|
318
|
+
if (decl.local.contributors && decl.local.contributors.length > 0) {
|
|
319
|
+
await recordOptedInContributors(logger, state, require, namespace, decl.local.package, decl.local.contributors);
|
|
320
|
+
}
|
|
321
|
+
if (decl.local.ui) {
|
|
322
|
+
await recordAddonScreens(logger, state, require, namespace, decl.local.package, undefined);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
294
325
|
continue;
|
|
295
326
|
}
|
|
296
327
|
const dirs = addonResolutionDirs(state.rootDir, decl.file);
|
|
@@ -304,30 +335,7 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
304
335
|
logger.critical(ErrorCode.ADDON_LEGACY_CONTRIBUTORS, `${decl.package} still declares "pikku.contributors" in its package.json, which is no longer read. Remove it, register the contributors with defineContributors in the addon's source, and opt in with wireAddon('${namespace}', { contributors: [...] }). See docs/contributors.md.`);
|
|
305
336
|
}
|
|
306
337
|
if (decl.ui) {
|
|
307
|
-
|
|
308
|
-
let unreadable = false;
|
|
309
|
-
try {
|
|
310
|
-
manifest = await loadStudioMeta(require, decl.package);
|
|
311
|
-
}
|
|
312
|
-
catch (e) {
|
|
313
|
-
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but the screens manifest of ${decl.package} could not be read: ${e instanceof Error ? e.message : e}`);
|
|
314
|
-
unreadable = true;
|
|
315
|
-
}
|
|
316
|
-
if (unreadable) {
|
|
317
|
-
// reported above; the addon's functions and scopes still load
|
|
318
|
-
}
|
|
319
|
-
else if (!manifest || manifest.screens.length === 0) {
|
|
320
|
-
logger.critical(ErrorCode.ADDON_UI_HAS_NO_SCREENS, `wireAddon('${namespace}') sets ui: true, but ${decl.package} declares no screens. Add a definePlugin call to the package, or drop ui.`);
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
;
|
|
324
|
-
(state.addonStudio ??= {})[namespace] = {
|
|
325
|
-
...manifest,
|
|
326
|
-
scopes: [
|
|
327
|
-
...new Set([...(manifest.scopes ?? []), ...(decl.scopes ?? [])]),
|
|
328
|
-
].sort(),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
338
|
+
await recordAddonScreens(logger, state, require, namespace, decl.package, decl.scopes);
|
|
331
339
|
}
|
|
332
340
|
// Verbose first. `description` is one of the fields stripped from the
|
|
333
341
|
// minimal copy, and it is what an addon's function is offered to a model
|
|
@@ -199,6 +199,11 @@ export interface SerializableInspectorState {
|
|
|
199
199
|
file?: string;
|
|
200
200
|
rpcEndpoint?: string;
|
|
201
201
|
remote?: boolean;
|
|
202
|
+
local?: {
|
|
203
|
+
package: string;
|
|
204
|
+
ui?: boolean;
|
|
205
|
+
contributors?: string[];
|
|
206
|
+
};
|
|
202
207
|
hasAuth?: boolean;
|
|
203
208
|
authCredentialId?: string;
|
|
204
209
|
authSecretId?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.114",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"zod": "^4.6.7"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@pikku/core": "^0.12.
|
|
73
|
+
"@pikku/core": "^0.12.151"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@pikku/core": "^0.12.
|
|
76
|
+
"@pikku/core": "^0.12.151",
|
|
77
77
|
"@types/node": "^26.6.5"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|