@stage-labs/metro 0.1.0-beta.140 → 0.1.0-beta.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/api.js +1 -6
  2. package/dist/cli.js +1 -14
  3. package/dist/local.js +0 -13
  4. package/package.json +1 -1
  5. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +2 -2
  6. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +24 -111
  7. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +3 -13
  8. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +5 -7
  9. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +2 -20
  10. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  11. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +1 -9
  12. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +8 -8
  13. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +6 -0
  14. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +2 -7
  15. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +0 -11
  16. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +42 -273
  17. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +6 -16
  18. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +0 -3
  19. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +23 -4
  20. package/runtime/runtime.json +1 -1
  21. package/dist/plugin.js +0 -120
  22. package/runtime/marketplace/plugin/skills/refresh/SKILL.md +0 -15
  23. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +0 -63
  24. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +0 -21
  25. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +0 -98
  26. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +0 -54
@@ -1,63 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { errMsg, log } from '@metro-labs/core/log';
3
- import { cors, sendJson, type AgentIdentity } from '@metro-labs/http/api-http';
4
- import { loopbackBase } from '../files/attach-serve.js';
5
- import { relayServersJson, type RelayServerEntry } from './json.js';
6
- import { agentIdForKey } from '../agents/keys.js';
7
-
8
- const PATH = '/api/cli/mcp';
9
-
10
- export interface LocalCliDeps {
11
- agentName: (agentId: string) => string | null;
12
- connectorEntries: (agentId: string) => Promise<RelayServerEntry[]>;
13
- }
14
-
15
- function keyOf(req: IncomingMessage): string {
16
- const header = req.headers.authorization ?? '';
17
- if (header.toLowerCase().startsWith('bearer ')) return header.slice(7).trim();
18
- const query = new URLSearchParams((req.url ?? '').split('?')[1] ?? '');
19
- return query.get('token')?.trim() ?? '';
20
- }
21
-
22
- export function keyIdentity(req: IncomingMessage): AgentIdentity | null {
23
- const key = keyOf(req);
24
- const agentId = key === '' ? undefined : agentIdForKey(key);
25
- return agentId === undefined ? null : { subject: 'agent-key', agentId };
26
- }
27
-
28
- export function handleLocalCliRequest(
29
- req: IncomingMessage,
30
- res: ServerResponse,
31
- deps: LocalCliDeps,
32
- ): boolean {
33
- const path = (req.url ?? '').split('?')[0] ?? '';
34
- if (path !== PATH && !path.startsWith('/api/cli/')) return false;
35
- if (req.method === 'OPTIONS') {
36
- res.writeHead(204, cors(req)).end();
37
- return true;
38
- }
39
- if (path !== PATH) {
40
- sendJson(req, res, 404, { error: 'not on a local daemon' });
41
- return true;
42
- }
43
- if (req.method !== 'GET') {
44
- sendJson(req, res, 405, { error: 'method not allowed' });
45
- return true;
46
- }
47
- const who = keyIdentity(req);
48
- if (who === null) {
49
- sendJson(req, res, 401, { error: 'unauthorized' });
50
- return true;
51
- }
52
- deps
53
- .connectorEntries(who.agentId)
54
- .then((entries) => {
55
- const agent = deps.agentName(who.agentId) ?? '';
56
- sendJson(req, res, 200, { json: relayServersJson(entries, loopbackBase(), keyOf(req)), agent });
57
- })
58
- .catch((err: unknown) => {
59
- log.warn({ err: errMsg(err) }, 'local cli: request failed');
60
- if (!res.headersSent) sendJson(req, res, 500, { error: 'cli failed' });
61
- });
62
- return true;
63
- }
@@ -1,21 +0,0 @@
1
- export interface RelayServerEntry {
2
- id: string;
3
- name: string;
4
- }
5
-
6
- export const NAME_PREFIX = 'metro.box ';
7
-
8
- export function relayServersJson(
9
- entries: RelayServerEntry[],
10
- base: string,
11
- cliToken: string,
12
- ): string {
13
- const mcpServers: Record<string, unknown> = {};
14
- for (const entry of entries)
15
- mcpServers[`${NAME_PREFIX}${entry.name}`] = {
16
- type: 'http',
17
- url: `${base}/relay/${entry.id}`,
18
- headers: { Authorization: `Bearer ${cliToken}` },
19
- };
20
- return JSON.stringify({ mcpServers }, null, 2);
21
- }
@@ -1,98 +0,0 @@
1
- import { isRecord } from '@metro-labs/core/is-record';
2
-
3
- export const TOOL_KINDS = ['read', 'write'] as const;
4
-
5
- export type ToolKind = (typeof TOOL_KINDS)[number];
6
-
7
- export interface ToolInfo {
8
- name: string;
9
- title: string;
10
- description: string;
11
- kind: ToolKind;
12
- annotated: boolean;
13
- destructive: boolean;
14
- idempotent: boolean;
15
- openWorld: boolean;
16
- }
17
-
18
- const MAX_TOOLS = 500;
19
- const MAX_DESCRIPTION = 400;
20
-
21
- const str = (value: unknown, cap: number): string =>
22
- typeof value === 'string' ? value.slice(0, cap) : '';
23
-
24
- const flag = (value: unknown, fallback: boolean): boolean =>
25
- typeof value === 'boolean' ? value : fallback;
26
-
27
- export function classifyTool(annotations: unknown): ToolKind {
28
- return isRecord(annotations) && annotations.readOnlyHint === true
29
- ? 'read'
30
- : 'write';
31
- }
32
-
33
- export function isDestructive(annotations: unknown): boolean {
34
- if (!isRecord(annotations)) return true;
35
- if (annotations.readOnlyHint === true) return false;
36
- return annotations.destructiveHint !== false;
37
- }
38
-
39
- export function toToolInfo(raw: unknown): ToolInfo | null {
40
- if (!isRecord(raw)) return null;
41
- const name = str(raw.name, 200);
42
- if (name === '') return null;
43
- const annotations = isRecord(raw.annotations) ? raw.annotations : undefined;
44
- return {
45
- name,
46
- title: str(annotations?.title ?? raw.title, 200),
47
- description: str(raw.description, MAX_DESCRIPTION),
48
- kind: classifyTool(raw.annotations),
49
- annotated: annotations !== undefined,
50
- destructive: isDestructive(raw.annotations),
51
- idempotent: flag(annotations?.idempotentHint, false),
52
- openWorld: flag(annotations?.openWorldHint, true),
53
- };
54
- }
55
-
56
- export function toToolList(raw: unknown): ToolInfo[] {
57
- if (!Array.isArray(raw)) return [];
58
- const out: ToolInfo[] = [];
59
- for (const entry of raw) {
60
- if (out.length >= MAX_TOOLS) break;
61
- const tool = toToolInfo(entry);
62
- if (tool !== null) out.push(tool);
63
- }
64
- return out;
65
- }
66
-
67
- function toStoredTool(raw: unknown): ToolInfo | null {
68
- if (!isRecord(raw)) return null;
69
- const name = str(raw.name, 200);
70
- if (name === '') return null;
71
- return {
72
- name,
73
- title: str(raw.title, 200),
74
- description: str(raw.description, MAX_DESCRIPTION),
75
- kind: raw.kind === 'read' ? 'read' : 'write',
76
- annotated: raw.annotated === true,
77
- destructive: raw.destructive !== false,
78
- idempotent: raw.idempotent === true,
79
- openWorld: raw.openWorld !== false,
80
- };
81
- }
82
-
83
- export function readStoredTools(raw: unknown): ToolInfo[] {
84
- if (!Array.isArray(raw)) return [];
85
- const out: ToolInfo[] = [];
86
- for (const entry of raw) {
87
- if (out.length >= MAX_TOOLS) break;
88
- const tool = toStoredTool(entry);
89
- if (tool !== null) out.push(tool);
90
- }
91
- return out;
92
- }
93
-
94
- export function countByKind(tools: ToolInfo[]): Record<ToolKind, number> {
95
- const counts: Record<ToolKind, number> = { read: 0, write: 0 };
96
- for (const tool of tools) counts[tool.kind] += 1;
97
- return counts;
98
- }
@@ -1,54 +0,0 @@
1
- import { watch, type FSWatcher } from 'node:fs';
2
- import { errMsg, log } from '@metro-labs/core/log';
3
-
4
- const DEBOUNCE_MS = 300;
5
- const FILE = 'connectors.json';
6
-
7
- export class ConnectorWatch {
8
- private watcher: FSWatcher | null = null;
9
- private timer: ReturnType<typeof setTimeout> | null = null;
10
-
11
- constructor(
12
- private readonly dir: string,
13
- private readonly onChange: () => void,
14
- ) {}
15
-
16
- start(): void {
17
- try {
18
- this.watcher = watch(this.dir, (_event, file) => {
19
- if (file === null || file === FILE) this.schedule();
20
- });
21
- this.watcher.on('error', (err: unknown) => {
22
- log.warn({ err: errMsg(err) }, 'connectors: watcher failed; the plugin list refreshes at the next start');
23
- });
24
- this.watcher.unref();
25
- } catch (err) {
26
- log.warn({ err: errMsg(err) }, 'connectors: could not watch the agents dir');
27
- }
28
- this.fire();
29
- }
30
-
31
- stop(): void {
32
- this.watcher?.close();
33
- this.watcher = null;
34
- if (this.timer !== null) clearTimeout(this.timer);
35
- this.timer = null;
36
- }
37
-
38
- private schedule(): void {
39
- if (this.timer !== null) clearTimeout(this.timer);
40
- this.timer = setTimeout(() => {
41
- this.timer = null;
42
- this.fire();
43
- }, DEBOUNCE_MS);
44
- this.timer.unref();
45
- }
46
-
47
- private fire(): void {
48
- try {
49
- this.onChange();
50
- } catch (err) {
51
- log.warn({ err: errMsg(err) }, 'connectors: change handler failed');
52
- }
53
- }
54
- }