@stage-labs/metro 0.1.0-beta.285 → 0.1.0-beta.286
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/package.json +1 -1
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-schema.ts +40 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +2 -32
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +1 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -0
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/runner.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.286",
|
|
4
4
|
"description": "Metro for Claude Code, installed by the metro daemon on this machine: every connector the agent holds as its own MCP server relayed through the daemon, the orchestrator-only guard on the main thread, and the standing rules loaded at each session start. No vendor credential ever sits in a config file.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bonustrack",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const frameSchema = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
description:
|
|
4
|
+
'Stage (XMTP) only: a frame, a small interactive view. Stage shows it in the chat at image size ' +
|
|
5
|
+
'(400 x 400 at most, its start screen, clipped with a fade) and opens it full screen on a tap; its ' +
|
|
6
|
+
'buttons work only there. `widget` is OpenAI ChatKit widget JSON (a Card, ListView ' +
|
|
7
|
+
'or Basic root, 64K characters at most); a missing `title` or `description` is taken from the ' +
|
|
8
|
+
'widget. Or give `screens` instead of `widget` for several screens in one frame. A tap there comes back ' +
|
|
9
|
+
'to you as a reply to the frame: "Frame action: <type> <payload ' +
|
|
10
|
+
"JSON>\". It is sent after the text, and the message_id returned is the frame's. The stage " +
|
|
11
|
+
'skill lists the widget nodes, with an example.',
|
|
12
|
+
properties: {
|
|
13
|
+
widget: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
description:
|
|
16
|
+
'ChatKit widget JSON, e.g. {"type":"Card","children":[{"type":"Title","value":"Deploy?"},' +
|
|
17
|
+
'{"type":"Button","label":"Ship it","onClickAction":{"type":"deploy","payload":{"env":"prod"}}}]}.',
|
|
18
|
+
additionalProperties: true,
|
|
19
|
+
},
|
|
20
|
+
screens: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
description:
|
|
23
|
+
'Instead of `widget`: up to 50 screens by id, each a widget or {"title", "widget"}, 64K ' +
|
|
24
|
+
'characters in all. An action {"type":"frame.open","payload":{"screen":"<id>"}} opens a ' +
|
|
25
|
+
'screen and {"type":"frame.back"} goes back, in Stage, with no message to you.',
|
|
26
|
+
additionalProperties: true,
|
|
27
|
+
},
|
|
28
|
+
start: { type: 'string', description: 'Required with `screens`: the id of the first screen.' },
|
|
29
|
+
title: { type: 'string', description: 'Card title in the chat (200 characters at most).' },
|
|
30
|
+
description: { type: 'string', description: 'Card description in the chat (1000 characters at most).' },
|
|
31
|
+
source: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
description:
|
|
34
|
+
'Optional: {"url": "https://…"}, a node URL that serves this widget live (2048 characters at most). ' +
|
|
35
|
+
'Add to dashboard on the frame then adds a live widget loaded from that URL instead of a copy.',
|
|
36
|
+
properties: { url: { type: 'string' } },
|
|
37
|
+
required: ['url'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
} as const;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolDef } from './tool-def.js';
|
|
2
2
|
import { attachmentItem, CREATE_UPLOAD_TOOL } from './attachment-schema.js';
|
|
3
|
+
import { frameSchema } from './frame-schema.js';
|
|
3
4
|
import { speechSchema } from './speech-schema.js';
|
|
4
5
|
|
|
5
6
|
const lineProp = {
|
|
@@ -69,38 +70,7 @@ export const COMMON_TOOLS: ToolDef[] = [
|
|
|
69
70
|
description: 'Optional files to attach.',
|
|
70
71
|
items: attachmentItem,
|
|
71
72
|
},
|
|
72
|
-
frame:
|
|
73
|
-
type: 'object',
|
|
74
|
-
description:
|
|
75
|
-
'Stage (XMTP) only: a frame, a small interactive view. Stage shows it in the chat at image size ' +
|
|
76
|
-
'(400 x 400 at most, its start screen, clipped with a fade) and opens it full screen on a tap; its ' +
|
|
77
|
-
'buttons work only there. `widget` is OpenAI ChatKit widget JSON (a Card, ListView ' +
|
|
78
|
-
'or Basic root, 64K characters at most); a missing `title` or `description` is taken from the ' +
|
|
79
|
-
'widget. Or give `screens` instead of `widget` for several screens in one frame. A tap there comes back ' +
|
|
80
|
-
'to you as a reply to the frame: "Frame action: <type> <payload ' +
|
|
81
|
-
"JSON>\". It is sent after the text, and the message_id returned is the frame's. The stage " +
|
|
82
|
-
'skill lists the widget nodes, with an example.',
|
|
83
|
-
properties: {
|
|
84
|
-
widget: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
description:
|
|
87
|
-
'ChatKit widget JSON, e.g. {"type":"Card","children":[{"type":"Title","value":"Deploy?"},' +
|
|
88
|
-
'{"type":"Button","label":"Ship it","onClickAction":{"type":"deploy","payload":{"env":"prod"}}}]}.',
|
|
89
|
-
additionalProperties: true,
|
|
90
|
-
},
|
|
91
|
-
screens: {
|
|
92
|
-
type: 'object',
|
|
93
|
-
description:
|
|
94
|
-
'Instead of `widget`: up to 50 screens by id, each a widget or {"title", "widget"}, 64K ' +
|
|
95
|
-
'characters in all. An action {"type":"frame.open","payload":{"screen":"<id>"}} opens a ' +
|
|
96
|
-
'screen and {"type":"frame.back"} goes back, in Stage, with no message to you.',
|
|
97
|
-
additionalProperties: true,
|
|
98
|
-
},
|
|
99
|
-
start: { type: 'string', description: 'Required with `screens`: the id of the first screen.' },
|
|
100
|
-
title: { type: 'string', description: 'Card title in the chat (200 characters at most).' },
|
|
101
|
-
description: { type: 'string', description: 'Card description in the chat (1000 characters at most).' },
|
|
102
|
-
},
|
|
103
|
-
},
|
|
73
|
+
frame: frameSchema,
|
|
104
74
|
wallet: {
|
|
105
75
|
type: 'object',
|
|
106
76
|
description:
|
|
@@ -196,6 +196,7 @@ export interface FrameContent {
|
|
|
196
196
|
widget?: Record<string, unknown>;
|
|
197
197
|
screens?: Record<string, unknown>;
|
|
198
198
|
start?: string;
|
|
199
|
+
source?: { url: string };
|
|
199
200
|
}
|
|
200
201
|
export const frameFallback = (c: FrameContent): string => {
|
|
201
202
|
const head = c.title ? `Frame: ${c.title}` : 'Frame';
|
|
@@ -7,6 +7,7 @@ const MAX_SCREEN_ID = 120;
|
|
|
7
7
|
const FRAME_OPEN = 'frame.open';
|
|
8
8
|
const MAX_TITLE = 200;
|
|
9
9
|
const MAX_DESCRIPTION = 1000;
|
|
10
|
+
const MAX_SOURCE_URL = 2048;
|
|
10
11
|
const SUMMARY_SCAN = 80;
|
|
11
12
|
const TEXT_TYPES = new Set(['Text', 'Caption', 'Markdown']);
|
|
12
13
|
|
|
@@ -150,6 +151,16 @@ function frameBody(args: Node): FrameBody {
|
|
|
150
151
|
return parseScreens(args.screens, args.start);
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
function sourcePart(raw: unknown): Pick<FrameContent, 'source'> {
|
|
155
|
+
if (raw === undefined) return {};
|
|
156
|
+
const url = isNode(raw) && typeof raw.url === 'string' ? raw.url.trim() : '';
|
|
157
|
+
if (url.length > MAX_SOURCE_URL)
|
|
158
|
+
throw bad(`frame source url is ${url.length} characters; the limit is ${MAX_SOURCE_URL}`);
|
|
159
|
+
if (!URL.canParse(url) || new URL(url).protocol !== 'https:')
|
|
160
|
+
throw bad('frame source must be {"url": "https://…"}, the node that serves this widget');
|
|
161
|
+
return { source: { url } };
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
export function buildFrameContent(raw: unknown): { frame: FrameContent; title: string } {
|
|
154
165
|
const args = parseJson(raw, 'frame');
|
|
155
166
|
if (!isNode(args))
|
|
@@ -162,6 +173,7 @@ export function buildFrameContent(raw: unknown): { frame: FrameContent; title: s
|
|
|
162
173
|
...(title ? { title } : {}),
|
|
163
174
|
...(description && description !== title ? { description } : {}),
|
|
164
175
|
...body.content,
|
|
176
|
+
...sourcePart(args.source),
|
|
165
177
|
};
|
|
166
178
|
return { frame, title: title ?? 'Frame' };
|
|
167
179
|
}
|
package/runtime/runtime.json
CHANGED