@babylen/legion 0.1.0
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/README.md +86 -0
- package/dist/index.js +361 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Tanuki Legion
|
|
2
|
+
|
|
3
|
+
Legion is a lightweight agent that connects your devices to Tanuki Cloud, enabling remote development and automation capabilities.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Quick Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -sL https://tanuki.sabw.ru/install-legion | LEGION_TOKEN=your_token_here bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Manual Installation
|
|
14
|
+
|
|
15
|
+
1. **Install dependencies:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. **Configure:**
|
|
22
|
+
|
|
23
|
+
Create `~/.tanuki/config.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"token": "your_legion_token",
|
|
28
|
+
"serverUrl": "wss://tanuki.sabw.ru"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or set environment variables:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export LEGION_TOKEN=your_token_here
|
|
36
|
+
export TANUKI_SERVER_URL=wss://tanuki.sabw.ru
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. **Run:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bun run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- 🔗 **Cloud Connection**: Seamlessly connect to Tanuki Cloud via WebSocket
|
|
48
|
+
- 🛡️ **Secure Authentication**: Token-based authentication for device access
|
|
49
|
+
- 🔄 **Auto-Reconnection**: Automatic reconnection on network interruptions
|
|
50
|
+
- 📊 **Device Management**: Manage multiple devices from Tanuki Cloud dashboard
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
Configuration is stored in `~/.tanuki/config.json`:
|
|
55
|
+
|
|
56
|
+
- `token`: Your Legion device token (required)
|
|
57
|
+
- `serverUrl`: Tanuki Cloud server URL (default: `wss://tanuki.sabw.ru`)
|
|
58
|
+
|
|
59
|
+
## Getting a Token
|
|
60
|
+
|
|
61
|
+
1. Log in to [Tanuki Cloud](https://tanuki.sabw.ru)
|
|
62
|
+
2. Go to Settings → Devices
|
|
63
|
+
3. Click "Connect New Device"
|
|
64
|
+
4. Copy the installation command or token
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
This project uses [Bun](https://bun.sh) as the JavaScript runtime.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Install dependencies
|
|
72
|
+
bun install
|
|
73
|
+
|
|
74
|
+
# Run in development mode
|
|
75
|
+
bun run dev
|
|
76
|
+
|
|
77
|
+
# Type check
|
|
78
|
+
bun run typecheck
|
|
79
|
+
|
|
80
|
+
# Build
|
|
81
|
+
bun run build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Proprietary - Tanuki Cloud
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_socket = require("socket.io-client");
|
|
28
|
+
var os3 = __toESM(require("os"));
|
|
29
|
+
|
|
30
|
+
// src/core/config.ts
|
|
31
|
+
var import_os = __toESM(require("os"));
|
|
32
|
+
var import_path = __toESM(require("path"));
|
|
33
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
34
|
+
var import_zod = require("zod");
|
|
35
|
+
var ConfigSchema = import_zod.z.object({
|
|
36
|
+
id: import_zod.z.string().optional(),
|
|
37
|
+
// Token ID from database (for faster lookup)
|
|
38
|
+
token: import_zod.z.string(),
|
|
39
|
+
// Secret token (lg_xxx format)
|
|
40
|
+
serverUrl: import_zod.z.string().url()
|
|
41
|
+
});
|
|
42
|
+
var HOME_DIR = import_os.default.homedir();
|
|
43
|
+
var CONFIG_DIR = import_path.default.join(HOME_DIR, ".tanuki");
|
|
44
|
+
var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
|
|
45
|
+
async function loadConfig() {
|
|
46
|
+
try {
|
|
47
|
+
const content = await import_promises.default.readFile(CONFIG_FILE, "utf-8");
|
|
48
|
+
const data2 = JSON.parse(content);
|
|
49
|
+
return ConfigSchema.parse(data2);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function getConfig() {
|
|
55
|
+
const fileConfig = await loadConfig();
|
|
56
|
+
const id = fileConfig?.id || process.env.LEGION_TOKEN_ID || void 0;
|
|
57
|
+
const token = fileConfig?.token || process.env.LEGION_TOKEN_SECRET || process.env.LEGION_TOKEN;
|
|
58
|
+
const serverUrl = fileConfig?.serverUrl || process.env.TANUKI_SERVER_URL || "wss://tanuki.sabw.ru";
|
|
59
|
+
if (!token) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Legion token not found. Please set LEGION_TOKEN_SECRET (or LEGION_TOKEN) environment variable or configure ~/.tanuki/config.json"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const config2 = {
|
|
65
|
+
token,
|
|
66
|
+
serverUrl
|
|
67
|
+
};
|
|
68
|
+
if (id) {
|
|
69
|
+
config2.id = id;
|
|
70
|
+
}
|
|
71
|
+
return config2;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/util/log.ts
|
|
75
|
+
var import_path3 = __toESM(require("path"));
|
|
76
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
77
|
+
var import_fs = require("fs");
|
|
78
|
+
|
|
79
|
+
// src/core/global.ts
|
|
80
|
+
var import_os2 = __toESM(require("os"));
|
|
81
|
+
var import_path2 = __toESM(require("path"));
|
|
82
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
83
|
+
var HOME_DIR2 = import_os2.default.homedir();
|
|
84
|
+
var data = import_path2.default.join(HOME_DIR2, ".tanuki", "legion");
|
|
85
|
+
var cache = import_path2.default.join(HOME_DIR2, ".tanuki", "legion", "cache");
|
|
86
|
+
var config = import_path2.default.join(HOME_DIR2, ".tanuki");
|
|
87
|
+
var state = import_path2.default.join(HOME_DIR2, ".tanuki", "legion", "state");
|
|
88
|
+
var Global;
|
|
89
|
+
((Global2) => {
|
|
90
|
+
Global2.Path = {
|
|
91
|
+
// Allow override via LEGION_TEST_HOME for test isolation
|
|
92
|
+
get home() {
|
|
93
|
+
return process.env.LEGION_TEST_HOME || import_os2.default.homedir();
|
|
94
|
+
},
|
|
95
|
+
data,
|
|
96
|
+
bin: import_path2.default.join(data, "bin"),
|
|
97
|
+
log: import_path2.default.join(data, "log"),
|
|
98
|
+
cache,
|
|
99
|
+
config,
|
|
100
|
+
state
|
|
101
|
+
};
|
|
102
|
+
async function init() {
|
|
103
|
+
await Promise.all([
|
|
104
|
+
import_promises2.default.mkdir(Global2.Path.data, { recursive: true }),
|
|
105
|
+
import_promises2.default.mkdir(Global2.Path.config, { recursive: true }),
|
|
106
|
+
import_promises2.default.mkdir(Global2.Path.state, { recursive: true }),
|
|
107
|
+
import_promises2.default.mkdir(Global2.Path.log, { recursive: true }),
|
|
108
|
+
import_promises2.default.mkdir(Global2.Path.bin, { recursive: true })
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
Global2.init = init;
|
|
112
|
+
})(Global || (Global = {}));
|
|
113
|
+
|
|
114
|
+
// src/util/log.ts
|
|
115
|
+
var import_zod2 = __toESM(require("zod"));
|
|
116
|
+
var Log;
|
|
117
|
+
((Log2) => {
|
|
118
|
+
Log2.Level = import_zod2.default.enum(["DEBUG", "INFO", "WARN", "ERROR"]);
|
|
119
|
+
const levelPriority = {
|
|
120
|
+
DEBUG: 0,
|
|
121
|
+
INFO: 1,
|
|
122
|
+
WARN: 2,
|
|
123
|
+
ERROR: 3
|
|
124
|
+
};
|
|
125
|
+
let level = "INFO";
|
|
126
|
+
function shouldLog(input) {
|
|
127
|
+
return levelPriority[input] >= levelPriority[level];
|
|
128
|
+
}
|
|
129
|
+
const loggers = /* @__PURE__ */ new Map();
|
|
130
|
+
Log2.Default = create({ service: "default" });
|
|
131
|
+
let logpath = "";
|
|
132
|
+
function file() {
|
|
133
|
+
return logpath;
|
|
134
|
+
}
|
|
135
|
+
Log2.file = file;
|
|
136
|
+
let write = (msg) => {
|
|
137
|
+
process.stderr.write(msg);
|
|
138
|
+
return msg.length;
|
|
139
|
+
};
|
|
140
|
+
let logStream = null;
|
|
141
|
+
async function init(options) {
|
|
142
|
+
if (options.level) level = options.level;
|
|
143
|
+
await Global.init().catch(() => {
|
|
144
|
+
});
|
|
145
|
+
cleanup(Global.Path.log).catch(() => {
|
|
146
|
+
});
|
|
147
|
+
if (options.print) return;
|
|
148
|
+
logpath = import_path3.default.join(
|
|
149
|
+
Global.Path.log,
|
|
150
|
+
options.dev ? "dev.log" : (/* @__PURE__ */ new Date()).toISOString().split(".")[0].replace(/:/g, "") + ".log"
|
|
151
|
+
);
|
|
152
|
+
try {
|
|
153
|
+
await import_promises3.default.writeFile(logpath, "", { flag: "w" });
|
|
154
|
+
logStream = (0, import_fs.createWriteStream)(logpath, { flags: "a" });
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
write = (msg) => {
|
|
158
|
+
if (logStream) {
|
|
159
|
+
const written = logStream.write(msg);
|
|
160
|
+
return written ? msg.length : 0;
|
|
161
|
+
}
|
|
162
|
+
process.stderr.write(msg);
|
|
163
|
+
return msg.length;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
Log2.init = init;
|
|
167
|
+
async function cleanup(dir) {
|
|
168
|
+
try {
|
|
169
|
+
const files = await import_promises3.default.readdir(dir);
|
|
170
|
+
const logFiles = files.filter((f) => /^\d{4}-\d{2}-\d{2}T\d{6}\.log$/.test(f));
|
|
171
|
+
if (logFiles.length <= 5) return;
|
|
172
|
+
const filesToDelete = logFiles.slice(0, -10);
|
|
173
|
+
await Promise.all(filesToDelete.map((file2) => import_promises3.default.unlink(import_path3.default.join(dir, file2)).catch(() => {
|
|
174
|
+
})));
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function formatError(error, depth = 0) {
|
|
179
|
+
const result = error.message;
|
|
180
|
+
return error.cause instanceof Error && depth < 10 ? result + " Caused by: " + formatError(error.cause, depth + 1) : result;
|
|
181
|
+
}
|
|
182
|
+
let last = Date.now();
|
|
183
|
+
function create(tags) {
|
|
184
|
+
tags = tags || {};
|
|
185
|
+
const service = tags["service"];
|
|
186
|
+
if (service && typeof service === "string") {
|
|
187
|
+
const cached = loggers.get(service);
|
|
188
|
+
if (cached) {
|
|
189
|
+
return cached;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function build(message, extra) {
|
|
193
|
+
const prefix = Object.entries({
|
|
194
|
+
...tags,
|
|
195
|
+
...extra
|
|
196
|
+
}).filter(([_, value]) => value !== void 0 && value !== null).map(([key, value]) => {
|
|
197
|
+
const prefix2 = `${key}=`;
|
|
198
|
+
if (value instanceof Error) return prefix2 + formatError(value);
|
|
199
|
+
if (typeof value === "object") return prefix2 + JSON.stringify(value);
|
|
200
|
+
return prefix2 + value;
|
|
201
|
+
}).join(" ");
|
|
202
|
+
const next = /* @__PURE__ */ new Date();
|
|
203
|
+
const diff = next.getTime() - last;
|
|
204
|
+
last = next.getTime();
|
|
205
|
+
return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n";
|
|
206
|
+
}
|
|
207
|
+
const result = {
|
|
208
|
+
debug(message, extra) {
|
|
209
|
+
if (shouldLog("DEBUG")) {
|
|
210
|
+
write("DEBUG " + build(message, extra));
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
info(message, extra) {
|
|
214
|
+
if (shouldLog("INFO")) {
|
|
215
|
+
write("INFO " + build(message, extra));
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
error(message, extra) {
|
|
219
|
+
if (shouldLog("ERROR")) {
|
|
220
|
+
write("ERROR " + build(message, extra));
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
warn(message, extra) {
|
|
224
|
+
if (shouldLog("WARN")) {
|
|
225
|
+
write("WARN " + build(message, extra));
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
tag(key, value) {
|
|
229
|
+
if (tags) tags[key] = value;
|
|
230
|
+
return result;
|
|
231
|
+
},
|
|
232
|
+
clone() {
|
|
233
|
+
return Log2.create({ ...tags });
|
|
234
|
+
},
|
|
235
|
+
time(message, extra) {
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
result.info(message, { status: "started", ...extra });
|
|
238
|
+
function stop() {
|
|
239
|
+
result.info(message, {
|
|
240
|
+
status: "completed",
|
|
241
|
+
duration: Date.now() - now,
|
|
242
|
+
...extra
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
stop,
|
|
247
|
+
[Symbol.dispose]() {
|
|
248
|
+
stop();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
if (service && typeof service === "string") {
|
|
254
|
+
loggers.set(service, result);
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
Log2.create = create;
|
|
259
|
+
})(Log || (Log = {}));
|
|
260
|
+
|
|
261
|
+
// src/index.ts
|
|
262
|
+
var log = Log.create({ service: "legion" });
|
|
263
|
+
async function main() {
|
|
264
|
+
try {
|
|
265
|
+
await Global.init();
|
|
266
|
+
log.info("\u{1F6E1}\uFE0F Legion v0.1 starting...");
|
|
267
|
+
const config2 = await getConfig();
|
|
268
|
+
log.info("\u{1F517} Connecting to server", { serverUrl: config2.serverUrl });
|
|
269
|
+
const socket = (0, import_socket.io)(config2.serverUrl, {
|
|
270
|
+
auth: {
|
|
271
|
+
id: config2.id,
|
|
272
|
+
// Token ID for faster lookup (optional)
|
|
273
|
+
token: config2.token,
|
|
274
|
+
// Secret token for authentication
|
|
275
|
+
type: "legion"
|
|
276
|
+
},
|
|
277
|
+
transports: ["websocket"],
|
|
278
|
+
reconnection: true,
|
|
279
|
+
reconnectionDelay: 1e3,
|
|
280
|
+
reconnectionDelayMax: 5e3,
|
|
281
|
+
reconnectionAttempts: Infinity
|
|
282
|
+
});
|
|
283
|
+
socket.on("connect", () => {
|
|
284
|
+
log.info("\u2705 Connected to server", { socketId: socket.id });
|
|
285
|
+
const handshakeData = {
|
|
286
|
+
hostname: os3.hostname(),
|
|
287
|
+
platform: os3.platform(),
|
|
288
|
+
release: os3.release(),
|
|
289
|
+
cwd: process.cwd()
|
|
290
|
+
};
|
|
291
|
+
socket.emit("legion:handshake", handshakeData);
|
|
292
|
+
log.debug("\u{1F4E4} Sent handshake", handshakeData);
|
|
293
|
+
});
|
|
294
|
+
socket.on("connect_error", (err) => {
|
|
295
|
+
log.error("\u274C Connection error", {
|
|
296
|
+
message: err.message,
|
|
297
|
+
type: err.type
|
|
298
|
+
});
|
|
299
|
+
if (err.message.includes("auth") || err.message.includes("token")) {
|
|
300
|
+
log.error("\u{1F510} Authentication failed. Please check your token.");
|
|
301
|
+
log.error("\u{1F4A1} Re-authenticate by updating ~/.tanuki/config.json or LEGION_TOKEN env var");
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
socket.on("disconnect", (reason) => {
|
|
306
|
+
log.warn("\u26A0\uFE0F Disconnected from server", { reason });
|
|
307
|
+
if (reason === "io server disconnect") {
|
|
308
|
+
log.error("\u{1F6AB} Server disconnected this client. Please check your token.");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
socket.on("server:ping", (data2) => {
|
|
313
|
+
log.debug("\u{1F4E9} Ping from server", data2);
|
|
314
|
+
socket.emit("legion:pong", { ts: Date.now() });
|
|
315
|
+
});
|
|
316
|
+
socket.on("reconnect", (attemptNumber) => {
|
|
317
|
+
log.info("\u{1F504} Reconnected to server", { attempt: attemptNumber });
|
|
318
|
+
});
|
|
319
|
+
socket.on("reconnect_attempt", (attemptNumber) => {
|
|
320
|
+
log.debug("\u{1F504} Reconnection attempt", { attempt: attemptNumber });
|
|
321
|
+
});
|
|
322
|
+
socket.on("reconnect_error", (error) => {
|
|
323
|
+
log.error("\u{1F504} Reconnection error", { message: error.message });
|
|
324
|
+
});
|
|
325
|
+
socket.on("reconnect_failed", () => {
|
|
326
|
+
log.error("\u{1F504} Reconnection failed after all attempts");
|
|
327
|
+
process.exit(1);
|
|
328
|
+
});
|
|
329
|
+
process.stdin.resume();
|
|
330
|
+
const shutdown = () => {
|
|
331
|
+
log.info("\u{1F6D1} Shutting down...");
|
|
332
|
+
socket.disconnect();
|
|
333
|
+
process.exit(0);
|
|
334
|
+
};
|
|
335
|
+
process.on("SIGINT", shutdown);
|
|
336
|
+
process.on("SIGTERM", shutdown);
|
|
337
|
+
process.on("unhandledRejection", (reason) => {
|
|
338
|
+
log.error("Unhandled rejection", { reason });
|
|
339
|
+
});
|
|
340
|
+
process.on("uncaughtException", (error) => {
|
|
341
|
+
log.error("Uncaught exception", { error: error.message, stack: error.stack });
|
|
342
|
+
shutdown();
|
|
343
|
+
});
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (error instanceof Error) {
|
|
346
|
+
log.error("Failed to start Legion", {
|
|
347
|
+
message: error.message,
|
|
348
|
+
stack: error.stack
|
|
349
|
+
});
|
|
350
|
+
console.error("\n\u274C", error.message);
|
|
351
|
+
console.error("\n\u{1F4A1} Make sure you have configured your token:");
|
|
352
|
+
console.error(" - Set LEGION_TOKEN environment variable, or");
|
|
353
|
+
console.error(" - Create ~/.tanuki/config.json with your token");
|
|
354
|
+
} else {
|
|
355
|
+
log.error("Failed to start Legion", { error });
|
|
356
|
+
console.error("\n\u274C Unknown error occurred");
|
|
357
|
+
}
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@babylen/legion",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Legion agent for connecting devices to Tanuki Cloud",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"legion"
|
|
8
|
+
],
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "PROPRIETARY",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"legion": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"package.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"dev": "bun run --conditions=browser ./src/index.ts",
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "bun test"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"socket.io-client": "^4.7.4",
|
|
31
|
+
"better-sqlite3": "^9.4.0",
|
|
32
|
+
"@opentui/core": "0.1.73",
|
|
33
|
+
"@opentui/solid": "0.1.73",
|
|
34
|
+
"solid-js": "^1.8.15",
|
|
35
|
+
"chokidar": "4.0.3",
|
|
36
|
+
"zod": "^3.22.4",
|
|
37
|
+
"bun-pty": "0.4.4",
|
|
38
|
+
"clipboardy": "4.0.0",
|
|
39
|
+
"diff": "^5.2.0",
|
|
40
|
+
"fuzzysort": "3.1.0",
|
|
41
|
+
"ignore": "7.0.5",
|
|
42
|
+
"jsonc-parser": "3.3.1",
|
|
43
|
+
"minimatch": "10.0.3",
|
|
44
|
+
"open": "10.1.2",
|
|
45
|
+
"opentui-spinner": "0.0.6",
|
|
46
|
+
"strip-ansi": "7.1.2",
|
|
47
|
+
"tree-sitter-bash": "0.25.0",
|
|
48
|
+
"turndown": "7.2.0",
|
|
49
|
+
"ulid": "^2.3.0",
|
|
50
|
+
"web-tree-sitter": "0.25.10",
|
|
51
|
+
"xdg-basedir": "5.1.0",
|
|
52
|
+
"yargs": "18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@tsconfig/bun": "^1.0.4",
|
|
56
|
+
"@types/better-sqlite3": "^7.6.9",
|
|
57
|
+
"@types/bun": "latest",
|
|
58
|
+
"@types/node": "^25.0.9",
|
|
59
|
+
"@types/yargs": "17.0.33",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
},
|
|
63
|
+
"trustedDependencies": [
|
|
64
|
+
"tree-sitter-bash"
|
|
65
|
+
]
|
|
66
|
+
}
|