coderemote 0.1.0 → 0.1.2
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 +22 -23
- package/dist/coderemote.js +181 -39
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/Tranthanh98/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Tranthanh98/coderemote/main/apps/ios/Design/AppIcon/Icon-Default-1024x1024@1x.png" width="96" height="96" alt="Code Remote icon">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">coderemote</h1>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://www.npmjs.com/package/coderemote"><img src="https://img.shields.io/npm/v/coderemote" alt="npm version"></a>
|
|
15
|
-
<a href="https://github.com/Tranthanh98/
|
|
15
|
+
<a href="https://github.com/Tranthanh98/coderemote/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/coderemote" alt="license: AGPL-3.0-or-later"></a>
|
|
16
16
|
<img src="https://img.shields.io/node/v/coderemote" alt="node version">
|
|
17
17
|
</p>
|
|
18
18
|
|
|
@@ -33,21 +33,20 @@ in your pocket:
|
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
35
|
npm install -g coderemote
|
|
36
|
-
|
|
37
|
-
devtunnel user login -g # once, sign in with GitHub (-d for a Microsoft account)
|
|
38
|
-
|
|
39
|
-
cd ~/projects/my-app
|
|
40
|
-
coderemote up
|
|
36
|
+
cd ~/projects/my-app && coderemote up
|
|
41
37
|
```
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
Scan the QR code it prints with your iPhone Camera (or the **+** button in the app). Done.
|
|
40
|
+
|
|
41
|
+
The first run sets itself up: it downloads Microsoft's Dev Tunnels CLI (once, verified) and
|
|
42
|
+
opens your browser to sign in with GitHub. Prefer a Microsoft account? Run
|
|
43
|
+
`coderemote login --microsoft` first.
|
|
45
44
|
|
|
46
45
|
## How it works
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
<p align="center">
|
|
48
|
+
<img src="https://raw.githubusercontent.com/Tranthanh98/coderemote/main/docs/images/how-it-works.png" width="760" alt="How it works: the Code Remote app on the iPhone connects over HTTPS/WSS to the Microsoft Dev Tunnels relay; coderemote on your computer (127.0.0.1:31580) connects outbound to the same relay and serves files, git and terminals from the shared folder. Security at each hop: token in the iPhone Keychain, only your account through the relay, token check and one folder on the computer.">
|
|
49
|
+
</p>
|
|
51
50
|
|
|
52
51
|
`coderemote` starts a small agent that listens on `127.0.0.1` only, and publishes it through
|
|
53
52
|
a [Microsoft Dev Tunnel](https://learn.microsoft.com/azure/developer/dev-tunnels/overview).
|
|
@@ -70,7 +69,7 @@ Remote shell access is serious, so access is locked down by default:
|
|
|
70
69
|
|
|
71
70
|
`coderemote up --allow-anonymous` skips the account sign-in when you want the pairing token
|
|
72
71
|
alone. Found a vulnerability? See the
|
|
73
|
-
[security policy](https://github.com/Tranthanh98/
|
|
72
|
+
[security policy](https://github.com/Tranthanh98/coderemote/blob/main/SECURITY.md).
|
|
74
73
|
|
|
75
74
|
## Commands
|
|
76
75
|
|
|
@@ -83,6 +82,7 @@ coderemote kill --all # end every session
|
|
|
83
82
|
coderemote stop # stop the agent for this folder (--all: every agent)
|
|
84
83
|
coderemote serve # share on 127.0.0.1 only, no tunnel (local testing)
|
|
85
84
|
coderemote rotate-token # revoke every paired device
|
|
85
|
+
coderemote login # sign in again (--microsoft, or --device-code without a browser)
|
|
86
86
|
coderemote help # all options
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -92,15 +92,14 @@ Options: `--root DIR` shares another folder; `--port N` picks the local port (de
|
|
|
92
92
|
## Requirements
|
|
93
93
|
|
|
94
94
|
- **Node.js 20** or newer on **macOS** (Linux is expected to work but not yet tested)
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
(`build-essential` on Debian/Ubuntu)
|
|
95
|
+
- A **GitHub or Microsoft account** to sign in to Dev Tunnels
|
|
96
|
+
- The **Dev Tunnels CLI** is downloaded on first run into `~/.coderemote/bin` (the macOS build
|
|
97
|
+
must carry Microsoft's code signature). A `devtunnel` already on your `PATH` is used instead,
|
|
98
|
+
or point `CODEREMOTE_DEVTUNNEL` at one.
|
|
99
|
+
- On Linux, `node-pty` compiles on install (`python3`, `make`, a C++ compiler:
|
|
100
|
+
`build-essential` on Debian/Ubuntu), and signing in needs `libsecret` (`libsecret-1-0`)
|
|
102
101
|
- The **Code Remote** iOS app on your iPhone (App Store release coming; meanwhile build it
|
|
103
|
-
from [source](https://github.com/Tranthanh98/
|
|
102
|
+
from [source](https://github.com/Tranthanh98/coderemote#build-the-ios-app))
|
|
104
103
|
|
|
105
104
|
## FAQ
|
|
106
105
|
|
|
@@ -121,6 +120,6 @@ repository inside it.
|
|
|
121
120
|
|
|
122
121
|
## License
|
|
123
122
|
|
|
124
|
-
[AGPL-3.0-or-later](https://github.com/Tranthanh98/
|
|
123
|
+
[AGPL-3.0-or-later](https://github.com/Tranthanh98/coderemote/blob/main/LICENSE).
|
|
125
124
|
"Code Remote" and its icon are trademarks of the project; see
|
|
126
|
-
[TRADEMARKS.md](https://github.com/Tranthanh98/
|
|
125
|
+
[TRADEMARKS.md](https://github.com/Tranthanh98/coderemote/blob/main/TRADEMARKS.md).
|
package/dist/coderemote.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/coderemote.js
|
|
4
|
-
import { execFile as
|
|
4
|
+
import { execFile as execFile3, spawn } from "node:child_process";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
-
import
|
|
6
|
+
import fs6 from "node:fs";
|
|
7
7
|
import os3 from "node:os";
|
|
8
|
-
import
|
|
9
|
-
import { promisify } from "node:util";
|
|
8
|
+
import path6 from "node:path";
|
|
9
|
+
import { promisify as promisify2 } from "node:util";
|
|
10
10
|
import qrcode from "qrcode-terminal";
|
|
11
11
|
|
|
12
12
|
// ../../packages/agent-core/src/server.js
|
|
@@ -713,20 +713,129 @@ function createAgentServer({ token, root: root2, tunnelToken = async () => null
|
|
|
713
713
|
return server;
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
-
// src/
|
|
716
|
+
// src/devtunnel.js
|
|
717
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
718
|
+
import { createHash } from "node:crypto";
|
|
717
719
|
import fs4 from "node:fs";
|
|
718
720
|
import path4 from "node:path";
|
|
721
|
+
import { promisify } from "node:util";
|
|
722
|
+
var run = promisify(execFile2);
|
|
723
|
+
var DOWNLOAD_BASE = "https://tunnelsassetsprod.blob.core.windows.net/cli";
|
|
724
|
+
var BUILDS = {
|
|
725
|
+
"darwin-arm64": "osx-arm64-devtunnel",
|
|
726
|
+
"darwin-x64": "osx-x64-devtunnel",
|
|
727
|
+
"linux-arm64": "linux-arm64-devtunnel",
|
|
728
|
+
"linux-x64": "linux-x64-devtunnel"
|
|
729
|
+
};
|
|
730
|
+
var MICROSOFT_TEAM_ID = "UBF8T346G9";
|
|
731
|
+
var REFRESH_INTERVAL_MS = 24 * 36e5;
|
|
732
|
+
function executable(file) {
|
|
733
|
+
try {
|
|
734
|
+
fs4.accessSync(file, fs4.constants.X_OK);
|
|
735
|
+
return fs4.statSync(file).isFile();
|
|
736
|
+
} catch {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function onPath(name) {
|
|
741
|
+
for (const dir of (process.env.PATH || "").split(path4.delimiter)) {
|
|
742
|
+
if (dir && executable(path4.join(dir, name))) return path4.join(dir, name);
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
async function ensureDevtunnel({ dir, progress }) {
|
|
747
|
+
const override = process.env.CODEREMOTE_DEVTUNNEL;
|
|
748
|
+
if (override) {
|
|
749
|
+
if (executable(override)) return override;
|
|
750
|
+
throw new Error(`CODEREMOTE_DEVTUNNEL=${override} isn't an executable file.`);
|
|
751
|
+
}
|
|
752
|
+
const fromPath = onPath("devtunnel");
|
|
753
|
+
if (fromPath) return fromPath;
|
|
754
|
+
const build = BUILDS[`${process.platform}-${process.arch}`];
|
|
755
|
+
if (!build) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`No devtunnel build for ${process.platform}-${process.arch}. Install it yourself: https://learn.microsoft.com/azure/developer/dev-tunnels/get-started`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
const bin = path4.join(dir, "devtunnel");
|
|
761
|
+
const metaFile = path4.join(dir, "devtunnel.json");
|
|
762
|
+
let meta = {};
|
|
763
|
+
try {
|
|
764
|
+
meta = JSON.parse(fs4.readFileSync(metaFile, "utf8"));
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
const installed = executable(bin);
|
|
768
|
+
if (installed && Date.now() - (meta.checkedAt ?? 0) < REFRESH_INTERVAL_MS) return bin;
|
|
769
|
+
const url = `${DOWNLOAD_BASE}/${build}`;
|
|
770
|
+
if (installed) {
|
|
771
|
+
try {
|
|
772
|
+
const head = await fetch(url, { method: "HEAD", signal: AbortSignal.timeout(3e3) });
|
|
773
|
+
const lastModified = head.headers.get("last-modified");
|
|
774
|
+
if (head.ok && lastModified && lastModified === meta.lastModified) {
|
|
775
|
+
fs4.writeFileSync(metaFile, JSON.stringify({ ...meta, checkedAt: Date.now() }));
|
|
776
|
+
return bin;
|
|
777
|
+
}
|
|
778
|
+
} catch {
|
|
779
|
+
return bin;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
await download(url, bin, metaFile, progress);
|
|
783
|
+
return bin;
|
|
784
|
+
}
|
|
785
|
+
async function download(url, bin, metaFile, progress) {
|
|
786
|
+
fs4.mkdirSync(path4.dirname(bin), { recursive: true, mode: 448 });
|
|
787
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(12e4) });
|
|
788
|
+
if (!res.ok || !res.body) throw new Error(`Downloading devtunnel failed: HTTP ${res.status} from ${url}`);
|
|
789
|
+
const total = Number(res.headers.get("content-length")) || 0;
|
|
790
|
+
const tmp = `${bin}.${process.pid}.download`;
|
|
791
|
+
const out = fs4.createWriteStream(tmp, { mode: 493 });
|
|
792
|
+
const md5 = createHash("md5");
|
|
793
|
+
let received = 0;
|
|
794
|
+
try {
|
|
795
|
+
for await (const chunk of res.body) {
|
|
796
|
+
md5.update(chunk);
|
|
797
|
+
received += chunk.length;
|
|
798
|
+
if (!out.write(chunk)) await new Promise((r) => out.once("drain", r));
|
|
799
|
+
if (total) progress(`${Math.floor(received / total * 100)}%`);
|
|
800
|
+
}
|
|
801
|
+
await new Promise((resolve, reject) => out.end((e) => e ? reject(e) : resolve()));
|
|
802
|
+
if (total && received !== total) throw new Error(`devtunnel download was cut short (${received} of ${total} bytes)`);
|
|
803
|
+
await verify(tmp, md5.digest("base64"), res.headers.get("content-md5"));
|
|
804
|
+
fs4.chmodSync(tmp, 493);
|
|
805
|
+
fs4.renameSync(tmp, bin);
|
|
806
|
+
} catch (e) {
|
|
807
|
+
out.destroy();
|
|
808
|
+
fs4.rmSync(tmp, { force: true });
|
|
809
|
+
throw e;
|
|
810
|
+
}
|
|
811
|
+
fs4.writeFileSync(metaFile, JSON.stringify({ lastModified: res.headers.get("last-modified"), checkedAt: Date.now() }));
|
|
812
|
+
}
|
|
813
|
+
async function verify(file, md5, expectedMd5) {
|
|
814
|
+
if (expectedMd5 && md5 !== expectedMd5) throw new Error("devtunnel download is corrupt (MD5 mismatch)");
|
|
815
|
+
if (process.platform !== "darwin") return;
|
|
816
|
+
try {
|
|
817
|
+
await run("codesign", ["--verify", "--strict", file]);
|
|
818
|
+
const { stderr } = await run("codesign", ["-dv", file]);
|
|
819
|
+
if (!stderr.includes(`TeamIdentifier=${MICROSOFT_TEAM_ID}`)) throw new Error("not signed by Microsoft");
|
|
820
|
+
} catch (e) {
|
|
821
|
+
throw new Error(`devtunnel download failed its signature check: ${e.message}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/registry.js
|
|
826
|
+
import fs5 from "node:fs";
|
|
827
|
+
import path5 from "node:path";
|
|
719
828
|
var AgentRegistry = class {
|
|
720
829
|
constructor(dir) {
|
|
721
830
|
this.dir = dir;
|
|
722
831
|
}
|
|
723
832
|
register({ port: port2, root: root2, url }) {
|
|
724
|
-
|
|
833
|
+
fs5.mkdirSync(this.dir, { recursive: true, mode: 448 });
|
|
725
834
|
const file = this.#file(port2);
|
|
726
|
-
|
|
835
|
+
fs5.writeFileSync(file, JSON.stringify({ pid: process.pid, port: port2, root: root2, url, startedAt: Date.now() }), { mode: 384 });
|
|
727
836
|
const cleanup = () => {
|
|
728
837
|
try {
|
|
729
|
-
if (JSON.parse(
|
|
838
|
+
if (JSON.parse(fs5.readFileSync(file, "utf8")).pid === process.pid) fs5.rmSync(file);
|
|
730
839
|
} catch {
|
|
731
840
|
}
|
|
732
841
|
};
|
|
@@ -737,25 +846,25 @@ var AgentRegistry = class {
|
|
|
737
846
|
list() {
|
|
738
847
|
let names = [];
|
|
739
848
|
try {
|
|
740
|
-
names =
|
|
849
|
+
names = fs5.readdirSync(this.dir).filter((n) => n.endsWith(".json"));
|
|
741
850
|
} catch {
|
|
742
851
|
return [];
|
|
743
852
|
}
|
|
744
853
|
const agents = [];
|
|
745
854
|
for (const name of names) {
|
|
746
|
-
const file =
|
|
855
|
+
const file = path5.join(this.dir, name);
|
|
747
856
|
try {
|
|
748
|
-
const agent = JSON.parse(
|
|
857
|
+
const agent = JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
749
858
|
process.kill(agent.pid, 0);
|
|
750
859
|
agents.push(agent);
|
|
751
860
|
} catch {
|
|
752
|
-
|
|
861
|
+
fs5.rmSync(file, { force: true });
|
|
753
862
|
}
|
|
754
863
|
}
|
|
755
864
|
return agents.sort((a, b) => a.port - b.port);
|
|
756
865
|
}
|
|
757
866
|
#file(port2) {
|
|
758
|
-
return
|
|
867
|
+
return path5.join(this.dir, `${port2}.json`);
|
|
759
868
|
}
|
|
760
869
|
};
|
|
761
870
|
|
|
@@ -775,6 +884,7 @@ var Spinner = class {
|
|
|
775
884
|
start(text) {
|
|
776
885
|
this.stop();
|
|
777
886
|
this.text = text;
|
|
887
|
+
this.detail = "";
|
|
778
888
|
this.startedAt = Date.now();
|
|
779
889
|
if (!this.tty) {
|
|
780
890
|
this.stream.write(` ${text}...
|
|
@@ -791,6 +901,10 @@ var Spinner = class {
|
|
|
791
901
|
this.render(FRAMES[0]);
|
|
792
902
|
this.timer = setInterval(() => this.render(FRAMES[++frame % FRAMES.length]), 80);
|
|
793
903
|
}
|
|
904
|
+
/** Extra status shown after the step's text while it runs, e.g. a download percentage. */
|
|
905
|
+
update(detail) {
|
|
906
|
+
this.detail = detail;
|
|
907
|
+
}
|
|
794
908
|
succeed() {
|
|
795
909
|
this.finish(green("\u2714"));
|
|
796
910
|
}
|
|
@@ -807,7 +921,8 @@ var Spinner = class {
|
|
|
807
921
|
this.text = null;
|
|
808
922
|
}
|
|
809
923
|
render(frame) {
|
|
810
|
-
|
|
924
|
+
const detail = this.detail ? ` ${this.detail}` : "";
|
|
925
|
+
this.stream.write(`\r\x1B[2K ${cyan(frame)} ${this.text}${detail} ${dim(this.elapsed())}`);
|
|
811
926
|
}
|
|
812
927
|
finish(mark) {
|
|
813
928
|
if (!this.text) return;
|
|
@@ -823,17 +938,17 @@ var Spinner = class {
|
|
|
823
938
|
};
|
|
824
939
|
|
|
825
940
|
// bin/coderemote.js
|
|
826
|
-
var
|
|
827
|
-
var CONFIG_DIR =
|
|
828
|
-
var LEGACY_CONFIG_DIR =
|
|
829
|
-
if (!
|
|
830
|
-
var CONFIG_FILE =
|
|
941
|
+
var run2 = promisify2(execFile3);
|
|
942
|
+
var CONFIG_DIR = path6.join(os3.homedir(), ".coderemote");
|
|
943
|
+
var LEGACY_CONFIG_DIR = path6.join(os3.homedir(), ".vsremote");
|
|
944
|
+
if (!fs6.existsSync(CONFIG_DIR) && fs6.existsSync(LEGACY_CONFIG_DIR)) fs6.renameSync(LEGACY_CONFIG_DIR, CONFIG_DIR);
|
|
945
|
+
var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
|
|
831
946
|
var DEFAULT_PORT = 31580;
|
|
832
947
|
var spinner = new Spinner();
|
|
833
948
|
function loadConfig() {
|
|
834
949
|
let cfg2 = {};
|
|
835
950
|
try {
|
|
836
|
-
cfg2 = JSON.parse(
|
|
951
|
+
cfg2 = JSON.parse(fs6.readFileSync(CONFIG_FILE, "utf8"));
|
|
837
952
|
} catch {
|
|
838
953
|
}
|
|
839
954
|
if (!cfg2.token) {
|
|
@@ -843,8 +958,8 @@ function loadConfig() {
|
|
|
843
958
|
return cfg2;
|
|
844
959
|
}
|
|
845
960
|
function saveConfig(cfg2) {
|
|
846
|
-
|
|
847
|
-
|
|
961
|
+
fs6.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
962
|
+
fs6.writeFileSync(CONFIG_FILE, JSON.stringify(cfg2, null, 2), { mode: 384 });
|
|
848
963
|
}
|
|
849
964
|
function listen(token, port2, root2, tunnelToken) {
|
|
850
965
|
const server = createAgentServer({ token, root: root2, tunnelToken });
|
|
@@ -853,33 +968,51 @@ function listen(token, port2, root2, tunnelToken) {
|
|
|
853
968
|
server.listen(port2, "127.0.0.1", () => resolve(server));
|
|
854
969
|
});
|
|
855
970
|
}
|
|
971
|
+
var DEVTUNNEL = "devtunnel";
|
|
972
|
+
async function resolveDevtunnel() {
|
|
973
|
+
let installing = false;
|
|
974
|
+
DEVTUNNEL = await ensureDevtunnel({
|
|
975
|
+
dir: path6.join(CONFIG_DIR, "bin"),
|
|
976
|
+
progress: (detail) => {
|
|
977
|
+
if (!installing) {
|
|
978
|
+
installing = true;
|
|
979
|
+
spinner.start("Installing devtunnel (Microsoft Dev Tunnels CLI, one time)");
|
|
980
|
+
}
|
|
981
|
+
spinner.update(detail);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
if (installing) spinner.succeed();
|
|
985
|
+
}
|
|
856
986
|
async function devtunnel(...args) {
|
|
857
987
|
try {
|
|
858
|
-
const { stdout } = await
|
|
988
|
+
const { stdout } = await run2(DEVTUNNEL, args);
|
|
859
989
|
return { ok: true, out: stdout };
|
|
860
990
|
} catch (e) {
|
|
861
|
-
|
|
862
|
-
spinner.stop();
|
|
863
|
-
console.error("devtunnel CLI not found. Install it: brew install --cask devtunnel");
|
|
864
|
-
process.exit(1);
|
|
865
|
-
}
|
|
866
|
-
return { ok: false, out: `${e.stdout || ""}${e.stderr || ""}` };
|
|
991
|
+
return { ok: false, out: `${e.stdout || ""}${e.stderr || ""}` || e.message };
|
|
867
992
|
}
|
|
868
993
|
}
|
|
994
|
+
function loginArgs(opts = rest) {
|
|
995
|
+
const args = ["user", "login"];
|
|
996
|
+
if (!opts.includes("--microsoft")) args.push("-g");
|
|
997
|
+
if (opts.includes("--device-code")) args.push("-d");
|
|
998
|
+
return args;
|
|
999
|
+
}
|
|
1000
|
+
function interactiveLogin(args) {
|
|
1001
|
+
return new Promise((resolve) => {
|
|
1002
|
+
spawn(DEVTUNNEL, args, { stdio: "inherit" }).on("exit", resolve);
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
869
1005
|
var LOGIN_NEEDED = /not logged in|login token expired|login required|please log ?in/i;
|
|
870
1006
|
async function login(reason) {
|
|
871
1007
|
spinner.stop();
|
|
872
1008
|
console.error(`Dev tunnels: ${reason.trim()}`);
|
|
873
1009
|
if (!process.stdin.isTTY) {
|
|
874
|
-
console.error("Run
|
|
1010
|
+
console.error("Run `coderemote login` (--microsoft for a Microsoft account), then try again.");
|
|
875
1011
|
process.exit(1);
|
|
876
1012
|
}
|
|
877
|
-
console.error("Signing in
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
});
|
|
881
|
-
if (code !== 0) {
|
|
882
|
-
console.error("\nLogin failed. Run `devtunnel user login -g` (or -d) yourself, then try again.");
|
|
1013
|
+
console.error("Signing in with GitHub (use `coderemote login --microsoft` for a Microsoft account)...\n");
|
|
1014
|
+
if (await interactiveLogin(loginArgs([])) !== 0) {
|
|
1015
|
+
console.error("\nLogin failed. Run `coderemote login` (--microsoft, --device-code), then try again.");
|
|
883
1016
|
process.exit(1);
|
|
884
1017
|
}
|
|
885
1018
|
}
|
|
@@ -935,7 +1068,7 @@ ${res.out}`);
|
|
|
935
1068
|
}
|
|
936
1069
|
function hostTunnel(tunnelId, port2) {
|
|
937
1070
|
return new Promise((resolve, reject) => {
|
|
938
|
-
const child = spawn(
|
|
1071
|
+
const child = spawn(DEVTUNNEL, ["host", tunnelId], { stdio: ["ignore", "pipe", "pipe"] });
|
|
939
1072
|
let log2 = "";
|
|
940
1073
|
const onData = (d) => {
|
|
941
1074
|
log2 += d;
|
|
@@ -971,6 +1104,8 @@ var USAGE = `Usage: coderemote <command> [options]
|
|
|
971
1104
|
kill <id>... | --all kill terminal sessions (an id prefix is enough)
|
|
972
1105
|
stop [--all] stop the agent for this folder (or every agent)
|
|
973
1106
|
rotate-token revoke every paired device
|
|
1107
|
+
login sign in to Microsoft Dev Tunnels with GitHub (up does this when needed);
|
|
1108
|
+
--microsoft for a Microsoft account, --device-code without a browser
|
|
974
1109
|
|
|
975
1110
|
Options:
|
|
976
1111
|
--root DIR folder to share (default: current folder)
|
|
@@ -986,9 +1121,9 @@ var flag = (name) => {
|
|
|
986
1121
|
};
|
|
987
1122
|
var anonymous = rest.includes("--allow-anonymous") || rest.includes("--no-auth");
|
|
988
1123
|
var port = flag("--port") ? parseInt(flag("--port"), 10) : DEFAULT_PORT;
|
|
989
|
-
var root =
|
|
1124
|
+
var root = path6.resolve(flag("--root") ?? process.cwd());
|
|
990
1125
|
var cfg = loadConfig();
|
|
991
|
-
var registry = new AgentRegistry(
|
|
1126
|
+
var registry = new AgentRegistry(path6.join(CONFIG_DIR, "agents"));
|
|
992
1127
|
async function start({ tunnel }) {
|
|
993
1128
|
const running = registry.list().find((a) => a.port === port);
|
|
994
1129
|
if (running) {
|
|
@@ -1015,6 +1150,7 @@ async function start({ tunnel }) {
|
|
|
1015
1150
|
let url = `http://127.0.0.1:${port}`;
|
|
1016
1151
|
if (tunnel) {
|
|
1017
1152
|
spinner.succeed();
|
|
1153
|
+
await resolveDevtunnel();
|
|
1018
1154
|
spinner.start("Checking dev tunnels sign-in");
|
|
1019
1155
|
const user = await tunnelUser();
|
|
1020
1156
|
spinner.succeed();
|
|
@@ -1042,6 +1178,8 @@ async function start({ tunnel }) {
|
|
|
1042
1178
|
};
|
|
1043
1179
|
process.on("SIGINT", shutdown);
|
|
1044
1180
|
process.on("SIGTERM", shutdown);
|
|
1181
|
+
process.on("SIGHUP", shutdown);
|
|
1182
|
+
process.on("exit", () => child?.kill());
|
|
1045
1183
|
return { url, access };
|
|
1046
1184
|
}
|
|
1047
1185
|
async function agentApi(agent, method, route, query = {}) {
|
|
@@ -1151,6 +1289,10 @@ Token: ${cfg.token}`);
|
|
|
1151
1289
|
console.log("Token rotated. Re-pair your devices, and restart running agents.");
|
|
1152
1290
|
break;
|
|
1153
1291
|
}
|
|
1292
|
+
case "login": {
|
|
1293
|
+
await resolveDevtunnel();
|
|
1294
|
+
process.exit(await interactiveLogin(loginArgs()));
|
|
1295
|
+
}
|
|
1154
1296
|
case "help":
|
|
1155
1297
|
console.log(USAGE);
|
|
1156
1298
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderemote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Use your computer from your iPhone: files, git and a real terminal over a secure Microsoft Dev Tunnel. No SSH, no port forwarding, no VPN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"remote",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"vscode",
|
|
22
22
|
"code-remote"
|
|
23
23
|
],
|
|
24
|
-
"homepage": "https://github.com/Tranthanh98/
|
|
24
|
+
"homepage": "https://github.com/Tranthanh98/coderemote#readme",
|
|
25
25
|
"bugs": {
|
|
26
|
-
"url": "https://github.com/Tranthanh98/
|
|
26
|
+
"url": "https://github.com/Tranthanh98/coderemote/issues"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/Tranthanh98/
|
|
30
|
+
"url": "git+https://github.com/Tranthanh98/coderemote.git",
|
|
31
31
|
"directory": "apps/agent"
|
|
32
32
|
},
|
|
33
33
|
"license": "AGPL-3.0-or-later",
|