@lawaty/lacode 0.0.0-stage → 0.1.1
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/LICENSE +32 -0
- package/README.md +562 -2
- package/dist/agents.js +819 -0
- package/dist/commands.js +109 -0
- package/dist/index.js +2250 -0
- package/dist/lacode.js +254 -0
- package/dist/pool.js +487 -0
- package/dist/tunnel.js +180 -0
- package/docs/BENCHMARKS.md +127 -0
- package/docs/CONTEXT.md +211 -0
- package/docs/TUNNEL.md +174 -0
- package/docs/USER-STORIES.md +421 -0
- package/package.json +60 -4
- package/presets/README.md +121 -0
- package/presets/free-tier.json +60 -0
package/dist/lacode.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/lacode
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
// src/tunnel/index.ts
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
var ICONS = [
|
|
11
|
+
"utilities-terminal",
|
|
12
|
+
"dialog-information",
|
|
13
|
+
"dialog-error",
|
|
14
|
+
"dialog-warning",
|
|
15
|
+
"system-run",
|
|
16
|
+
"emblem-default"
|
|
17
|
+
];
|
|
18
|
+
function resolveTunnel(env = process.env) {
|
|
19
|
+
const port = Number(env.OC_TUNNEL_PORT);
|
|
20
|
+
return {
|
|
21
|
+
user: env.OC_LOCAL_USER ?? "",
|
|
22
|
+
port: Number.isFinite(port) && port > 0 ? port : 10022,
|
|
23
|
+
key: env.OC_NOTIFY_KEY ?? join(homedir(), ".ssh", "lacode_notify"),
|
|
24
|
+
connectTimeoutMs: Number(env.OC_CONNECT_TIMEOUT_MS) || 5e3
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function isRemote(env = process.env) {
|
|
28
|
+
return Boolean(env.SSH_CLIENT || env.SSH_TTY);
|
|
29
|
+
}
|
|
30
|
+
function normalizeUrgency(raw) {
|
|
31
|
+
return raw === "low" || raw === "critical" || raw === "normal" ? raw : "normal";
|
|
32
|
+
}
|
|
33
|
+
function normalizeIcon(raw) {
|
|
34
|
+
return ICONS.includes(raw) ? raw : "utilities-terminal";
|
|
35
|
+
}
|
|
36
|
+
var safeText = (raw, fallback, max) => {
|
|
37
|
+
const clean = raw.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
38
|
+
if (!clean) return fallback;
|
|
39
|
+
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
40
|
+
};
|
|
41
|
+
var safeTitle = (raw) => safeText(raw ?? "", "LaCode", 120);
|
|
42
|
+
var safeMessage = (raw) => safeText(raw ?? "", "", 400);
|
|
43
|
+
var FIELD_SEPARATOR = "\n";
|
|
44
|
+
var encodePayload = (request) => [
|
|
45
|
+
safeTitle(request.title),
|
|
46
|
+
safeMessage(request.message),
|
|
47
|
+
normalizeUrgency(request.urgency),
|
|
48
|
+
normalizeIcon(request.icon)
|
|
49
|
+
].join(FIELD_SEPARATOR);
|
|
50
|
+
var SSH_OPTS = [
|
|
51
|
+
"-o",
|
|
52
|
+
"BatchMode=yes",
|
|
53
|
+
"-o",
|
|
54
|
+
"IdentitiesOnly=yes",
|
|
55
|
+
"-o",
|
|
56
|
+
"ClearAllForwardings=yes",
|
|
57
|
+
"-o",
|
|
58
|
+
"LogLevel=ERROR",
|
|
59
|
+
"-o",
|
|
60
|
+
"StrictHostKeyChecking=accept-new"
|
|
61
|
+
];
|
|
62
|
+
var run = (command2, args, timeout) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = spawnSync(command2, args, { timeout, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
65
|
+
return {
|
|
66
|
+
ok: result.status === 0,
|
|
67
|
+
stdout: result.stdout ?? "",
|
|
68
|
+
stderr: (result.stderr ?? "").trim(),
|
|
69
|
+
// spawnSync reports a timeout as a null status and an ETIMEDOUT error; both
|
|
70
|
+
// are "did not arrive", not "crashed", and neither may throw.
|
|
71
|
+
detail: result.error ? String(result.error.code ?? result.error.message) : void 0
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { ok: false, stdout: "", stderr: "", detail: String(error) };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var have = (command2) => {
|
|
78
|
+
try {
|
|
79
|
+
return spawnSync("sh", ["-c", `command -v ${command2}`], { encoding: "utf8" }).status === 0;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var CHANNEL_BINARY = {
|
|
85
|
+
"notify-send": "notify-send",
|
|
86
|
+
osascript: "osascript",
|
|
87
|
+
"terminal-notifier": "terminal-notifier"
|
|
88
|
+
};
|
|
89
|
+
var DARWIN_SCRIPT = [
|
|
90
|
+
"on run argv",
|
|
91
|
+
" display notification (item 3 of argv) with title (item 2 of argv)",
|
|
92
|
+
"end run"
|
|
93
|
+
].join("\n");
|
|
94
|
+
var DARWIN_ARGV_MARKER = "lacode";
|
|
95
|
+
function localChannel(platform = process.platform) {
|
|
96
|
+
if (platform === "darwin") {
|
|
97
|
+
if (have(CHANNEL_BINARY.osascript)) return "osascript";
|
|
98
|
+
if (have(CHANNEL_BINARY["terminal-notifier"])) return "terminal-notifier";
|
|
99
|
+
return "none";
|
|
100
|
+
}
|
|
101
|
+
return have(CHANNEL_BINARY["notify-send"]) ? "notify-send" : "none";
|
|
102
|
+
}
|
|
103
|
+
var NO_CHANNEL = {
|
|
104
|
+
linux: "notify-send is not installed",
|
|
105
|
+
darwin: "neither osascript nor terminal-notifier is installed"
|
|
106
|
+
};
|
|
107
|
+
function sendLocal(request, platform = process.platform) {
|
|
108
|
+
const channel = localChannel(platform);
|
|
109
|
+
if (channel === "none") {
|
|
110
|
+
return { ok: false, detail: NO_CHANNEL[platform] ?? `no local notification channel on ${platform}`, transport: "local" };
|
|
111
|
+
}
|
|
112
|
+
const title = safeTitle(request.title);
|
|
113
|
+
const body = safeMessage(request.message);
|
|
114
|
+
const args = channel === "osascript" ? ["-e", DARWIN_SCRIPT, DARWIN_ARGV_MARKER, title, body] : channel === "terminal-notifier" ? ["-title", title, "-message", body, "-appName", "LaCode", "-priority", normalizeUrgency(request.urgency)] : (
|
|
115
|
+
// Byte-identical to the notify-send call this package has always made.
|
|
116
|
+
[title, body, `--urgency=${normalizeUrgency(request.urgency)}`, `--icon=${normalizeIcon(request.icon)}`]
|
|
117
|
+
);
|
|
118
|
+
const result = run(CHANNEL_BINARY[channel], args, 5e3);
|
|
119
|
+
return { ok: result.ok, detail: result.ok ? void 0 : result.stderr || result.detail, transport: "local" };
|
|
120
|
+
}
|
|
121
|
+
function sendRemote(request, tunnel) {
|
|
122
|
+
if (!tunnel.user) return { ok: false, detail: "OC_LOCAL_USER is unset; the desktop user for the tunnel is unknown", transport: "remote" };
|
|
123
|
+
const args = [
|
|
124
|
+
...SSH_OPTS,
|
|
125
|
+
"-o",
|
|
126
|
+
`ConnectTimeout=${Math.max(1, Math.round(tunnel.connectTimeoutMs / 1e3))}`,
|
|
127
|
+
"-i",
|
|
128
|
+
tunnel.key,
|
|
129
|
+
"-p",
|
|
130
|
+
String(tunnel.port),
|
|
131
|
+
`${tunnel.user}@localhost`,
|
|
132
|
+
"--",
|
|
133
|
+
encodePayload(request)
|
|
134
|
+
];
|
|
135
|
+
const result = run("ssh", args, tunnel.connectTimeoutMs + 2e3);
|
|
136
|
+
return { ok: result.ok, detail: result.ok ? void 0 : result.stderr || result.detail, transport: "remote" };
|
|
137
|
+
}
|
|
138
|
+
function notify(request, env = process.env, platform = process.platform) {
|
|
139
|
+
try {
|
|
140
|
+
return isRemote(env) ? sendRemote(request, resolveTunnel(env)) : sendLocal(request, platform);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return { ok: false, detail: String(error), transport: "none" };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function forward(host, argv2, options = {}) {
|
|
146
|
+
const env = options.env ?? process.env;
|
|
147
|
+
const timeoutMs = options.timeoutMs ?? (Number(env.OC_SSH_TIMEOUT_MS) || 3e4);
|
|
148
|
+
const result = run("ssh", [...SSH_OPTS, "-o", `ConnectTimeout=${Math.max(1, Math.round(timeoutMs / 1e3))}`, host, "--", ...argv2], timeoutMs);
|
|
149
|
+
return {
|
|
150
|
+
ok: result.ok,
|
|
151
|
+
detail: result.ok ? void 0 : result.stderr || result.detail,
|
|
152
|
+
transport: "remote",
|
|
153
|
+
stdout: result.stdout
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function describe(env = process.env, platform = process.platform) {
|
|
157
|
+
const tunnel = resolveTunnel(env);
|
|
158
|
+
return {
|
|
159
|
+
running: isRemote(env) ? "remote (over ssh)" : "local",
|
|
160
|
+
user: tunnel.user || "(unset)",
|
|
161
|
+
port: tunnel.port,
|
|
162
|
+
key: tunnel.key,
|
|
163
|
+
channel: localChannel(platform),
|
|
164
|
+
// Still exactly what it always meant -- is the notify-send binary present --
|
|
165
|
+
// rather than a second opinion about which channel runs.
|
|
166
|
+
notifySend: have("notify-send")
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// bin/lacode
|
|
171
|
+
if (!process.features?.typescript) {
|
|
172
|
+
if (process.env.LACODE_STRIP_TYPECHECKED === "1") {
|
|
173
|
+
console.error("lacode: this node cannot load TypeScript sources; node >= 22.18 is required.");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const { spawnSync: spawnSync2 } = await import("node:child_process");
|
|
177
|
+
const result = spawnSync2(
|
|
178
|
+
process.execPath,
|
|
179
|
+
["--experimental-strip-types", "--no-warnings", fileURLToPath(import.meta.url), ...process.argv.slice(2)],
|
|
180
|
+
{ stdio: "inherit", env: { ...process.env, LACODE_STRIP_TYPECHECKED: "1" } }
|
|
181
|
+
);
|
|
182
|
+
process.exit(result.status ?? 1);
|
|
183
|
+
}
|
|
184
|
+
var USAGE = `lacode \u2014 portable notifications and remote exec for LaCode
|
|
185
|
+
|
|
186
|
+
lacode notify TITLE [MESSAGE] [URGENCY] [ICON]
|
|
187
|
+
Send one desktop notification. Local host: straight to the desktop
|
|
188
|
+
session. Over ssh: handed to the desktop through the reverse forward.
|
|
189
|
+
URGENCY is low | normal | critical. Fire-and-forget: always exits 0.
|
|
190
|
+
|
|
191
|
+
lacode forward HOST -- COMMAND [ARG...]
|
|
192
|
+
Run COMMAND on HOST over ssh. Exits with the remote's status.
|
|
193
|
+
|
|
194
|
+
lacode doctor
|
|
195
|
+
Print what the environment resolves to. Diagnostics only.
|
|
196
|
+
|
|
197
|
+
Environment:
|
|
198
|
+
OC_LOCAL_USER desktop user the tunnel authenticates as
|
|
199
|
+
OC_TUNNEL_PORT reverse-forward listener port (default 10022)
|
|
200
|
+
OC_NOTIFY_KEY dedicated key (default ~/.ssh/lacode_notify)
|
|
201
|
+
OC_CONNECT_TIMEOUT_MS / OC_SSH_TIMEOUT_MS`;
|
|
202
|
+
var argv = process.argv.slice(2);
|
|
203
|
+
var command = argv.shift();
|
|
204
|
+
switch (command) {
|
|
205
|
+
case void 0:
|
|
206
|
+
case "--help":
|
|
207
|
+
case "-h":
|
|
208
|
+
case "help":
|
|
209
|
+
console.log(USAGE);
|
|
210
|
+
process.exit(0);
|
|
211
|
+
break;
|
|
212
|
+
case "notify": {
|
|
213
|
+
const [rawTitle, message, urgency, icon] = argv;
|
|
214
|
+
if (!rawTitle) {
|
|
215
|
+
console.error("lacode notify: TITLE is required");
|
|
216
|
+
console.error(USAGE);
|
|
217
|
+
process.exit(2);
|
|
218
|
+
}
|
|
219
|
+
const result = notify({ title: rawTitle, message, urgency, icon });
|
|
220
|
+
if (!result.ok) console.error(`lacode: notification not delivered via ${result.transport}: ${result.detail ?? "unknown reason"}`);
|
|
221
|
+
process.exit(0);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "forward": {
|
|
225
|
+
const host = argv.shift();
|
|
226
|
+
const separator = argv.indexOf("--");
|
|
227
|
+
if (!host || separator < 0 || separator === argv.length - 1) {
|
|
228
|
+
console.error("lacode forward: usage: lacode forward HOST -- COMMAND [ARG...]");
|
|
229
|
+
process.exit(2);
|
|
230
|
+
}
|
|
231
|
+
const command2 = argv.slice(separator + 1);
|
|
232
|
+
const result = forward(host, command2);
|
|
233
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
234
|
+
if (!result.ok) console.error(`lacode: ${host}: ${result.detail ?? "remote command failed"}`);
|
|
235
|
+
process.exit(result.ok ? 0 : 1);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "doctor": {
|
|
239
|
+
const info = describe();
|
|
240
|
+
for (const [key, value] of Object.entries(info)) console.log(`${key.padEnd(11)} ${String(value)}`);
|
|
241
|
+
if (info.running.startsWith("remote")) {
|
|
242
|
+
if (info.user === "(unset)") console.log("\nOC_LOCAL_USER is unset: the hop cannot name the desktop user, and guessing with `id -un` is wrong on most remote accounts.");
|
|
243
|
+
console.log("Set OC_LOCAL_USER, OC_TUNNEL_PORT and OC_NOTIFY_KEY, or point OC_NOTIFY_HELPER at a helper that knows them.");
|
|
244
|
+
} else if (info.channel === "none") {
|
|
245
|
+
console.log("\nNo local notification channel: `lacode notify` on this machine has nothing to deliver with, and every send will be reported as failed rather than shown.");
|
|
246
|
+
}
|
|
247
|
+
process.exit(0);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
default:
|
|
251
|
+
console.error(`lacode: unknown command ${JSON.stringify(safeTitle(command))}`);
|
|
252
|
+
console.error(USAGE);
|
|
253
|
+
process.exit(2);
|
|
254
|
+
}
|