@stage-labs/metro 0.1.0-beta.325 → 0.1.0-beta.327
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/agent.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/lock.js +15 -3
- package/dist/root-release-io.js +1 -2
- package/dist/root-release.js +2 -3
- package/dist/route.js +1 -1
- package/dist/service.js +4 -4
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/runtime/node_modules/@metro-labs/core/package.json +3 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/brands.ts +100 -0
- package/runtime/node_modules/@metro-labs/core/src/favicon.ts +38 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/node_modules/@metro-labs/core/src/skill-source.ts +6 -6
- package/runtime/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/package.json +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +0 -24
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +4 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/github-fetch.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/github-skills.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +3 -2
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -7
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +10 -19
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/server-approvals.ts +2 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp-server/announced.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/tailscale-guard.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +1 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/gmail-attach.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +6 -21
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +12 -61
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +13 -25
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +5 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +2 -7
- package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +12 -12
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +1 -1
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +5 -23
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/discord-bot/src/channels.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +10 -35
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -11
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +6 -34
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +11 -14
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +1 -3
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +2 -3
- package/runtime/node_modules/@metro-labs/gmail/package.json +1 -1
- package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +3 -3
- package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +8 -8
- package/runtime/node_modules/@metro-labs/gmail/src/managed.ts +3 -2
- package/runtime/node_modules/@metro-labs/http/package.json +1 -1
- package/runtime/node_modules/@metro-labs/http/src/agent-signature.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +3 -4
- package/runtime/node_modules/@metro-labs/http/src/connector-discovery.ts +0 -113
- package/runtime/node_modules/@metro-labs/http/src/connector-input.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-mcp.ts +12 -13
- package/runtime/node_modules/@metro-labs/http/src/connector-oauth.ts +2 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-policy.ts +1 -6
- package/runtime/node_modules/@metro-labs/http/src/connector-public-url.ts +4 -2
- package/runtime/node_modules/@metro-labs/http/src/connector-tools.ts +43 -13
- package/runtime/node_modules/@metro-labs/http/src/connector-url.ts +0 -6
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-protocol.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-tools.ts +0 -2
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +2 -2
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +6 -7
- package/runtime/node_modules/@metro-labs/inference/package.json +2 -1
- package/runtime/node_modules/@metro-labs/inference/src/attempts.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/bedrock.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/codex-translate.ts +2 -2
- package/runtime/node_modules/@metro-labs/inference/src/delivered.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/effort.ts +0 -6
- package/runtime/node_modules/@metro-labs/inference/src/fallback.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/forward.ts +5 -5
- package/runtime/node_modules/@metro-labs/inference/src/measure.ts +2 -3
- package/runtime/node_modules/@metro-labs/inference/src/named.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/probes.ts +98 -0
- package/runtime/node_modules/@metro-labs/inference/src/usage.ts +5 -5
- package/runtime/node_modules/@metro-labs/outlook/package.json +1 -1
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +6 -8
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +2 -1
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +3 -5
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +6 -6
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +10 -13
- package/runtime/node_modules/@metro-labs/telegram/package.json +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +7 -26
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +3 -6
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +3 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +6 -20
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +0 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +1 -16
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -8
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/package.json +1 -1
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +1 -8
- package/runtime/node_modules/@metro-labs/threema/src/chats.ts +4 -3
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +15 -17
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +2 -4
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +7 -7
- package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +1 -3
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +0 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/package.json +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +7 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +4 -10
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +14 -26
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +17 -58
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +1 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +10 -31
- package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -14
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +3 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +8 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +4 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/network.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/read-attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +23 -46
- package/runtime/node_modules/@metro-labs/xmtp/src/wallet.ts +5 -4
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +12 -15
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/package.json +3 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/brands.ts +100 -0
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/favicon.ts +38 -0
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/skill-source.ts +6 -6
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/sdk-runner/runner.json +1 -1
- package/runtime/sdk-runner/src/approvals.ts +2 -2
- package/runtime/sdk-runner/src/automation-command.ts +2 -2
- package/runtime/sdk-runner/src/calls.ts +0 -6
- package/runtime/sdk-runner/src/errors.ts +1 -1
- package/runtime/sdk-runner/src/link.ts +1 -1
- package/runtime/sdk-runner/src/runner.ts +2 -2
- package/runtime/sdk-runner/src/session-watch.ts +5 -34
- package/runtime/sdk-runner/src/task-state.ts +1 -1
- package/runtime/sdk-runner/src/tool-proxy.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +0 -40
package/dist/agent.js
CHANGED
|
@@ -9,7 +9,7 @@ import { withLock } from './lock.js';
|
|
|
9
9
|
import { currentModel, permissionMode, systemPrompt } from './route.js';
|
|
10
10
|
import { installDependencies, ranges, readOrNull } from './runtime-install.js';
|
|
11
11
|
import { findBun, localPort, runtimeDir } from './runtime.js';
|
|
12
|
-
|
|
12
|
+
const RUNNER_SOURCES = 'sdk-runner';
|
|
13
13
|
const STAMP = 'runner.json';
|
|
14
14
|
const ENTRY = join('src', 'main.ts');
|
|
15
15
|
const METRO_MODULES = join('node_modules', '@metro-labs');
|
package/dist/cli.js
CHANGED
|
@@ -93,7 +93,7 @@ async function run(command) {
|
|
|
93
93
|
if (handler !== undefined)
|
|
94
94
|
return handler();
|
|
95
95
|
process.stderr.write(USAGE);
|
|
96
|
-
return
|
|
96
|
+
return HELP.has(command) ? 0 : 1;
|
|
97
97
|
}
|
|
98
98
|
const code = await run(process.argv[2]).catch((err) => {
|
|
99
99
|
const message = err instanceof Error ? err.message : String(err);
|
package/dist/lock.js
CHANGED
|
@@ -2,6 +2,7 @@ import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeSy
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { readOrNull } from './runtime-install.js';
|
|
4
4
|
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
5
|
+
const EMPTY_GRACE_MS = 5_000;
|
|
5
6
|
export function processAlive(pid) {
|
|
6
7
|
try {
|
|
7
8
|
process.kill(pid, 0);
|
|
@@ -13,9 +14,13 @@ export function processAlive(pid) {
|
|
|
13
14
|
}
|
|
14
15
|
function lockHeld(path, now, staleMs) {
|
|
15
16
|
try {
|
|
16
|
-
|
|
17
|
+
const age = now - statSync(path).mtimeMs;
|
|
18
|
+
if (age >= staleMs)
|
|
17
19
|
return false;
|
|
18
|
-
const
|
|
20
|
+
const text = readFileSync(path, 'utf8').trim();
|
|
21
|
+
if (text === '')
|
|
22
|
+
return age < EMPTY_GRACE_MS;
|
|
23
|
+
const pid = Number(text);
|
|
19
24
|
return !Number.isInteger(pid) || pid <= 0 || processAlive(pid);
|
|
20
25
|
}
|
|
21
26
|
catch (err) {
|
|
@@ -28,7 +33,14 @@ export function takeLock(path, staleMs, now = Date.now(), retry = true) {
|
|
|
28
33
|
try {
|
|
29
34
|
mkdirSync(dirname(path), { recursive: true });
|
|
30
35
|
const fd = openSync(path, 'wx');
|
|
31
|
-
|
|
36
|
+
try {
|
|
37
|
+
writeSync(fd, String(process.pid));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
closeSync(fd);
|
|
41
|
+
rmSync(path, { force: true });
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
32
44
|
closeSync(fd);
|
|
33
45
|
return true;
|
|
34
46
|
}
|
package/dist/root-release-io.js
CHANGED
|
@@ -5,8 +5,7 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { ISSUER, isRecord, isVersion, PUBLISHER, RELEASE_DIR } from './root-release.js';
|
|
8
|
-
import { PACKAGE_NAME } from './version.js';
|
|
9
|
-
const REGISTRY = 'https://registry.npmjs.org';
|
|
8
|
+
import { PACKAGE_NAME, REGISTRY } from './version.js';
|
|
10
9
|
const ENCODED = PACKAGE_NAME.replace('/', '%2F');
|
|
11
10
|
const ROOT_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
|
12
11
|
const SETUP = join('runtime', 'node_modules', '@metro-labs', 'daemon', 'src', 'metro-user', 'install.ts');
|
package/dist/root-release.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { compareVersions, PACKAGE_NAME } from './version.js';
|
|
2
|
+
import { compareVersions, PACKAGE_NAME, REGISTRY } from './version.js';
|
|
3
3
|
export const RELEASE_DIR = '/usr/local/lib/metro/release';
|
|
4
4
|
export const PUBLISHER = 'https://github.com/bonustrack/metro/.github/workflows/publish-cli.yml@refs/heads/main';
|
|
5
5
|
export const ISSUER = 'https://token.actions.githubusercontent.com';
|
|
6
|
-
const REGISTRY = 'https://registry.npmjs.org';
|
|
7
6
|
const PROVENANCE = 'https://slsa.dev/provenance/v1';
|
|
8
7
|
const IN_TOTO = 'application/vnd.in-toto+json';
|
|
9
8
|
const VERSION_RE = /^\d{1,9}\.\d{1,9}\.\d{1,9}(?:-[0-9A-Za-z]{1,20}(?:\.[0-9A-Za-z]{1,20}){0,4})?$/;
|
|
@@ -27,7 +26,7 @@ function statementOf(bundle) {
|
|
|
27
26
|
throw new Error('the provenance statement is unreadable; refused');
|
|
28
27
|
return statement;
|
|
29
28
|
}
|
|
30
|
-
|
|
29
|
+
function provenanceNames(statement, version, sha512) {
|
|
31
30
|
const subjects = Array.isArray(statement.subject) ? statement.subject : [];
|
|
32
31
|
return (statement.predicateType === PROVENANCE &&
|
|
33
32
|
subjects.some((s) => isRecord(s) && s.name === subjectName(version) && isRecord(s.digest) && s.digest.sha512 === sha512));
|
package/dist/route.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { agentsDir } from './local.js';
|
|
4
|
-
const PROVIDERS = new Set(['bedrock', 'openrouter', 'codex', '
|
|
4
|
+
const PROVIDERS = new Set(['bedrock', 'openrouter', 'codex', 'routeway']);
|
|
5
5
|
const ANTHROPIC = 'anthropic';
|
|
6
6
|
const text = (value) => (typeof value === 'string' ? value : '');
|
|
7
7
|
function routedIn(cfg) {
|
package/dist/service.js
CHANGED
|
@@ -126,7 +126,8 @@ function systemdPlan(host, serveArgs, runAs) {
|
|
|
126
126
|
],
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
function launchdPlan(host,
|
|
129
|
+
function launchdPlan(host, serveArgs) {
|
|
130
|
+
const exec = [host.node, host.cli, 'serve', ...serveArgs];
|
|
130
131
|
const file = join(host.home, 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
131
132
|
const domain = `gui/${String(host.uid)}`;
|
|
132
133
|
const logFile = join(cacheDir(host), 'metro', 'serve', 'service.log');
|
|
@@ -146,13 +147,12 @@ function launchdPlan(host, exec) {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
export function servicePlan(host, serveArgs, runAs) {
|
|
149
|
-
const exec = [host.node, host.cli, 'serve', ...serveArgs];
|
|
150
150
|
if (runAs !== undefined && (host.platform !== 'linux' || host.uid !== 0))
|
|
151
151
|
throw new Error('--user installs a system service for another user: run it as root on Linux');
|
|
152
152
|
if (host.platform === 'linux')
|
|
153
153
|
return systemdPlan(host, serveArgs, runAs);
|
|
154
154
|
if (host.platform === 'darwin')
|
|
155
|
-
return launchdPlan(host,
|
|
155
|
+
return launchdPlan(host, serveArgs);
|
|
156
156
|
throw new Error(`metro service supports Linux (systemd) and macOS (launchd), not ${host.platform}`);
|
|
157
157
|
}
|
|
158
158
|
function splitUser(argv) {
|
|
@@ -287,7 +287,7 @@ function realDeps() {
|
|
|
287
287
|
const said = [result.stderr, result.stdout, result.error?.message].find((t) => typeof t === 'string' && t.trim() !== '') ?? '';
|
|
288
288
|
throw new Error(`${command.args.join(' ')} failed (exit ${String(code)}): ${said.trim()}`);
|
|
289
289
|
}
|
|
290
|
-
return { status: code
|
|
290
|
+
return { status: code };
|
|
291
291
|
},
|
|
292
292
|
running: serveLockedBy,
|
|
293
293
|
account: lookupAccount,
|
package/dist/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
export const PACKAGE_NAME = '@stage-labs/metro';
|
|
3
|
-
const REGISTRY = 'https://registry.npmjs.org';
|
|
3
|
+
export const REGISTRY = 'https://registry.npmjs.org';
|
|
4
4
|
const TIMEOUT_MS = 15_000;
|
|
5
5
|
export function parseVersion(raw) {
|
|
6
6
|
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(raw.trim());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage-labs/metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.327",
|
|
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": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test": "tsc --noEmit && node scripts/stage-runtime.mjs && bun test test/"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@stage-labs/config": "0.1.0-beta.
|
|
35
|
+
"@stage-labs/config": "0.1.0-beta.5",
|
|
36
36
|
"eslint": "^10.3.0",
|
|
37
37
|
"knip": "^6.15.0",
|
|
38
38
|
"madge": "^8.0.0",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"./log": "./src/log.ts",
|
|
9
9
|
"./events": "./src/events.ts",
|
|
10
10
|
"./is-record": "./src/is-record.ts",
|
|
11
|
+
"./favicon": "./src/favicon.ts",
|
|
12
|
+
"./brands": "./src/brands.ts",
|
|
11
13
|
"./runner-activity": "./src/runner-activity.ts",
|
|
12
14
|
"./automation-store": "./src/automation-store.ts",
|
|
13
15
|
"./automation-types": "./src/automation-types.ts",
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
"pino-pretty": "^13.1.3"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
|
-
"@stage-labs/config": "0.1.0-beta.
|
|
57
|
+
"@stage-labs/config": "0.1.0-beta.5",
|
|
56
58
|
"eslint": "^10.3.0",
|
|
57
59
|
"knip": "^6.15.0",
|
|
58
60
|
"madge": "^8.0.0",
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from './automation-types.js';
|
|
9
9
|
import { isRecord } from './is-record.js';
|
|
10
10
|
|
|
11
|
-
export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus
|
|
11
|
+
export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus } from './automation-types.js';
|
|
12
12
|
|
|
13
13
|
function payload<T extends { uuid: string }>(row: unknown, parse: (value: unknown) => T): T {
|
|
14
14
|
try {
|
|
@@ -5,7 +5,7 @@ export const AUTOMATION_PROMPT_MAX = 64 * 1024;
|
|
|
5
5
|
const PROMPT_JSON_MAX = 128 * 1024 + 2;
|
|
6
6
|
export const AUTOMATION_REQUEST_MAX = 512;
|
|
7
7
|
export const AUTOMATION_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
8
|
-
|
|
8
|
+
const AUTOMATION_ROUTINE_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
9
9
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
10
10
|
const STATES = ['dispatched', 'consumed', 'awaiting-completion', 'completed', 'failed', 'interrupted', 'coalesced', 'cancelled'] as const;
|
|
11
11
|
const DETAILS = ['sdk-completed', 'sdk-failed', 'runner-interrupted', 'newer-slot', 'owner-cancelled', 'worker-blocked', 'worker-completed'] as const;
|
|
@@ -29,7 +29,7 @@ const ERRORS = {
|
|
|
29
29
|
|
|
30
30
|
export type AutomationUuid = `${string}-${string}-${string}-${string}-${string}`;
|
|
31
31
|
export type AutomationState = (typeof STATES)[number];
|
|
32
|
-
|
|
32
|
+
type AutomationDetail = (typeof DETAILS)[number];
|
|
33
33
|
export type AutomationOutcome = 'completed' | 'blocked';
|
|
34
34
|
export type AutomationErrorCode = keyof typeof ERRORS;
|
|
35
35
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { baseDomain } from './favicon.js';
|
|
2
|
+
|
|
3
|
+
interface BrandLogo {
|
|
4
|
+
light: string;
|
|
5
|
+
dark?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const BY_HOST: Record<string, string> = {
|
|
9
|
+
'gemini.google.com': 'gemini',
|
|
10
|
+
'aws.amazon.com': 'aws',
|
|
11
|
+
'mail.google.com': 'gmail-0a242131',
|
|
12
|
+
'gmailmcp.googleapis.com': 'gmail-0a242131',
|
|
13
|
+
'drive.google.com': 'googledrive',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const BY_DOMAIN: Record<string, string> = {
|
|
17
|
+
'xmtp.org': 'xmtp',
|
|
18
|
+
'whatsapp.com': 'whatsapp',
|
|
19
|
+
'whatsapp.net': 'whatsapp',
|
|
20
|
+
'telegram.org': 'telegram',
|
|
21
|
+
't.me': 'telegram',
|
|
22
|
+
'discord.com': 'discord',
|
|
23
|
+
'discord.gg': 'discord',
|
|
24
|
+
'threema.ch': 'threema',
|
|
25
|
+
'outlook.com': 'outlook',
|
|
26
|
+
'office.com': 'outlook',
|
|
27
|
+
'live.com': 'outlook',
|
|
28
|
+
'microsoft.com': 'microsoft',
|
|
29
|
+
'sharepoint.com': 'sharepoint',
|
|
30
|
+
'linear.app': 'linear',
|
|
31
|
+
'notion.so': 'notion',
|
|
32
|
+
'notion.com': 'notion',
|
|
33
|
+
'claude.ai': 'claude',
|
|
34
|
+
'anthropic.com': 'claude',
|
|
35
|
+
'slack.com': 'slack',
|
|
36
|
+
'github.com': 'github',
|
|
37
|
+
'openrouter.ai': 'openrouter',
|
|
38
|
+
'routeway.ai': 'routeway',
|
|
39
|
+
'openai.com': 'openai',
|
|
40
|
+
'mistral.ai': 'mistral',
|
|
41
|
+
'qwen.ai': 'qwen',
|
|
42
|
+
'qwenlm.ai': 'qwen',
|
|
43
|
+
'chatgpt.com': 'openai',
|
|
44
|
+
'x.ai': 'grok',
|
|
45
|
+
'grok.com': 'grok',
|
|
46
|
+
'atlassian.com': 'atlassian',
|
|
47
|
+
'atlassian.net': 'atlassian',
|
|
48
|
+
'hubspot.com': 'hubspot',
|
|
49
|
+
'stripe.com': 'stripe',
|
|
50
|
+
'sentry.io': 'sentry',
|
|
51
|
+
'asana.com': 'asana',
|
|
52
|
+
'intercom.com': 'intercom',
|
|
53
|
+
'intercom.io': 'intercom',
|
|
54
|
+
'cloudflare.com': 'cloudflare',
|
|
55
|
+
'figma.com': 'figma',
|
|
56
|
+
'box.com': 'box',
|
|
57
|
+
'zapier.com': 'zapier',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const WITH_DARK = new Set(['github', 'openrouter', 'aws', 'openai', 'grok']);
|
|
61
|
+
const RASTER = new Set(['routeway']);
|
|
62
|
+
|
|
63
|
+
function logoOf(slug: string): BrandLogo {
|
|
64
|
+
const light = `/brands/${slug}.${RASTER.has(slug) ? 'png' : 'svg'}`;
|
|
65
|
+
return WITH_DARK.has(slug) ? { light, dark: `/brands/${slug}-dark.svg` } : { light };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function brandLogo(url: string): BrandLogo | null {
|
|
69
|
+
let host: string;
|
|
70
|
+
try {
|
|
71
|
+
host = new URL(url).hostname.toLowerCase();
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const slug = BY_HOST[host] ?? BY_DOMAIN[baseDomain(host)];
|
|
76
|
+
return slug === undefined ? null : logoOf(slug);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function brandSrc(url: string, dark: boolean): string | null {
|
|
80
|
+
const logo = brandLogo(url);
|
|
81
|
+
if (logo === null) return null;
|
|
82
|
+
return dark ? (logo.dark ?? logo.light) : logo.light;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ConnectorIcon {
|
|
86
|
+
src: string;
|
|
87
|
+
mimeType?: string;
|
|
88
|
+
sizes?: string[];
|
|
89
|
+
theme?: 'light' | 'dark';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const BRAND_ORIGIN = 'https://metro.box';
|
|
93
|
+
|
|
94
|
+
const brandIcon = (path: string, theme?: 'light' | 'dark'): ConnectorIcon => ({ src: `${BRAND_ORIGIN}${path}`, mimeType: path.endsWith('.png') ? 'image/png' : 'image/svg+xml', ...(theme === undefined ? {} : { theme }) });
|
|
95
|
+
|
|
96
|
+
export function brandIcons(url: string): ConnectorIcon[] {
|
|
97
|
+
const logo = brandLogo(url);
|
|
98
|
+
if (logo === null) return [];
|
|
99
|
+
return logo.dark === undefined ? [brandIcon(logo.light)] : [brandIcon(logo.light, 'light'), brandIcon(logo.dark, 'dark')];
|
|
100
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const CHECKED_BASE = 'https://t1.gstatic.com/faviconV2';
|
|
2
|
+
|
|
3
|
+
const MULTI_LABEL_SUFFIXES = new Set([
|
|
4
|
+
'ac.uk', 'co.uk', 'gov.uk', 'me.uk', 'net.uk', 'org.uk',
|
|
5
|
+
'com.au', 'edu.au', 'net.au', 'org.au',
|
|
6
|
+
'co.nz', 'net.nz', 'org.nz',
|
|
7
|
+
'ac.jp', 'co.jp', 'ne.jp', 'or.jp',
|
|
8
|
+
'co.kr', 'co.za', 'co.il', 'co.in', 'net.in', 'org.in',
|
|
9
|
+
'com.ar', 'com.br', 'net.br', 'org.br',
|
|
10
|
+
'com.cn', 'net.cn', 'org.cn',
|
|
11
|
+
'com.hk', 'com.mx', 'com.pl', 'com.sg', 'com.tr', 'com.tw',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export function baseDomain(host: string): string {
|
|
15
|
+
const labels = host
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/\.$/, '')
|
|
18
|
+
.split('.')
|
|
19
|
+
.filter((label) => label !== '');
|
|
20
|
+
if (labels.length <= 2) return labels.join('.');
|
|
21
|
+
const lastTwo = labels.slice(-2).join('.');
|
|
22
|
+
if (MULTI_LABEL_SUFFIXES.has(lastTwo)) return labels.slice(-3).join('.');
|
|
23
|
+
return lastTwo;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function siteDomain(url: string): string {
|
|
27
|
+
try {
|
|
28
|
+
const domain = baseDomain(new URL(url).hostname);
|
|
29
|
+
return domain.includes('.') ? domain : '';
|
|
30
|
+
} catch {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function checkedFaviconUrl(url: string, size: number): string {
|
|
36
|
+
const domain = siteDomain(url);
|
|
37
|
+
return domain === '' ? '' : `${CHECKED_BASE}?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(`https://${domain}`)}&size=${String(size)}`;
|
|
38
|
+
}
|
|
@@ -2,8 +2,6 @@ export type Line = string & { readonly __line: unique symbol };
|
|
|
2
2
|
export const asLine = (s: string): Line => s as Line;
|
|
3
3
|
|
|
4
4
|
const PREFIX = 'metro://';
|
|
5
|
-
const build = (station: string, ...seg: (string | number)[]): Line =>
|
|
6
|
-
asLine(`${PREFIX}${station}/${seg.map(String).join('/')}`);
|
|
7
5
|
|
|
8
6
|
function parseAccountScoped(
|
|
9
7
|
line: Line | string,
|
|
@@ -42,11 +40,7 @@ function parseTelegramLine(
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
export const Line = {
|
|
45
|
-
|
|
46
|
-
build('claude', orgId, sessionId),
|
|
47
|
-
webhook: (endpointId: string): Line => build('webhook', endpointId),
|
|
48
|
-
user: (station: string, id: string | number): Line =>
|
|
49
|
-
build(station, 'user', id),
|
|
43
|
+
webhook: (endpointId: string): Line => asLine(`${PREFIX}webhook/${endpointId}`),
|
|
50
44
|
|
|
51
45
|
parse(line: Line | string): { station: string; path: string[] } | null {
|
|
52
46
|
if (!line.startsWith(PREFIX)) return null;
|
|
@@ -68,7 +62,7 @@ export const Line = {
|
|
|
68
62
|
parseDiscord: (line: Line | string) =>
|
|
69
63
|
parseAccountScoped(line, 'discord-bot', isSnowflake),
|
|
70
64
|
|
|
71
|
-
parseTelegram:
|
|
65
|
+
parseTelegram: parseTelegramLine,
|
|
72
66
|
|
|
73
67
|
parseThreema: (line: Line | string) =>
|
|
74
68
|
parseAccountScoped(line, 'threema', isThreemaResource),
|
|
@@ -5,12 +5,6 @@ import { coerceErrorInfo, type TrainErrorInfo } from './train-error.js';
|
|
|
5
5
|
import type { StructuredEvent } from './events.js';
|
|
6
6
|
import type { CallMsg } from './stations/station-runtime.js';
|
|
7
7
|
|
|
8
|
-
export {
|
|
9
|
-
TrainError,
|
|
10
|
-
serializeTrainError,
|
|
11
|
-
type TrainErrorInfo,
|
|
12
|
-
} from './train-error.js';
|
|
13
|
-
|
|
14
8
|
const CALL_TIMEOUT_MS = 60_000;
|
|
15
9
|
|
|
16
10
|
export interface Pending {
|
|
@@ -4,10 +4,10 @@ import { isRecord } from './is-record.js';
|
|
|
4
4
|
export const SKILL_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
5
5
|
export const SKILL_HASH = /^[a-f0-9]{64}$/;
|
|
6
6
|
export const SKILL_COMMIT = /^[a-f0-9]{40}$/;
|
|
7
|
-
export const SKILL_FILE_MAX =
|
|
8
|
-
export const SKILL_TOTAL_MAX =
|
|
9
|
-
export const SKILL_FILES_MAX =
|
|
10
|
-
export const SKILL_MANIFEST_MAX =
|
|
7
|
+
export const SKILL_FILE_MAX = 512 * 1024;
|
|
8
|
+
export const SKILL_TOTAL_MAX = 32 * 1024 * 1024;
|
|
9
|
+
export const SKILL_FILES_MAX = 512;
|
|
10
|
+
export const SKILL_MANIFEST_MAX = 256 * 1024;
|
|
11
11
|
|
|
12
12
|
export interface SkillSource {
|
|
13
13
|
repository: string;
|
|
@@ -15,7 +15,7 @@ export interface SkillSource {
|
|
|
15
15
|
folder: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
interface SkillFile {
|
|
19
19
|
path: string;
|
|
20
20
|
size: number;
|
|
21
21
|
sha256: string;
|
|
@@ -48,7 +48,7 @@ export const skillRelease = (root: string, id: string): string => {
|
|
|
48
48
|
export function skillPath(path: string): boolean {
|
|
49
49
|
const parts = path.split('/');
|
|
50
50
|
return path.length <= 240 && parts.length >= 2 && parts.length <= 8 && SKILL_NAME.test(parts[0] ?? '') &&
|
|
51
|
-
parts.every((part) => /^[A-Za-z0-
|
|
51
|
+
parts.every((part) => /^[A-Za-z0-9_](?:[A-Za-z0-9 ._()-]{0,126}[A-Za-z0-9_.()-])?$/.test(part)) &&
|
|
52
52
|
!parts.slice(1, -1).includes('SKILL.md') && !(parts.length > 2 && parts.at(-1) === 'SKILL.md');
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRecord } from '../is-record.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const ACCOUNT_STATES = ['connected', 'connecting', 'logged_out', 'disconnected'] as const;
|
|
4
4
|
|
|
5
5
|
export type AccountState = (typeof ACCOUNT_STATES)[number];
|
|
6
6
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
1
5
|
export const attachDir = (): string =>
|
|
2
6
|
process.env.METRO_XMTP_ATTACH_DIR ??
|
|
3
7
|
`${process.env.HOME}/.cache/metro/messenger-uploads`;
|
|
@@ -89,14 +93,9 @@ const cachePathFor = async (
|
|
|
89
93
|
index: number,
|
|
90
94
|
meta: { mime?: string; name?: string },
|
|
91
95
|
): Promise<string> => {
|
|
92
|
-
const { mkdir } = await import('node:fs/promises');
|
|
93
|
-
const nodePath = await import('node:path');
|
|
94
96
|
const dir = attachDir();
|
|
95
97
|
await mkdir(dir, { recursive: true });
|
|
96
|
-
return
|
|
97
|
-
dir,
|
|
98
|
-
cacheFileName(messageId, index, meta.name, meta.mime),
|
|
99
|
-
);
|
|
98
|
+
return join(dir, cacheFileName(messageId, index, meta.name, meta.mime));
|
|
100
99
|
};
|
|
101
100
|
|
|
102
101
|
export const saveBufferToCache = async (
|
|
@@ -106,8 +105,6 @@ export const saveBufferToCache = async (
|
|
|
106
105
|
meta: { mime?: string; name?: string },
|
|
107
106
|
): Promise<SavedAttachment> => {
|
|
108
107
|
assertAttachmentSize(data.length);
|
|
109
|
-
const { writeFile, rename, rm } = await import('node:fs/promises');
|
|
110
|
-
const { randomUUID } = await import('node:crypto');
|
|
111
108
|
const path = await cachePathFor(messageId, index, meta);
|
|
112
109
|
const part = `${await cachePathFor(randomUUID(), index, meta)}.part`;
|
|
113
110
|
try {
|
|
@@ -125,7 +122,6 @@ export const saveStreamToCache = async (
|
|
|
125
122
|
index: number,
|
|
126
123
|
meta: { mime?: string; name?: string },
|
|
127
124
|
): Promise<SavedAttachment> => {
|
|
128
|
-
const { open, rename, rm } = await import('node:fs/promises');
|
|
129
125
|
const path = await cachePathFor(messageId, index, meta);
|
|
130
126
|
const part = `${path}.part`;
|
|
131
127
|
const handle = await open(part, 'w');
|
|
@@ -14,7 +14,7 @@ export interface ChannelEntry {
|
|
|
14
14
|
kind: 'direct' | 'group' | 'channel' | 'thread';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
interface ChannelCapability {
|
|
18
18
|
supported: boolean;
|
|
19
19
|
complete: boolean;
|
|
20
20
|
source: 'remote' | 'local' | 'mixed' | 'unsupported';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const MAX_TEXT_CHARS = 20_000;
|
|
2
2
|
|
|
3
3
|
const ENTITIES: Record<string, string> = {
|
|
4
4
|
amp: '&',
|
|
@@ -52,9 +52,9 @@ export function tidy(text: string): string {
|
|
|
52
52
|
.trim();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function capText(text: string
|
|
56
|
-
if (text.length <=
|
|
57
|
-
return `${text.slice(0,
|
|
55
|
+
export function capText(text: string): string {
|
|
56
|
+
if (text.length <= MAX_TEXT_CHARS) return text;
|
|
57
|
+
return `${text.slice(0, MAX_TEXT_CHARS)}\n[... ${String(text.length - MAX_TEXT_CHARS)} more characters]`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function bodyText(body: { contentType?: unknown; content?: unknown } | undefined): string {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mintId } from '../ids.js';
|
|
2
|
+
import { isRecord } from '../is-record.js';
|
|
2
3
|
import { Line } from '../lines.js';
|
|
3
4
|
import { TrainError } from '../train-error.js';
|
|
4
5
|
import { capText } from './mail-text.js';
|
|
@@ -47,7 +48,7 @@ export interface ReadQuery {
|
|
|
47
48
|
limit: number;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
const DEFAULT_LIMIT = 20;
|
|
51
52
|
export const MAX_LIMIT = 50;
|
|
52
53
|
const SEEN_MAX = 500;
|
|
53
54
|
const SUBJECT_MAX = 255;
|
|
@@ -160,7 +161,7 @@ export function subjectOf(station: MailStation, subject: string, text: string):
|
|
|
160
161
|
|
|
161
162
|
export const filesOf = (raw: unknown): OutgoingFile[] =>
|
|
162
163
|
(Array.isArray(raw) ? raw : [])
|
|
163
|
-
.
|
|
164
|
+
.filter(isRecord)
|
|
164
165
|
.filter((a) => typeof a.path === 'string' && a.path !== '')
|
|
165
166
|
.map((a) => ({
|
|
166
167
|
path: String(a.path),
|
|
@@ -36,7 +36,7 @@ export function pkcePair(verifier = randomBytes(32).toString('base64url')): Pkce
|
|
|
36
36
|
return { verifier, challenge: createHash('sha256').update(verifier).digest('base64url') };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
const newState = (): string => randomBytes(24).toString('base64url');
|
|
40
40
|
|
|
41
41
|
export function parseMailbox(value: unknown): string | null {
|
|
42
42
|
const mailbox = (typeof value === 'string' ? value.trim() : '').toLowerCase();
|
|
@@ -3,8 +3,8 @@ import { TrainError } from '../train-error.js';
|
|
|
3
3
|
export const PROFILE_FIELDS = ['name', 'bio', 'avatar'] as const;
|
|
4
4
|
export type ProfileField = (typeof PROFILE_FIELDS)[number];
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const NAME_MAX = 64;
|
|
7
|
+
const BIO_MAX = 512;
|
|
8
8
|
|
|
9
9
|
export interface ProfileAvatar {
|
|
10
10
|
path: string;
|
|
@@ -82,7 +82,7 @@ export interface MemberOutcome {
|
|
|
82
82
|
reason?: string;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
interface GroupCapability {
|
|
86
86
|
supported: boolean;
|
|
87
87
|
reason?: string;
|
|
88
88
|
}
|
|
@@ -98,7 +98,7 @@ export interface GroupResult {
|
|
|
98
98
|
|
|
99
99
|
export type ReadFilter = 'account' | 'query' | 'from' | 'until' | 'unread_only' | 'message_id';
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
type AttachmentMode = 'canonical' | 'native' | 'none';
|
|
102
102
|
|
|
103
103
|
export interface Station {
|
|
104
104
|
name: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isRecord } from './is-record.js';
|
|
2
|
+
|
|
1
3
|
export interface TrainErrorInfo {
|
|
2
4
|
code: string;
|
|
3
5
|
message: string;
|
|
@@ -41,10 +43,8 @@ export function serializeTrainError(err: unknown): {
|
|
|
41
43
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export function coerceErrorInfo(
|
|
45
|
-
if (!
|
|
46
|
-
const o = v as Record<string, unknown>;
|
|
47
|
-
if (typeof o.code !== 'string' || typeof o.message !== 'string')
|
|
46
|
+
export function coerceErrorInfo(o: unknown): TrainErrorInfo | undefined {
|
|
47
|
+
if (!isRecord(o) || typeof o.code !== 'string' || typeof o.message !== 'string')
|
|
48
48
|
return undefined;
|
|
49
49
|
const info: TrainErrorInfo = { code: o.code, message: o.message };
|
|
50
50
|
if (typeof o.retryable === 'boolean') info.retryable = o.retryable;
|
|
@@ -69,7 +69,8 @@ function parseFile(root: string, path: string, out: Buffer): FilesAnswer {
|
|
|
69
69
|
return { root, path, kind: 'file', bytes, modifiedAt: stamp(Number(mtime)), ...preview(out.subarray(end + 1), bytes) };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function
|
|
72
|
+
export function readAgentPath(user: AgentUser | null, path: string): FilesAnswer {
|
|
73
|
+
if (user === null) throw new ApiError('Claude Code does not run as its own user on this machine, so there is no agent folder to show', 409);
|
|
73
74
|
const target = join(user.home, ...segmentsOf(path));
|
|
74
75
|
const [file, argv] = asUser(user, 'sh', ['-c', SCRIPT, 'metro', target, String(PREVIEW_BYTES + 1)]);
|
|
75
76
|
const run = spawnSync(file, argv, { stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 64 * 1024 * 1024, timeout: 20_000 });
|
|
@@ -82,8 +83,3 @@ export function answerOf(root: string, path: string, out: Buffer): FilesAnswer {
|
|
|
82
83
|
const body = out.subarray(2);
|
|
83
84
|
return out.subarray(0, 1).toString() === 'D' ? folderAnswer(root, path, parseListing(body)) : parseFile(root, path, body);
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
-
export function readAgentPath(user: AgentUser | null, path: string): FilesAnswer {
|
|
87
|
-
if (user === null) throw new ApiError('Claude Code does not run as its own user on this machine, so there is no agent folder to show', 409);
|
|
88
|
-
return readAsAgent(user, path);
|
|
89
|
-
}
|