@pocketenv/cli 0.3.4 → 0.4.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 +1 -0
- package/dist/index.js +182 -6
- package/package.json +1 -1
- package/src/cmd/service.ts +191 -0
- package/src/cmd/start.ts +6 -1
- package/src/index.ts +56 -0
- package/src/theme.ts +5 -3
- package/src/types/service.ts +9 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Pocketenv CLI
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@pocketenv/cli)
|
|
4
|
+

|
|
4
5
|
[](https://discord.gg/9ada4pFUFS)
|
|
5
6
|
[](https://opensource.org/licenses/MPL-2.0)
|
|
6
7
|
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,9 @@ import { execSync } from 'child_process';
|
|
|
23
23
|
import * as fs from 'fs';
|
|
24
24
|
import { password, editor, input } from '@inquirer/prompts';
|
|
25
25
|
import sodium from 'libsodium-wrappers';
|
|
26
|
+
import process$1 from 'node:process';
|
|
26
27
|
|
|
27
|
-
var version = "0.
|
|
28
|
+
var version = "0.4.0";
|
|
28
29
|
|
|
29
30
|
async function getAccessToken() {
|
|
30
31
|
const tokenPath = path.join(os.homedir(), ".pocketenv", "token.json");
|
|
@@ -440,7 +441,11 @@ async function waitUntilRunning(name, authToken, timeoutMs = 6e4, intervalMs = 2
|
|
|
440
441
|
);
|
|
441
442
|
}
|
|
442
443
|
|
|
443
|
-
async function start(name, {
|
|
444
|
+
async function start(name, {
|
|
445
|
+
ssh: ssh$1,
|
|
446
|
+
repo,
|
|
447
|
+
keepAlive
|
|
448
|
+
}) {
|
|
444
449
|
const token = await getAccessToken();
|
|
445
450
|
if (repo) repo = expandRepo(repo);
|
|
446
451
|
try {
|
|
@@ -448,7 +453,8 @@ async function start(name, { ssh: ssh$1, repo }) {
|
|
|
448
453
|
await client.post(
|
|
449
454
|
"/xrpc/io.pocketenv.sandbox.startSandbox",
|
|
450
455
|
{
|
|
451
|
-
repo
|
|
456
|
+
repo,
|
|
457
|
+
keepAlive
|
|
452
458
|
},
|
|
453
459
|
{
|
|
454
460
|
params: {
|
|
@@ -536,7 +542,7 @@ function detectLightTerminal() {
|
|
|
536
542
|
if (!savedState) return false;
|
|
537
543
|
const tty = fs.openSync("/dev/tty", "r+");
|
|
538
544
|
try {
|
|
539
|
-
execSync("stty
|
|
545
|
+
execSync("stty -icanon -echo min 0 time 2 </dev/tty 2>/dev/null");
|
|
540
546
|
fs.writeSync(tty, "\x1B]11;?\x07");
|
|
541
547
|
let resp = "";
|
|
542
548
|
const buf = Buffer.alloc(64);
|
|
@@ -553,8 +559,14 @@ function detectLightTerminal() {
|
|
|
553
559
|
return 0.299 * r + 0.587 * g + 0.114 * b > 127;
|
|
554
560
|
}
|
|
555
561
|
} finally {
|
|
556
|
-
|
|
557
|
-
|
|
562
|
+
try {
|
|
563
|
+
fs.closeSync(tty);
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
execSync(`stty ${savedState} </dev/tty 2>/dev/null`);
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
558
570
|
}
|
|
559
571
|
} catch {
|
|
560
572
|
}
|
|
@@ -1639,6 +1651,160 @@ async function exec(sandbox, command) {
|
|
|
1639
1651
|
}
|
|
1640
1652
|
}
|
|
1641
1653
|
|
|
1654
|
+
dayjs.extend(relativeTime);
|
|
1655
|
+
async function createService(sandboxId, name, command, { ports, description }) {
|
|
1656
|
+
const token = await getAccessToken();
|
|
1657
|
+
try {
|
|
1658
|
+
await client.post(
|
|
1659
|
+
"/xrpc/io.pocketenv.service.addService",
|
|
1660
|
+
{
|
|
1661
|
+
service: {
|
|
1662
|
+
name,
|
|
1663
|
+
command: command.join(" "),
|
|
1664
|
+
description,
|
|
1665
|
+
ports: ports?.map((port) => parseInt(port))
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
params: {
|
|
1670
|
+
sandboxId
|
|
1671
|
+
},
|
|
1672
|
+
headers: {
|
|
1673
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
);
|
|
1677
|
+
consola.success(`Service ${c.highlight(name)} created successfully`);
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
consola.error("Failed to create service", error);
|
|
1680
|
+
process$1.exit(1);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
async function listServices(sandboxId) {
|
|
1684
|
+
const token = await getAccessToken();
|
|
1685
|
+
try {
|
|
1686
|
+
const { data } = await client.get(
|
|
1687
|
+
"/xrpc/io.pocketenv.service.getServices",
|
|
1688
|
+
{
|
|
1689
|
+
params: {
|
|
1690
|
+
sandboxId
|
|
1691
|
+
},
|
|
1692
|
+
headers: {
|
|
1693
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1697
|
+
const table = new Table({
|
|
1698
|
+
head: [
|
|
1699
|
+
c.primary("ID"),
|
|
1700
|
+
c.primary("NAME"),
|
|
1701
|
+
c.primary("COMMAND"),
|
|
1702
|
+
c.primary("STATUS"),
|
|
1703
|
+
c.primary("CREATED AT")
|
|
1704
|
+
],
|
|
1705
|
+
chars: {
|
|
1706
|
+
top: "",
|
|
1707
|
+
"top-mid": "",
|
|
1708
|
+
"top-left": "",
|
|
1709
|
+
"top-right": "",
|
|
1710
|
+
bottom: "",
|
|
1711
|
+
"bottom-mid": "",
|
|
1712
|
+
"bottom-left": "",
|
|
1713
|
+
"bottom-right": "",
|
|
1714
|
+
left: "",
|
|
1715
|
+
"left-mid": "",
|
|
1716
|
+
mid: "",
|
|
1717
|
+
"mid-mid": "",
|
|
1718
|
+
right: "",
|
|
1719
|
+
"right-mid": "",
|
|
1720
|
+
middle: " "
|
|
1721
|
+
},
|
|
1722
|
+
style: {
|
|
1723
|
+
border: [],
|
|
1724
|
+
head: []
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1727
|
+
for (const service of data.services) {
|
|
1728
|
+
table.push([
|
|
1729
|
+
c.secondary(service.id),
|
|
1730
|
+
service.name,
|
|
1731
|
+
service.command,
|
|
1732
|
+
service.status === "RUNNING" ? c.highlight(service.status) : service.status,
|
|
1733
|
+
dayjs(service.createdAt).fromNow()
|
|
1734
|
+
]);
|
|
1735
|
+
}
|
|
1736
|
+
consola.log(table.toString());
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
consola.error("Failed to list services", error);
|
|
1739
|
+
process$1.exit(1);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
async function restartService(serviceId) {
|
|
1743
|
+
const token = await getAccessToken();
|
|
1744
|
+
try {
|
|
1745
|
+
await client.post("/xrpc/io.pocketenv.service.restartService", void 0, {
|
|
1746
|
+
params: {
|
|
1747
|
+
serviceId
|
|
1748
|
+
},
|
|
1749
|
+
headers: {
|
|
1750
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
consola.error(`Failed to restart service ${serviceId}`, error);
|
|
1755
|
+
process$1.exit(1);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
async function startService(serviceId) {
|
|
1759
|
+
const token = await getAccessToken();
|
|
1760
|
+
try {
|
|
1761
|
+
await client.post("/xrpc/io.pocketenv.service.startService", void 0, {
|
|
1762
|
+
params: {
|
|
1763
|
+
serviceId
|
|
1764
|
+
},
|
|
1765
|
+
headers: {
|
|
1766
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
consola.error(`Failed to start service ${serviceId}`, error);
|
|
1771
|
+
process$1.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async function stopService(serviceId) {
|
|
1775
|
+
const token = await getAccessToken();
|
|
1776
|
+
try {
|
|
1777
|
+
await client.post("/xrpc/io.pocketenv.service.stopService", void 0, {
|
|
1778
|
+
params: {
|
|
1779
|
+
serviceId
|
|
1780
|
+
},
|
|
1781
|
+
headers: {
|
|
1782
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
consola.error(`Failed to stop service ${serviceId}`, error);
|
|
1787
|
+
process$1.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
async function deleteService(serviceId) {
|
|
1791
|
+
const token = await getAccessToken();
|
|
1792
|
+
try {
|
|
1793
|
+
await client.post("/xrpc/io.pocketenv.service.deleteService", void 0, {
|
|
1794
|
+
params: {
|
|
1795
|
+
serviceId
|
|
1796
|
+
},
|
|
1797
|
+
headers: {
|
|
1798
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
consola.success(`Service ${c.highlight(serviceId)} deleted successfully`);
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
consola.error(`Failed to delete service ${serviceId}`, error);
|
|
1804
|
+
process$1.exit(1);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1642
1808
|
const program = new Command();
|
|
1643
1809
|
program.name("pocketenv").description(
|
|
1644
1810
|
`${chalk.bold.rgb(0, 232, 198)(`pocketenv v${version}`)} ${c.muted("\u2500")} ${c.muted("Open, interoperable sandbox platform for agents and humans")}`
|
|
@@ -1668,6 +1834,9 @@ program.command("ls").description("list sandboxes").action(listSandboxes);
|
|
|
1668
1834
|
program.command("start").argument("<sandbox>", "the sandbox to start").option("--ssh, -s", "connect to the Sandbox and automatically open a shell").option(
|
|
1669
1835
|
"--repo, -r <repo>",
|
|
1670
1836
|
"the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)"
|
|
1837
|
+
).option(
|
|
1838
|
+
"--keep-alive, -k",
|
|
1839
|
+
"keep the sandbox alive, ignoring inactivity timeout"
|
|
1671
1840
|
).description("start the given sandbox").action(start);
|
|
1672
1841
|
program.command("stop").argument("<sandbox>", "the sandbox to stop").description("stop the given sandbox").action(stop);
|
|
1673
1842
|
program.command("create").aliases(["new"]).option("--provider, -p <provider>", "the provider to use for the sandbox").option(
|
|
@@ -1720,6 +1889,13 @@ sshkeys.command("get").argument("<sandbox>", "the sandbox to get the SSH key fro
|
|
|
1720
1889
|
const tailscale = program.command("tailscale").description("manage Tailscale");
|
|
1721
1890
|
tailscale.command("put").argument("<sandbox>", "the sandbox to put the Tailscale Auth Key in").description("put a Tailscale Auth Key in the given sandbox").action(putAuthKey);
|
|
1722
1891
|
tailscale.command("get").argument("<sandbox>", "the sandbox to get the Tailscale Auth Key from").description("get a Tailscale Auth Key (redacted) from the given sandbox").action(getTailscaleAuthKey);
|
|
1892
|
+
const service = program.command("service").description("manage services");
|
|
1893
|
+
service.command("create").argument("<sandbox>", "the sandbox to create the service in").argument("<name>", "the name of the service").argument("<command...>", "the command to run for the service").option("--description, -d <description>", "a description for the service").option("--ports, -p <ports...>", "a list of ports to expose for the service").description("create a new service in the given sandbox").action(createService);
|
|
1894
|
+
service.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list services for").description("list services in the given sandbox").action(listServices);
|
|
1895
|
+
service.command("delete").aliases(["rm", "remove"]).argument("<service_id>", "the ID of the service to delete").description("delete a service").action(deleteService);
|
|
1896
|
+
service.command("start").argument("<service_id>", "the ID of the service to start").description("start a service").action(startService);
|
|
1897
|
+
service.command("stop").argument("<service_id>", "the ID of the service to stop").description("stop a service").action(stopService);
|
|
1898
|
+
service.command("restart").argument("<service_id>", "the ID of the service to restart").description("restart a service").action(restartService);
|
|
1723
1899
|
if (process.argv.length <= 2) {
|
|
1724
1900
|
program.help();
|
|
1725
1901
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { client } from "../client";
|
|
3
|
+
import { env } from "../lib/env";
|
|
4
|
+
import getAccessToken from "../lib/getAccessToken";
|
|
5
|
+
import type { Service } from "../types/service";
|
|
6
|
+
import Table from "cli-table3";
|
|
7
|
+
import dayjs from "dayjs";
|
|
8
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
9
|
+
import { c } from "../theme";
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
|
|
12
|
+
dayjs.extend(relativeTime);
|
|
13
|
+
|
|
14
|
+
type CreateServiceOptions = {
|
|
15
|
+
ports?: string[];
|
|
16
|
+
description?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function createService(
|
|
20
|
+
sandboxId: string,
|
|
21
|
+
name: string,
|
|
22
|
+
command: string[],
|
|
23
|
+
{ ports, description }: CreateServiceOptions,
|
|
24
|
+
) {
|
|
25
|
+
const token = await getAccessToken();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await client.post(
|
|
29
|
+
"/xrpc/io.pocketenv.service.addService",
|
|
30
|
+
{
|
|
31
|
+
service: {
|
|
32
|
+
name,
|
|
33
|
+
command: command.join(" "),
|
|
34
|
+
description,
|
|
35
|
+
ports: ports?.map((port) => parseInt(port)),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
params: {
|
|
40
|
+
sandboxId,
|
|
41
|
+
},
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
consola.success(`Service ${c.highlight(name)} created successfully`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
consola.error("Failed to create service", error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function listServices(sandboxId: string) {
|
|
56
|
+
const token = await getAccessToken();
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const { data } = await client.get<{ services: Service[] }>(
|
|
60
|
+
"/xrpc/io.pocketenv.service.getServices",
|
|
61
|
+
{
|
|
62
|
+
params: {
|
|
63
|
+
sandboxId,
|
|
64
|
+
},
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const table = new Table({
|
|
72
|
+
head: [
|
|
73
|
+
c.primary("ID"),
|
|
74
|
+
c.primary("NAME"),
|
|
75
|
+
c.primary("COMMAND"),
|
|
76
|
+
c.primary("STATUS"),
|
|
77
|
+
c.primary("CREATED AT"),
|
|
78
|
+
],
|
|
79
|
+
chars: {
|
|
80
|
+
top: "",
|
|
81
|
+
"top-mid": "",
|
|
82
|
+
"top-left": "",
|
|
83
|
+
"top-right": "",
|
|
84
|
+
bottom: "",
|
|
85
|
+
"bottom-mid": "",
|
|
86
|
+
"bottom-left": "",
|
|
87
|
+
"bottom-right": "",
|
|
88
|
+
left: "",
|
|
89
|
+
"left-mid": "",
|
|
90
|
+
mid: "",
|
|
91
|
+
"mid-mid": "",
|
|
92
|
+
right: "",
|
|
93
|
+
"right-mid": "",
|
|
94
|
+
middle: " ",
|
|
95
|
+
},
|
|
96
|
+
style: {
|
|
97
|
+
border: [],
|
|
98
|
+
head: [],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
for (const service of data.services) {
|
|
103
|
+
table.push([
|
|
104
|
+
c.secondary(service.id),
|
|
105
|
+
service.name,
|
|
106
|
+
service.command,
|
|
107
|
+
service.status === "RUNNING"
|
|
108
|
+
? c.highlight(service.status)
|
|
109
|
+
: service.status,
|
|
110
|
+
dayjs(service.createdAt).fromNow(),
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
consola.log(table.toString());
|
|
115
|
+
} catch (error) {
|
|
116
|
+
consola.error("Failed to list services", error);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function restartService(serviceId: string) {
|
|
122
|
+
const token = await getAccessToken();
|
|
123
|
+
try {
|
|
124
|
+
await client.post("/xrpc/io.pocketenv.service.restartService", undefined, {
|
|
125
|
+
params: {
|
|
126
|
+
serviceId,
|
|
127
|
+
},
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
consola.error(`Failed to restart service ${serviceId}`, error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function startService(serviceId: string) {
|
|
139
|
+
const token = await getAccessToken();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await client.post("/xrpc/io.pocketenv.service.startService", undefined, {
|
|
143
|
+
params: {
|
|
144
|
+
serviceId,
|
|
145
|
+
},
|
|
146
|
+
headers: {
|
|
147
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
consola.error(`Failed to start service ${serviceId}`, error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function stopService(serviceId: string) {
|
|
157
|
+
const token = await getAccessToken();
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
await client.post("/xrpc/io.pocketenv.service.stopService", undefined, {
|
|
161
|
+
params: {
|
|
162
|
+
serviceId,
|
|
163
|
+
},
|
|
164
|
+
headers: {
|
|
165
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
consola.error(`Failed to stop service ${serviceId}`, error);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function deleteService(serviceId: string) {
|
|
175
|
+
const token = await getAccessToken();
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await client.post("/xrpc/io.pocketenv.service.deleteService", undefined, {
|
|
179
|
+
params: {
|
|
180
|
+
serviceId,
|
|
181
|
+
},
|
|
182
|
+
headers: {
|
|
183
|
+
Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
consola.success(`Service ${c.highlight(serviceId)} deleted successfully`);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
consola.error(`Failed to delete service ${serviceId}`, error);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
package/src/cmd/start.ts
CHANGED
|
@@ -9,7 +9,11 @@ import waitUntilRunning from "../lib/waitUntilRunning";
|
|
|
9
9
|
|
|
10
10
|
async function start(
|
|
11
11
|
name: string,
|
|
12
|
-
{
|
|
12
|
+
{
|
|
13
|
+
ssh,
|
|
14
|
+
repo,
|
|
15
|
+
keepAlive,
|
|
16
|
+
}: { ssh?: boolean; repo?: string; keepAlive?: boolean },
|
|
13
17
|
) {
|
|
14
18
|
const token = await getAccessToken();
|
|
15
19
|
if (repo) repo = expandRepo(repo);
|
|
@@ -20,6 +24,7 @@ async function start(
|
|
|
20
24
|
"/xrpc/io.pocketenv.sandbox.startSandbox",
|
|
21
25
|
{
|
|
22
26
|
repo,
|
|
27
|
+
keepAlive,
|
|
23
28
|
},
|
|
24
29
|
{
|
|
25
30
|
params: {
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,14 @@ import { listPorts } from "./cmd/ports";
|
|
|
23
23
|
import { c } from "./theme";
|
|
24
24
|
import { exposeVscode } from "./cmd/vscode";
|
|
25
25
|
import { exec } from "./cmd/exec";
|
|
26
|
+
import {
|
|
27
|
+
createService,
|
|
28
|
+
deleteService,
|
|
29
|
+
listServices,
|
|
30
|
+
restartService,
|
|
31
|
+
startService,
|
|
32
|
+
stopService,
|
|
33
|
+
} from "./cmd/service";
|
|
26
34
|
|
|
27
35
|
const program = new Command();
|
|
28
36
|
|
|
@@ -81,6 +89,10 @@ program
|
|
|
81
89
|
"--repo, -r <repo>",
|
|
82
90
|
"the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)",
|
|
83
91
|
)
|
|
92
|
+
.option(
|
|
93
|
+
"--keep-alive, -k",
|
|
94
|
+
"keep the sandbox alive, ignoring inactivity timeout",
|
|
95
|
+
)
|
|
84
96
|
.description("start the given sandbox")
|
|
85
97
|
.action(start);
|
|
86
98
|
|
|
@@ -296,6 +308,50 @@ tailscale
|
|
|
296
308
|
.description("get a Tailscale Auth Key (redacted) from the given sandbox")
|
|
297
309
|
.action(getTailscaleAuthKey);
|
|
298
310
|
|
|
311
|
+
const service = program.command("service").description("manage services");
|
|
312
|
+
|
|
313
|
+
service
|
|
314
|
+
.command("create")
|
|
315
|
+
.argument("<sandbox>", "the sandbox to create the service in")
|
|
316
|
+
.argument("<name>", "the name of the service")
|
|
317
|
+
.argument("<command...>", "the command to run for the service")
|
|
318
|
+
.option("--description, -d <description>", "a description for the service")
|
|
319
|
+
.option("--ports, -p <ports...>", "a list of ports to expose for the service")
|
|
320
|
+
.description("create a new service in the given sandbox")
|
|
321
|
+
.action(createService);
|
|
322
|
+
|
|
323
|
+
service
|
|
324
|
+
.command("list")
|
|
325
|
+
.aliases(["ls"])
|
|
326
|
+
.argument("<sandbox>", "the sandbox to list services for")
|
|
327
|
+
.description("list services in the given sandbox")
|
|
328
|
+
.action(listServices);
|
|
329
|
+
|
|
330
|
+
service
|
|
331
|
+
.command("delete")
|
|
332
|
+
.aliases(["rm", "remove"])
|
|
333
|
+
.argument("<service_id>", "the ID of the service to delete")
|
|
334
|
+
.description("delete a service")
|
|
335
|
+
.action(deleteService);
|
|
336
|
+
|
|
337
|
+
service
|
|
338
|
+
.command("start")
|
|
339
|
+
.argument("<service_id>", "the ID of the service to start")
|
|
340
|
+
.description("start a service")
|
|
341
|
+
.action(startService);
|
|
342
|
+
|
|
343
|
+
service
|
|
344
|
+
.command("stop")
|
|
345
|
+
.argument("<service_id>", "the ID of the service to stop")
|
|
346
|
+
.description("stop a service")
|
|
347
|
+
.action(stopService);
|
|
348
|
+
|
|
349
|
+
service
|
|
350
|
+
.command("restart")
|
|
351
|
+
.argument("<service_id>", "the ID of the service to restart")
|
|
352
|
+
.description("restart a service")
|
|
353
|
+
.action(restartService);
|
|
354
|
+
|
|
299
355
|
if (process.argv.length <= 2) {
|
|
300
356
|
program.help();
|
|
301
357
|
}
|
package/src/theme.ts
CHANGED
|
@@ -27,7 +27,9 @@ function detectLightTerminal(): boolean {
|
|
|
27
27
|
if (!savedState) return false;
|
|
28
28
|
const tty = fs.openSync("/dev/tty", "r+");
|
|
29
29
|
try {
|
|
30
|
-
|
|
30
|
+
// Use -icanon -echo instead of raw: avoids disabling ISIG (Ctrl+C) so
|
|
31
|
+
// signal handling stays intact even if the restore below fails.
|
|
32
|
+
execSync("stty -icanon -echo min 0 time 2 </dev/tty 2>/dev/null");
|
|
31
33
|
fs.writeSync(tty, "\x1b]11;?\x07");
|
|
32
34
|
// Read in a loop until we see the response terminator (BEL or ST),
|
|
33
35
|
// so leftover bytes don't leak into the terminal input buffer.
|
|
@@ -47,8 +49,8 @@ function detectLightTerminal(): boolean {
|
|
|
47
49
|
return 0.299 * r + 0.587 * g + 0.114 * b > 127;
|
|
48
50
|
}
|
|
49
51
|
} finally {
|
|
50
|
-
fs.closeSync(tty);
|
|
51
|
-
execSync(`stty ${savedState} </dev/tty 2>/dev/null`);
|
|
52
|
+
try { fs.closeSync(tty); } catch {}
|
|
53
|
+
try { execSync(`stty ${savedState} </dev/tty 2>/dev/null`); } catch {}
|
|
52
54
|
}
|
|
53
55
|
} catch {}
|
|
54
56
|
}
|