youmd 0.8.15 → 0.8.17
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/commands/remote.d.ts +3 -0
- package/dist/commands/remote.d.ts.map +1 -1
- package/dist/commands/remote.js +107 -3
- package/dist/commands/remote.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +10 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/remote-command.d.ts +95 -0
- package/dist/lib/remote-command.d.ts.map +1 -0
- package/dist/lib/remote-command.js +279 -0
- package/dist/lib/remote-command.js.map +1 -0
- package/dist/lib/remote-executor.d.ts +96 -0
- package/dist/lib/remote-executor.d.ts.map +1 -0
- package/dist/lib/remote-executor.js +438 -0
- package/dist/lib/remote-executor.js.map +1 -0
- package/dist/lib/request-id.d.ts +15 -0
- package/dist/lib/request-id.d.ts.map +1 -0
- package/dist/lib/request-id.js +54 -0
- package/dist/lib/request-id.js.map +1 -0
- package/dist/lib/skill-catalog.d.ts.map +1 -1
- package/dist/lib/skill-catalog.js +10 -0
- package/dist/lib/skill-catalog.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +109 -1
- package/dist/mcp/registry.js.map +1 -1
- package/package.json +1 -1
- package/skills/remote-machine.md +69 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-machine remote command bus convention (CROSS-MACHINE-AGENTS.md §3).
|
|
4
|
+
*
|
|
5
|
+
* Phase 1 is bus-only: commands and results travel as ordinary agent-bus
|
|
6
|
+
* messages distinguished by `channel` + `kind` + `metadata`. This module owns
|
|
7
|
+
* the envelope schema, the daemon-side handler, and a shared dispatch/poll
|
|
8
|
+
* helper so the CLI and MCP tool do not duplicate the wire format.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.SeenRequestSet = exports.DEFAULT_COMMAND_TTL_MS = exports.REMOTE_RESULT_CHANNEL = exports.REMOTE_COMMAND_CHANNEL = void 0;
|
|
45
|
+
exports.buildCommandMetadata = buildCommandMetadata;
|
|
46
|
+
exports.dispatchRemoteCommand = dispatchRemoteCommand;
|
|
47
|
+
exports.pollForResult = pollForResult;
|
|
48
|
+
exports.shouldHandleCommand = shouldHandleCommand;
|
|
49
|
+
exports.handleRemoteCommand = handleRemoteCommand;
|
|
50
|
+
exports.describeWhitelist = describeWhitelist;
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const api_1 = require("./api");
|
|
53
|
+
const request_id_1 = require("./request-id");
|
|
54
|
+
const remote_executor_1 = require("./remote-executor");
|
|
55
|
+
exports.REMOTE_COMMAND_CHANNEL = "remote-command";
|
|
56
|
+
exports.REMOTE_RESULT_CHANNEL = "remote-command-result";
|
|
57
|
+
/** Default validity window for a dispatched command (ms). */
|
|
58
|
+
exports.DEFAULT_COMMAND_TTL_MS = 5 * 60 * 1000;
|
|
59
|
+
function asMetadata(value) {
|
|
60
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
61
|
+
? value
|
|
62
|
+
: {};
|
|
63
|
+
}
|
|
64
|
+
/** Build the command metadata (pure — for tests + dispatch). */
|
|
65
|
+
function buildCommandMetadata(opts, now = Date.now()) {
|
|
66
|
+
const ttl = opts.ttlMs && opts.ttlMs > 0 ? opts.ttlMs : exports.DEFAULT_COMMAND_TTL_MS;
|
|
67
|
+
return {
|
|
68
|
+
requestId: (0, request_id_1.generateRequestId)(),
|
|
69
|
+
action: opts.action,
|
|
70
|
+
args: opts.args,
|
|
71
|
+
issuedBy: opts.issuedBy ?? os.userInfo().username,
|
|
72
|
+
issuedAt: now,
|
|
73
|
+
expiresAt: now + ttl,
|
|
74
|
+
secretValuesExposed: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Dispatch a whitelisted command to a target machine over the agent bus. */
|
|
78
|
+
async function dispatchRemoteCommand(opts) {
|
|
79
|
+
if (!(0, remote_executor_1.isAllowedRemoteAction)(opts.action)) {
|
|
80
|
+
return { ok: false, error: `action not in whitelist: ${opts.action}` };
|
|
81
|
+
}
|
|
82
|
+
const meta = buildCommandMetadata({
|
|
83
|
+
action: opts.action,
|
|
84
|
+
args: opts.args,
|
|
85
|
+
ttlMs: opts.ttlMs,
|
|
86
|
+
});
|
|
87
|
+
const body = opts.message?.trim() ||
|
|
88
|
+
`${opts.action}${opts.args?.project ? ` ${opts.args.project}` : ""}`;
|
|
89
|
+
const res = await (0, api_1.sendAgentBusMessage)({
|
|
90
|
+
channel: exports.REMOTE_COMMAND_CHANNEL,
|
|
91
|
+
kind: "command",
|
|
92
|
+
body,
|
|
93
|
+
sourceHost: os.hostname(),
|
|
94
|
+
sourceAgent: opts.sourceAgent ?? "youmd remote",
|
|
95
|
+
sourceRuntime: process.version,
|
|
96
|
+
targetHost: opts.machine,
|
|
97
|
+
metadata: meta,
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
const errBody = res.data;
|
|
101
|
+
const message = errBody?.error?.message || `dispatch failed (HTTP ${res.status})`;
|
|
102
|
+
return { ok: false, error: message };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
data: { requestId: meta.requestId, expiresAt: meta.expiresAt, targetHost: opts.machine },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Bounded poll of the result channel for a matching requestId. */
|
|
110
|
+
async function pollForResult(opts) {
|
|
111
|
+
const interval = opts.intervalMs ?? 2000;
|
|
112
|
+
const timeout = opts.timeoutMs ?? 60000;
|
|
113
|
+
const deadline = Date.now() + timeout;
|
|
114
|
+
while (Date.now() < deadline) {
|
|
115
|
+
const res = await (0, api_1.listAgentBusMessages)({
|
|
116
|
+
channel: exports.REMOTE_RESULT_CHANNEL,
|
|
117
|
+
limit: 50,
|
|
118
|
+
});
|
|
119
|
+
if (res.ok) {
|
|
120
|
+
const match = (res.data?.messages || []).find((m) => {
|
|
121
|
+
const meta = asMetadata(m.metadata);
|
|
122
|
+
return meta.requestId === opts.requestId;
|
|
123
|
+
});
|
|
124
|
+
if (match) {
|
|
125
|
+
const meta = asMetadata(match.metadata);
|
|
126
|
+
return meta;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// ─── Daemon handler (target side) ──────────────────────────────────────────────
|
|
134
|
+
/** Local idempotency set so a replayed command is a no-op. Daemon-process scoped. */
|
|
135
|
+
class SeenRequestSet {
|
|
136
|
+
constructor() {
|
|
137
|
+
this.seen = new Set();
|
|
138
|
+
}
|
|
139
|
+
has(id) {
|
|
140
|
+
return this.seen.has(id);
|
|
141
|
+
}
|
|
142
|
+
add(id) {
|
|
143
|
+
this.seen.add(id);
|
|
144
|
+
// Bound memory: keep the most recent ~1000 ids.
|
|
145
|
+
if (this.seen.size > 1000) {
|
|
146
|
+
const first = this.seen.values().next().value;
|
|
147
|
+
if (first !== undefined)
|
|
148
|
+
this.seen.delete(first);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.SeenRequestSet = SeenRequestSet;
|
|
153
|
+
/**
|
|
154
|
+
* Pure decision: should the daemon act on this message? Filters channel,
|
|
155
|
+
* targetHost, idempotency, and expiry. No side effects — testable.
|
|
156
|
+
*/
|
|
157
|
+
function shouldHandleCommand(message, seen, hostname = os.hostname(), now = Date.now()) {
|
|
158
|
+
if (message.channel !== exports.REMOTE_COMMAND_CHANNEL) {
|
|
159
|
+
return { process: false, reason: "not a remote-command" };
|
|
160
|
+
}
|
|
161
|
+
const target = String(message.targetHost || "").toLowerCase();
|
|
162
|
+
if (!target || target !== hostname.toLowerCase()) {
|
|
163
|
+
return { process: false, reason: "not addressed to this host" };
|
|
164
|
+
}
|
|
165
|
+
const meta = asMetadata(message.metadata);
|
|
166
|
+
const requestId = typeof meta.requestId === "string" ? meta.requestId : "";
|
|
167
|
+
if (!requestId) {
|
|
168
|
+
return { process: false, reason: "missing requestId" };
|
|
169
|
+
}
|
|
170
|
+
if (seen.has(requestId)) {
|
|
171
|
+
return { process: false, reason: "already processed" };
|
|
172
|
+
}
|
|
173
|
+
const expiresAt = Number(meta.expiresAt);
|
|
174
|
+
if (Number.isFinite(expiresAt) && now > expiresAt) {
|
|
175
|
+
return { process: false, reason: "command expired" };
|
|
176
|
+
}
|
|
177
|
+
return { process: true };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Process one inbound command message end-to-end: validate, execute via the
|
|
181
|
+
* whitelisted executor, post a result message, and write a brainActivity audit
|
|
182
|
+
* row. Marks the requestId seen BEFORE execution so a concurrent replay is a
|
|
183
|
+
* no-op. Never throws.
|
|
184
|
+
*/
|
|
185
|
+
async function handleRemoteCommand(message, seen, log) {
|
|
186
|
+
const decision = shouldHandleCommand(message, seen);
|
|
187
|
+
if (!decision.process)
|
|
188
|
+
return;
|
|
189
|
+
const meta = asMetadata(message.metadata);
|
|
190
|
+
const requestId = String(meta.requestId);
|
|
191
|
+
const action = typeof meta.action === "string" ? meta.action : "(unknown)";
|
|
192
|
+
const args = meta.args && typeof meta.args === "object" && !Array.isArray(meta.args)
|
|
193
|
+
? meta.args
|
|
194
|
+
: undefined;
|
|
195
|
+
const issuerHost = message.sourceHost || undefined;
|
|
196
|
+
// Mark seen up-front for idempotency (a replay won't re-execute).
|
|
197
|
+
seen.add(requestId);
|
|
198
|
+
log?.(`remote-command ${action} (${requestId}) from ${issuerHost ?? "?"}`);
|
|
199
|
+
let result;
|
|
200
|
+
try {
|
|
201
|
+
result = await (0, remote_executor_1.executeRemoteAction)({ action, args });
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
result = {
|
|
205
|
+
ok: false,
|
|
206
|
+
action,
|
|
207
|
+
exitCode: null,
|
|
208
|
+
output: "",
|
|
209
|
+
error: err instanceof Error ? err.message : String(err),
|
|
210
|
+
status: "error",
|
|
211
|
+
secretValuesExposed: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const resultMeta = {
|
|
215
|
+
requestId,
|
|
216
|
+
ok: result.ok,
|
|
217
|
+
action: result.action,
|
|
218
|
+
exitCode: result.exitCode,
|
|
219
|
+
output: result.output,
|
|
220
|
+
gitState: result.gitState,
|
|
221
|
+
status: result.status,
|
|
222
|
+
error: result.error,
|
|
223
|
+
completedAt: Date.now(),
|
|
224
|
+
secretValuesExposed: false,
|
|
225
|
+
};
|
|
226
|
+
const summary = result.ok
|
|
227
|
+
? `${result.action} ok`
|
|
228
|
+
: `${result.action} ${result.status}: ${result.error ?? "failed"}`;
|
|
229
|
+
// Post the result back to the issuer.
|
|
230
|
+
try {
|
|
231
|
+
await (0, api_1.sendAgentBusMessage)({
|
|
232
|
+
channel: exports.REMOTE_RESULT_CHANNEL,
|
|
233
|
+
kind: "result",
|
|
234
|
+
body: summary,
|
|
235
|
+
sourceHost: os.hostname(),
|
|
236
|
+
sourceAgent: "youmd remote daemon",
|
|
237
|
+
sourceRuntime: process.version,
|
|
238
|
+
targetHost: issuerHost,
|
|
239
|
+
metadata: resultMeta,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
log?.(`failed to post remote-command-result: ${err instanceof Error ? err.message : err}`);
|
|
244
|
+
}
|
|
245
|
+
// Audit row — every dispatch outcome is recorded.
|
|
246
|
+
try {
|
|
247
|
+
await (0, api_1.recordBrainActivity)({
|
|
248
|
+
activityId: `remote-command:${requestId}`,
|
|
249
|
+
source: "daemon",
|
|
250
|
+
channel: exports.REMOTE_RESULT_CHANNEL,
|
|
251
|
+
kind: "result",
|
|
252
|
+
status: result.ok ? "ok" : result.status === "rejected" ? "warn" : "error",
|
|
253
|
+
title: `remote command ${result.action}`,
|
|
254
|
+
detail: summary,
|
|
255
|
+
sourceHost: os.hostname(),
|
|
256
|
+
sourceAgent: "youmd remote daemon",
|
|
257
|
+
sourceRuntime: process.version,
|
|
258
|
+
metadata: {
|
|
259
|
+
requestId,
|
|
260
|
+
action: result.action,
|
|
261
|
+
ok: result.ok,
|
|
262
|
+
status: result.status,
|
|
263
|
+
exitCode: result.exitCode,
|
|
264
|
+
issuerHost: issuerHost ?? null,
|
|
265
|
+
project: args?.project ?? null,
|
|
266
|
+
secretValuesExposed: false,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
log?.(`failed to write remote-command audit: ${err instanceof Error ? err.message : err}`);
|
|
272
|
+
}
|
|
273
|
+
log?.(summary);
|
|
274
|
+
}
|
|
275
|
+
/** Human-readable list of whitelisted actions for help/usage text. */
|
|
276
|
+
function describeWhitelist() {
|
|
277
|
+
return Object.entries(remote_executor_1.ACTION_SPECS).map(([id, spec]) => `${id} — ${spec.description}${spec.mutating === "reversible" ? " (mutating, reversible)" : ""}`);
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=remote-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-command.js","sourceRoot":"","sources":["../../src/lib/remote-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-machine remote command executor (CROSS-MACHINE-AGENTS.md §5 + §7).
|
|
3
|
+
*
|
|
4
|
+
* This is the HARD security boundary for Phase 1. A remote agent can only ever
|
|
5
|
+
* trigger the fixed, typed action table below — never arbitrary shell, never an
|
|
6
|
+
* interpolated command string, never a path outside a known You.md project root.
|
|
7
|
+
*
|
|
8
|
+
* Security guarantees implemented here:
|
|
9
|
+
* 1. WHITELIST ONLY — `action` must be a key of ACTION_SPECS. Anything else is
|
|
10
|
+
* rejected before any process is spawned.
|
|
11
|
+
* 2. NO SHELL, NO EVAL — every action runs `git`/process via execFile with an
|
|
12
|
+
* explicit argv ARRAY and `shell: false`. Arguments are never concatenated
|
|
13
|
+
* into a command string, so shell metacharacters in args are inert.
|
|
14
|
+
* 3. PATH CONTAINMENT — the project directory is resolved ONLY against the
|
|
15
|
+
* caller's known You.md workspace/project roots (lib/project.ts). Any
|
|
16
|
+
* resolved path that escapes those roots (`..`, symlinks, absolute paths)
|
|
17
|
+
* is rejected.
|
|
18
|
+
* 4. BOUNDED TIMEOUT — every spawn has a per-action timeout; runaway commands
|
|
19
|
+
* are killed.
|
|
20
|
+
* 5. OUTPUT REDACTION + TRUNCATION — stdout/stderr are scrubbed of
|
|
21
|
+
* secret-looking tokens and truncated before they leave the machine.
|
|
22
|
+
*/
|
|
23
|
+
/** A remote action's mutating classification, for audit + result reporting. */
|
|
24
|
+
export type RemoteActionMutating = "no" | "reversible";
|
|
25
|
+
/** The exact set of actions a remote machine may trigger. The keys ARE the
|
|
26
|
+
* security boundary — extending it requires a code change + review. */
|
|
27
|
+
export declare const ALLOWED_REMOTE_ACTIONS: readonly ["git.status", "git.last_activity", "git.commit_push", "git.pull", "agent.status"];
|
|
28
|
+
export type RemoteAction = (typeof ALLOWED_REMOTE_ACTIONS)[number];
|
|
29
|
+
export interface RemoteActionSpec {
|
|
30
|
+
/** Whether the action requires a resolved project directory. */
|
|
31
|
+
needsProject: boolean;
|
|
32
|
+
mutating: RemoteActionMutating;
|
|
33
|
+
/** Per-action wall-clock cap (ms). */
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
description: string;
|
|
36
|
+
}
|
|
37
|
+
export declare const ACTION_SPECS: Record<RemoteAction, RemoteActionSpec>;
|
|
38
|
+
export declare function isAllowedRemoteAction(action: unknown): action is RemoteAction;
|
|
39
|
+
export interface RemoteGitState {
|
|
40
|
+
branch: string | null;
|
|
41
|
+
dirty: boolean;
|
|
42
|
+
ahead: number;
|
|
43
|
+
behind: number;
|
|
44
|
+
lastCommit: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface RemoteExecResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
action: string;
|
|
49
|
+
/** null when the action never spawned a process (validation/rejection). */
|
|
50
|
+
exitCode: number | null;
|
|
51
|
+
/** Redacted + truncated combined output. */
|
|
52
|
+
output: string;
|
|
53
|
+
gitState?: RemoteGitState;
|
|
54
|
+
/** Set when ok=false: machine-readable reason. */
|
|
55
|
+
error?: string;
|
|
56
|
+
/** "rejected" = failed validation/whitelist/path; "error" = ran but failed. */
|
|
57
|
+
status: "ok" | "rejected" | "error";
|
|
58
|
+
/** Always false — this executor never echoes secret values. */
|
|
59
|
+
secretValuesExposed: false;
|
|
60
|
+
}
|
|
61
|
+
export declare function redactSecrets(text: string): string;
|
|
62
|
+
export declare function truncateOutput(text: string, max?: number): string;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve `project` to an absolute directory that is GUARANTEED to live inside
|
|
65
|
+
* one of the caller's known You.md workspace/project roots. Returns null on any
|
|
66
|
+
* rejection (escape attempt, missing project, no roots). Never throws.
|
|
67
|
+
*
|
|
68
|
+
* Rules:
|
|
69
|
+
* - `project` must be a simple name token — no path separators, no `..`,
|
|
70
|
+
* no leading `~` or `/`. (We resolve names to dirs ourselves.)
|
|
71
|
+
* - The candidate must be a directory whose REAL path (symlinks resolved) is
|
|
72
|
+
* inside a known root's REAL path.
|
|
73
|
+
* - The candidate must look like a real project (git repo or project marker).
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveProjectDir(project: unknown, opts?: {
|
|
76
|
+
startDir?: string;
|
|
77
|
+
}): {
|
|
78
|
+
ok: true;
|
|
79
|
+
dir: string;
|
|
80
|
+
} | {
|
|
81
|
+
ok: false;
|
|
82
|
+
reason: string;
|
|
83
|
+
};
|
|
84
|
+
export interface ExecuteRemoteOptions {
|
|
85
|
+
action: unknown;
|
|
86
|
+
args?: Record<string, unknown>;
|
|
87
|
+
/** Override cwd used for project-root discovery (tests). */
|
|
88
|
+
startDir?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Validate + execute a single remote action. Always resolves (never throws) and
|
|
92
|
+
* always returns a structured result, so the daemon can post a clean
|
|
93
|
+
* remote-command-result for both success and every rejection path.
|
|
94
|
+
*/
|
|
95
|
+
export declare function executeRemoteAction(opts: ExecuteRemoteOptions): Promise<RemoteExecResult>;
|
|
96
|
+
//# sourceMappingURL=remote-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-executor.d.ts","sourceRoot":"","sources":["../../src/lib/remote-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAeH,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,YAAY,CAAC;AAEvD;wEACwE;AACxE,eAAO,MAAM,sBAAsB,6FAMzB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,gBAAgB,CA+B/D,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,YAAY,CAK7E;AAID,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,MAAM,EAAE,IAAI,GAAG,UAAU,GAAG,OAAO,CAAC;IACpC,+DAA+D;IAC/D,mBAAmB,EAAE,KAAK,CAAC;CAC5B;AAqBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBlD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAmB,GAAG,MAAM,CAG3E;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA6E3D;AAmGD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAcD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA6I3B"}
|