@stage-labs/metro 0.1.0-beta.202 → 0.1.0-beta.204

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.202",
3
+ "version": "0.1.0-beta.204",
4
4
  "description": "The metro command line: run your agent on this machine, relaying its chat channels and connectors to Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.1.0-beta.202",
3
+ "version": "0.1.0-beta.204",
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",
@@ -7,7 +7,8 @@ export type Verb =
7
7
  | 'unreact'
8
8
  | 'edit'
9
9
  | 'delete'
10
- | 'read';
10
+ | 'read'
11
+ | 'typing';
11
12
 
12
13
  export interface ToolResult {
13
14
  content: { type: 'text'; text: string }[];
@@ -133,6 +133,8 @@ const PRIOR_WORKER: ReadonlySet<string> = new Set(['08a0cd8710df285d6512245246bb
133
133
  const PRIOR_SKILL: ReadonlySet<string> = new Set([
134
134
  '0b3d122ed95beff09d579cf912cd4238e1db524c41fce4b314de57d6ff5908ad',
135
135
  '36f4fb57f231a119d717b5e3d6ccb654f94960fcca708683f376679aead8df25',
136
+ '3840bc50253e51f431691376cf29a925b8245d87b77b8b20804a159e378a6e67',
137
+ 'e6b59eba825b4568dfa9ace5049fd9568a2e10d731719976ad2657084fd4e5f9',
136
138
  ]);
137
139
 
138
140
  const digest = (text: string): string => createHash('sha256').update(text).digest('hex');
@@ -186,9 +186,15 @@ function makeVerbHandler(verb: string, spec: VerbSpec): MessageHandler {
186
186
  };
187
187
  }
188
188
 
189
+ const handleTyping = async ({ line, ctx }: MessageArgs): Promise<ToolResult> => {
190
+ await ctx.call('typing', { line });
191
+ return ok('typing shown');
192
+ };
193
+
189
194
  const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
190
195
  send: handleSend,
191
196
  read: handleRead,
197
+ typing: handleTyping,
192
198
  ...Object.fromEntries(
193
199
  Object.entries(MESSAGE_VERBS).map(([verb, spec]) => [verb, makeVerbHandler(verb, spec)]),
194
200
  ),
@@ -85,6 +85,19 @@ export const COMMON_TOOLS: ToolDef[] = [
85
85
  required: ['line', 'message_id', 'emoji'],
86
86
  },
87
87
  },
88
+ {
89
+ name: 'typing',
90
+ group: 'write',
91
+ description:
92
+ 'Show "typing…" once in a chat while you prepare an answer. The network clears it by itself after its own time (about 5 s on the Telegram bot, 10 s on Discord, 25 s on WhatsApp) or when you send. Call it again to show it again. Args: line.',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ line: lineProp,
97
+ },
98
+ required: ['line'],
99
+ },
100
+ },
88
101
  {
89
102
  name: 'unreact',
90
103
  group: 'write',
@@ -92,6 +92,13 @@ function listAccounts(id: string): void {
92
92
  });
93
93
  }
94
94
 
95
+ async function typing(id: string, args: Record<string, unknown>): Promise<void> {
96
+ const { line, account } = args as { line: string; account?: string };
97
+ const { accountId, channelId } = routeOf(line, account);
98
+ await rest(accountId, 'POST', `/channels/${channelId}/typing`);
99
+ respond(id, { result: { ok: true, account: accountId } });
100
+ }
101
+
95
102
  async function react(id: string, args: Record<string, unknown>): Promise<void> {
96
103
  const { line, messageId, emoji, account } = args as {
97
104
  line: string;
@@ -198,6 +205,7 @@ const HANDLERS: Record<string, StationHandler> = {
198
205
  groupRemoveMembers: groupRemoveHandler,
199
206
  send,
200
207
  react,
208
+ typing,
201
209
  edit,
202
210
  delete: remove,
203
211
  read,
@@ -13,6 +13,7 @@ export const discordBotStation: Station = {
13
13
  'edit',
14
14
  'delete',
15
15
  'read',
16
+ 'typing',
16
17
  ]),
17
18
  groupOps: new Set<GroupOp>(['create_group', 'add_members', 'remove_members']),
18
19
  attachmentMode: 'canonical',
@@ -157,6 +157,15 @@ async function react(id: string, args: Record<string, unknown>): Promise<void> {
157
157
  respond(id, { result: { ok: true, account: accountId } });
158
158
  }
159
159
 
160
+ async function typing(id: string, args: Record<string, unknown>): Promise<void> {
161
+ const { line, account } = args as { line: string; account?: string };
162
+ const { accountId, chatId, topicId } = targetOf(line, account);
163
+ const body: Record<string, unknown> = { chat_id: chatId, action: 'typing' };
164
+ if (topicId !== undefined) body.message_thread_id = topicId;
165
+ await tg(accountId, 'sendChatAction', body);
166
+ respond(id, { result: { ok: true, account: accountId } });
167
+ }
168
+
160
169
  async function edit(id: string, args: Record<string, unknown>): Promise<void> {
161
170
  const { line, messageId, text, account } = args as {
162
171
  line: string;
@@ -217,6 +226,7 @@ const HANDLERS: Record<string, StationHandler> = {
217
226
  accounts: (id) => listAccounts(id),
218
227
  send,
219
228
  react,
229
+ typing,
220
230
  edit,
221
231
  delete: remove,
222
232
  listMembers,
@@ -12,6 +12,7 @@ export const telegramBotStation: Station = {
12
12
  'unreact',
13
13
  'edit',
14
14
  'delete',
15
+ 'typing',
15
16
  ]),
16
17
  attachmentMode: 'canonical',
17
18
  profileFields: new Set<ProfileField>(PROFILE_FIELDS),
@@ -127,6 +127,14 @@ function makeReact(clientFor: ClientFor): StationHandler {
127
127
  };
128
128
  }
129
129
 
130
+ function makeTyping(clientFor: ClientFor): StationHandler {
131
+ return async (id, args) => {
132
+ const { accountId, client, jid } = resolve(args, clientFor);
133
+ await guard(() => client.showTyping(jid));
134
+ respond(id, { result: { ok: true, account: accountId } });
135
+ };
136
+ }
137
+
130
138
  function makeEdit(clientFor: ClientFor): StationHandler {
131
139
  return async (id, args) => {
132
140
  const { accountId, client, jid } = resolve(args, clientFor);
@@ -201,6 +209,7 @@ export function makeHandleCall(
201
209
  accounts: makeAccounts(clientFor),
202
210
  send: makeSend(clientFor),
203
211
  react: makeReact(clientFor),
212
+ typing: makeTyping(clientFor),
204
213
  edit: makeEdit(clientFor),
205
214
  delete: makeDelete(clientFor),
206
215
  resolve_sender: makeResolveSender(clientFor),
@@ -50,6 +50,7 @@ export interface WAClient {
50
50
  sendText(jid: string, text: string, quotedId?: string): Promise<string>;
51
51
  sendMedia(jid: string, media: WAMedia, quotedId?: string): Promise<string>;
52
52
  sendReaction(jid: string, messageId: string, emoji: string): Promise<void>;
53
+ showTyping(jid: string): Promise<void>;
53
54
  editMessage(jid: string, messageId: string, text: string): Promise<void>;
54
55
  deleteMessage(jid: string, messageId: string): Promise<void>;
55
56
  reuploadMedia(m: WAMessage): Promise<WAMessage>;
@@ -339,6 +340,10 @@ export function createClient(account: WhatsAppAccount): WAClient {
339
340
  quotedId ? quotedOpts(st, jid, quotedId) : undefined,
340
341
  );
341
342
  },
343
+ async showTyping(jid) {
344
+ const sock = await ready(st);
345
+ await sock.sendPresenceUpdate('composing', jid);
346
+ },
342
347
  async sendReaction(jid, messageId, emoji) {
343
348
  const target = targetKey(st.keys, jid, messageId, 'react to');
344
349
  await send(st, jid, { react: { text: emoji, key: target } });
@@ -13,6 +13,7 @@ export const whatsappStation: Station = {
13
13
  'unreact',
14
14
  'edit',
15
15
  'delete',
16
+ 'typing',
16
17
  ]),
17
18
  attachmentMode: 'canonical',
18
19
  profileFields: new Set<ProfileField>(PROFILE_FIELDS),
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.202"
2
+ "version": "0.1.0-beta.204"
3
3
  }