@lightyearminds/dotline-agent 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.
Files changed (3) hide show
  1. package/README.md +34 -0
  2. package/dist/index.js +218 -0
  3. package/package.json +22 -0
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Dotline Agent (CLI)
2
+
3
+ The Dotline backend cannot reach `localhost` or private network addresses from the cloud.
4
+ Dotline Agent runs on the user's machine and **executes HTTP requests locally**, then
5
+ returns the response back to Dotline.
6
+
7
+ ## Install (dev)
8
+
9
+ From the repo root:
10
+
11
+ ```bash
12
+ cd agent-cli
13
+ npm install
14
+ npm run build
15
+ node dist/index.js --help
16
+ ```
17
+
18
+ ## Pair (from the UI)
19
+
20
+ 1) Your Dotline UI calls `POST /agents/pairing-code` (authenticated) and shows the code.
21
+
22
+ 2) On your machine:
23
+
24
+ ```bash
25
+ dotline-agent login --api https://<DOTLINE_API> --code <PAIRING_CODE> --name "My Machine"
26
+ dotline-agent start --api https://<DOTLINE_API>
27
+ ```
28
+
29
+ ## What it supports
30
+
31
+ * JSON/text requests (GET/POST/PUT/PATCH/DELETE)
32
+ * Localhost/private IP targets
33
+
34
+ Multipart/FormData support can be added next (it requires streaming files and boundaries).
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { setTimeout as sleep } from "node:timers/promises";
5
+ function hasTask(r) {
6
+ return typeof r.id === "string";
7
+ }
8
+ const CONFIG_DIR = path.join(os.homedir(), ".dotline");
9
+ const CONFIG_PATH = path.join(CONFIG_DIR, "agent.json");
10
+ function usage() {
11
+ console.log(`
12
+ Dotline Agent
13
+
14
+ Commands:
15
+ dotline-agent login --api <DOTLINE_API_BASE> --code <PAIRING_CODE> [--name "My Machine"]
16
+ dotline-agent start --api <DOTLINE_API_BASE>
17
+ dotline-agent status
18
+
19
+ Examples:
20
+ dotline-agent login --api https://api.dotline.app --code ABCDEF1234 --name "MacBook"
21
+ dotline-agent start --api https://api.dotline.app
22
+ `);
23
+ }
24
+ function parseArgs(argv) {
25
+ const out = {};
26
+ for (let i = 0; i < argv.length; i++) {
27
+ const a = argv[i];
28
+ if (!a)
29
+ continue;
30
+ if (a.startsWith("--")) {
31
+ const k = a.slice(2);
32
+ const next = argv[i + 1];
33
+ if (next && !next.startsWith("--")) {
34
+ out[k] = next;
35
+ i++;
36
+ }
37
+ else {
38
+ out[k] = true;
39
+ }
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ function loadConfig() {
45
+ try {
46
+ const raw = fs.readFileSync(CONFIG_PATH, "utf8");
47
+ return JSON.parse(raw);
48
+ }
49
+ catch {
50
+ return {};
51
+ }
52
+ }
53
+ function saveConfig(cfg) {
54
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
55
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), "utf8");
56
+ }
57
+ function normalizeApiBase(api) {
58
+ return api.replace(/\/$/, "");
59
+ }
60
+ async function httpJson(url, init) {
61
+ const res = await fetch(url, init);
62
+ const text = await res.text();
63
+ let json = null;
64
+ try {
65
+ json = JSON.parse(text);
66
+ }
67
+ catch {
68
+ // ignore
69
+ }
70
+ if (!res.ok) {
71
+ const msg = json?.message || text || `${res.status} ${res.statusText}`;
72
+ throw new Error(msg);
73
+ }
74
+ return json;
75
+ }
76
+ async function cmdLogin(args) {
77
+ const api = args.api;
78
+ const code = args.code;
79
+ const name = args.name ?? "My Machine";
80
+ if (!api || !code) {
81
+ usage();
82
+ process.exit(1);
83
+ }
84
+ const apiBase = normalizeApiBase(api);
85
+ const resp = (await httpJson(`${apiBase}/agents/claim`, {
86
+ method: "POST",
87
+ headers: { "Content-Type": "application/json" },
88
+ body: JSON.stringify({ code, name }),
89
+ }));
90
+ saveConfig({ api: apiBase, token: resp.agentToken, agentId: resp.agentId });
91
+ console.log(`✅ Agent paired as "${resp.name}" (agentId=${resp.agentId}).`);
92
+ console.log(`Config saved to ${CONFIG_PATH}`);
93
+ }
94
+ async function cmdStatus() {
95
+ const cfg = loadConfig();
96
+ if (!cfg.api || !cfg.token) {
97
+ console.log("Not logged in. Run: dotline-agent login --api <...> --code <...>");
98
+ process.exit(1);
99
+ }
100
+ try {
101
+ const me = await httpJson(`${cfg.api}/agents/me`, {
102
+ method: "GET",
103
+ headers: { "x-dotline-agent-token": cfg.token },
104
+ });
105
+ console.log(JSON.stringify(me, null, 2));
106
+ }
107
+ catch (e) {
108
+ console.error(`❌ ${e instanceof Error ? e.message : String(e)}`);
109
+ process.exit(1);
110
+ }
111
+ }
112
+ async function runHttpTask(api, token, task) {
113
+ const req = task.request ?? {};
114
+ const url = String(req.url ?? "");
115
+ const method = String(req.method ?? "GET").toUpperCase();
116
+ const headers = (req.headers ?? {});
117
+ const bodyText = req.bodyText;
118
+ const started = Date.now();
119
+ try {
120
+ const res = await fetch(url, {
121
+ method,
122
+ headers,
123
+ body: bodyText === undefined ? undefined : String(bodyText),
124
+ });
125
+ const text = await res.text();
126
+ const elapsedMs = Date.now() - started;
127
+ await httpJson(`${api}/agents/tasks/${task.id}/complete`, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json",
131
+ "x-dotline-agent-token": token,
132
+ },
133
+ body: JSON.stringify({
134
+ statusCode: res.status,
135
+ headers: Object.fromEntries(res.headers.entries()),
136
+ bodyText: text,
137
+ elapsedMs,
138
+ }),
139
+ });
140
+ }
141
+ catch (e) {
142
+ const elapsedMs = Date.now() - started;
143
+ await httpJson(`${api}/agents/tasks/${task.id}/complete`, {
144
+ method: "POST",
145
+ headers: {
146
+ "Content-Type": "application/json",
147
+ "x-dotline-agent-token": token,
148
+ },
149
+ body: JSON.stringify({
150
+ error: e instanceof Error ? e.message : String(e),
151
+ elapsedMs,
152
+ }),
153
+ });
154
+ }
155
+ }
156
+ async function cmdStart(args) {
157
+ const cfg = loadConfig();
158
+ const api = normalizeApiBase(args.api ?? cfg.api ?? "");
159
+ const token = cfg.token ?? undefined;
160
+ if (!api || !token) {
161
+ console.log("Not logged in. Run: dotline-agent login --api <...> --code <...>");
162
+ process.exit(1);
163
+ }
164
+ console.log(`🤖 Dotline Agent started. Polling ${api} ...`);
165
+ while (true) {
166
+ try {
167
+ // heartbeat (best effort)
168
+ fetch(`${api}/agents/heartbeat`, {
169
+ method: "POST",
170
+ headers: { "x-dotline-agent-token": token },
171
+ }).catch(() => void 0);
172
+ const next = (await httpJson(`${api}/agents/tasks/next?waitMs=25000`, {
173
+ method: "GET",
174
+ headers: { "x-dotline-agent-token": token },
175
+ }));
176
+ if (!hasTask(next)) {
177
+ continue;
178
+ }
179
+ if (next.kind === "HTTP" || next.kind === "HTTP_REQUEST") {
180
+ await runHttpTask(api, token, next);
181
+ }
182
+ else {
183
+ await httpJson(`${api}/agents/tasks/${next.id}/complete`, {
184
+ method: "POST",
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ "x-dotline-agent-token": token,
188
+ },
189
+ body: JSON.stringify({ error: `Unknown task kind: ${next.kind}` }),
190
+ });
191
+ }
192
+ }
193
+ catch (e) {
194
+ console.error(`⚠️ ${e instanceof Error ? e.message : String(e)}`);
195
+ await sleep(1000);
196
+ }
197
+ }
198
+ }
199
+ async function main() {
200
+ const [cmd, ...rest] = process.argv.slice(2);
201
+ if (!cmd) {
202
+ usage();
203
+ return;
204
+ }
205
+ const args = parseArgs(rest);
206
+ if (cmd === "login")
207
+ return cmdLogin(args);
208
+ if (cmd === "start")
209
+ return cmdStart(args);
210
+ if (cmd === "status")
211
+ return cmdStatus();
212
+ usage();
213
+ process.exit(1);
214
+ }
215
+ main().catch((e) => {
216
+ console.error(`❌ ${e instanceof Error ? e.message : String(e)}`);
217
+ process.exit(1);
218
+ });
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@lightyearminds/dotline-agent",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "dotline-agent": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.json",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.6.3"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ }