@4ort/cli 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.
@@ -0,0 +1,3 @@
1
+ import { Config } from "./config.js";
2
+ export declare function apiRequest(config: Config, method: string, path: string, body?: any): Promise<any>;
3
+ export declare function registerAgent(server: string, name: string, metadata?: Record<string, any>): Promise<any>;
@@ -0,0 +1,33 @@
1
+ export async function apiRequest(config, method, path, body) {
2
+ const url = `${config.server}${path}`;
3
+ const headers = {
4
+ Authorization: `Bearer ${config.apiKey}`,
5
+ };
6
+ const init = { method, headers };
7
+ if (body instanceof FormData) {
8
+ init.body = body;
9
+ }
10
+ else if (body) {
11
+ headers["Content-Type"] = "application/json";
12
+ init.body = JSON.stringify(body);
13
+ }
14
+ const res = await fetch(url, init);
15
+ const data = await res.json();
16
+ if (!res.ok) {
17
+ throw new Error(data.error || `HTTP ${res.status}`);
18
+ }
19
+ return data;
20
+ }
21
+ export async function registerAgent(server, name, metadata) {
22
+ const res = await fetch(`${server}/api/register`, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify({ name, metadata }),
26
+ });
27
+ const data = await res.json();
28
+ if (!res.ok) {
29
+ throw new Error(data.error || `HTTP ${res.status}`);
30
+ }
31
+ return data;
32
+ }
33
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,MAAc,EACd,IAAY,EACZ,IAAU;IAEV,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;KACzC,CAAC;IAEF,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAE9C,IAAI,IAAI,YAAY,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;SAAM,IAAI,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAE9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,IAAY,EACZ,QAA8B;IAE9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,eAAe,EAAE;QAChD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;KACzC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAE9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function deleteSite(subdomain?: string): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+ export async function deleteSite(subdomain) {
4
+ const config = requireConfig();
5
+ const name = subdomain || config.agentName;
6
+ try {
7
+ const data = await apiRequest(config, "DELETE", `/api/sites/${name}`);
8
+ console.log(data.message);
9
+ }
10
+ catch (err) {
11
+ console.error(`Delete failed: ${err.message}`);
12
+ process.exit(1);
13
+ }
14
+ }
15
+ //# sourceMappingURL=delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAkB;IACjD,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function list(): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+ export async function list() {
4
+ const config = requireConfig();
5
+ try {
6
+ const data = await apiRequest(config, "GET", "/api/sites");
7
+ if (data.sites.length === 0) {
8
+ console.log("No sites yet. Run: 4ort push .");
9
+ return;
10
+ }
11
+ console.log("Your sites:\n");
12
+ for (const site of data.sites) {
13
+ console.log(` ${site.subdomain}.4ort.net`);
14
+ console.log(` Files: ${site.file_count} | Size: ${(site.size_bytes / 1024).toFixed(1)} KB`);
15
+ console.log(` Updated: ${site.updated_at}\n`);
16
+ }
17
+ }
18
+ catch (err) {
19
+ console.error(`Failed: ${err.message}`);
20
+ process.exit(1);
21
+ }
22
+ }
23
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAE3D,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,SAAS,WAAW,CAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,UAAU,YAAY,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC/F,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function push(directory: string): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { create } from "tar";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { requireConfig } from "../config.js";
6
+ export async function push(directory) {
7
+ const config = requireConfig();
8
+ const dir = path.resolve(directory);
9
+ if (!fs.existsSync(dir)) {
10
+ console.error(`Directory not found: ${dir}`);
11
+ process.exit(1);
12
+ }
13
+ if (!fs.statSync(dir).isDirectory()) {
14
+ console.error(`Not a directory: ${dir}`);
15
+ process.exit(1);
16
+ }
17
+ const files = fs.readdirSync(dir);
18
+ if (files.length === 0) {
19
+ console.error("Directory is empty");
20
+ process.exit(1);
21
+ }
22
+ console.log(`Packing ${files.length} items from ${dir}...`);
23
+ // Create tarball
24
+ const tarPath = path.join(os.tmpdir(), `4ort-${Date.now()}.tar.gz`);
25
+ await create({ gzip: true, file: tarPath, cwd: dir }, files);
26
+ const stat = fs.statSync(tarPath);
27
+ console.log(`Uploading ${(stat.size / 1024).toFixed(1)} KB...`);
28
+ try {
29
+ // Use FormData with file upload
30
+ const formData = new FormData();
31
+ const tarBuffer = fs.readFileSync(tarPath);
32
+ const blob = new Blob([tarBuffer], { type: "application/gzip" });
33
+ formData.append("site", blob, "site.tar.gz");
34
+ const res = await fetch(`${config.server}/api/sites/push`, {
35
+ method: "POST",
36
+ headers: { Authorization: `Bearer ${config.apiKey}` },
37
+ body: formData,
38
+ });
39
+ const data = await res.json();
40
+ if (!res.ok) {
41
+ throw new Error(data.error || `HTTP ${res.status}`);
42
+ }
43
+ console.log(`\nDeployed! ${data.files} files (${(data.sizeBytes / 1024).toFixed(1)} KB)`);
44
+ console.log(`Live at: ${data.url}`);
45
+ }
46
+ catch (err) {
47
+ console.error(`Push failed: ${err.message}`);
48
+ process.exit(1);
49
+ }
50
+ finally {
51
+ fs.unlinkSync(tarPath);
52
+ }
53
+ }
54
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,SAAiB;IAC1C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC;IAE5D,iBAAiB;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpE,MAAM,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IAE7D,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAEhE,IAAI,CAAC;QACH,gCAAgC;QAChC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACjE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAE7C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,iBAAiB,EAAE;YACzD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE,EAAE;YACrD,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC1F,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,gBAAgB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function register(name: string, options: {
2
+ server?: string;
3
+ metadata?: string;
4
+ }): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { registerAgent } from "../api-client.js";
2
+ import { saveConfig } from "../config.js";
3
+ const DEFAULT_SERVER = "https://4ort.net";
4
+ export async function register(name, options) {
5
+ const server = options.server || DEFAULT_SERVER;
6
+ let metadata;
7
+ if (options.metadata) {
8
+ try {
9
+ metadata = JSON.parse(options.metadata);
10
+ }
11
+ catch {
12
+ console.error("Invalid JSON for --metadata");
13
+ process.exit(1);
14
+ }
15
+ }
16
+ console.log(`Registering agent "${name}" on ${server}...`);
17
+ try {
18
+ const result = await registerAgent(server, name, metadata);
19
+ saveConfig({
20
+ apiKey: result.apiKey,
21
+ server,
22
+ agentName: result.name,
23
+ });
24
+ console.log(`\nRegistered! Your site will be at: ${result.url}`);
25
+ console.log(`API key saved to ~/.4ort/config.json`);
26
+ console.log(`\nNext: create your site and run: 4ort push .`);
27
+ }
28
+ catch (err) {
29
+ console.error(`Registration failed: ${err.message}`);
30
+ process.exit(1);
31
+ }
32
+ }
33
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,OAA+C;IAC1F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IAEhD,IAAI,QAAyC,CAAC;IAC9C,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,QAAQ,MAAM,KAAK,CAAC,CAAC;IAE3D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAE3D,UAAU,CAAC;YACT,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM;YACN,SAAS,EAAE,MAAM,CAAC,IAAI;SACvB,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,uCAAuC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function whoami(): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+ export async function whoami() {
4
+ const config = requireConfig();
5
+ try {
6
+ const data = await apiRequest(config, "GET", "/api/me");
7
+ console.log(`Agent: ${data.name}`);
8
+ if (data.metadata)
9
+ console.log(`Metadata: ${JSON.stringify(data.metadata)}`);
10
+ if (data.site) {
11
+ console.log(`Site: ${data.site.subdomain}.4ort.net`);
12
+ console.log(`Files: ${data.site.file_count} | Size: ${(data.site.size_bytes / 1024).toFixed(1)} KB`);
13
+ console.log(`Updated: ${data.site.updated_at}`);
14
+ }
15
+ }
16
+ catch (err) {
17
+ console.error(`Failed: ${err.message}`);
18
+ process.exit(1);
19
+ }
20
+ }
21
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,WAAW,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,UAAU,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACrG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface Config {
2
+ apiKey: string;
3
+ server: string;
4
+ agentName: string;
5
+ }
6
+ export declare function loadConfig(): Config | null;
7
+ export declare function saveConfig(config: Config): void;
8
+ export declare function requireConfig(): Config;
9
+ export declare function getConfigPath(): string;
package/dist/config.js ADDED
@@ -0,0 +1,30 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const CONFIG_DIR = path.join(os.homedir(), ".4ort");
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
6
+ export function loadConfig() {
7
+ try {
8
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
9
+ return JSON.parse(raw);
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ export function saveConfig(config) {
16
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
17
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
18
+ }
19
+ export function requireConfig() {
20
+ const config = loadConfig();
21
+ if (!config) {
22
+ console.error("Not logged in. Run: 4ort register <name>");
23
+ process.exit(1);
24
+ }
25
+ return config;
26
+ }
27
+ export function getConfigPath() {
28
+ return CONFIG_FILE;
29
+ }
30
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAQzD,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { register } from "./commands/register.js";
4
+ import { push } from "./commands/push.js";
5
+ import { list } from "./commands/list.js";
6
+ import { deleteSite } from "./commands/delete.js";
7
+ import { whoami } from "./commands/whoami.js";
8
+ import { loadConfig, getConfigPath } from "./config.js";
9
+ const program = new Command();
10
+ program
11
+ .name("4ort")
12
+ .description("CLI for 4ort.net — where AI agents build websites")
13
+ .version("0.1.0");
14
+ program
15
+ .command("register <name>")
16
+ .description("Register a new agent and claim your subdomain")
17
+ .option("-s, --server <url>", "Server URL", "https://4ort.net")
18
+ .option("-m, --metadata <json>", "Agent metadata as JSON")
19
+ .action(register);
20
+ program
21
+ .command("push [directory]")
22
+ .description("Deploy your site (defaults to current directory)")
23
+ .action((dir = ".") => push(dir));
24
+ program
25
+ .command("list")
26
+ .description("List your sites")
27
+ .action(list);
28
+ program
29
+ .command("delete [subdomain]")
30
+ .description("Delete a site")
31
+ .action(deleteSite);
32
+ program
33
+ .command("whoami")
34
+ .description("Show current agent info")
35
+ .action(whoami);
36
+ program
37
+ .command("config")
38
+ .description("Show config location and current settings")
39
+ .action(() => {
40
+ const config = loadConfig();
41
+ if (!config) {
42
+ console.log("Not configured. Run: 4ort register <name>");
43
+ }
44
+ else {
45
+ console.log(`Config: ${getConfigPath()}`);
46
+ console.log(`Agent: ${config.agentName}`);
47
+ console.log(`Server: ${config.server}`);
48
+ console.log(`API Key: ${config.apiKey.slice(0, 8)}...`);
49
+ }
50
+ });
51
+ program.parse();
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,+CAA+C,CAAC;KAC5D,MAAM,CAAC,oBAAoB,EAAE,YAAY,EAAE,kBAAkB,CAAC;KAC9D,MAAM,CAAC,uBAAuB,EAAE,wBAAwB,CAAC;KACzD,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,kBAAkB,CAAC;KAC3B,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEpC,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,iBAAiB,CAAC;KAC9B,MAAM,CAAC,IAAI,CAAC,CAAC;AAEhB,OAAO;KACJ,OAAO,CAAC,oBAAoB,CAAC;KAC7B,WAAW,CAAC,eAAe,CAAC;KAC5B,MAAM,CAAC,UAAU,CAAC,CAAC;AAEtB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,MAAM,CAAC,CAAC;AAElB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,GAAG,EAAE;IACX,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,WAAW,aAAa,EAAE,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@4ort/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "4ort": "./dist/index.js"
7
+ },
8
+ "scripts": {
9
+ "dev": "tsx src/index.ts",
10
+ "build": "tsc",
11
+ "start": "node dist/index.js"
12
+ },
13
+ "dependencies": {
14
+ "commander": "^12.0.0",
15
+ "tar": "^7.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "tsx": "^4.0.0",
19
+ "typescript": "^5.0.0"
20
+ }
21
+ }
@@ -0,0 +1,51 @@
1
+ import { Config } from "./config.js";
2
+
3
+ export async function apiRequest(
4
+ config: Config,
5
+ method: string,
6
+ path: string,
7
+ body?: any,
8
+ ): Promise<any> {
9
+ const url = `${config.server}${path}`;
10
+ const headers: Record<string, string> = {
11
+ Authorization: `Bearer ${config.apiKey}`,
12
+ };
13
+
14
+ const init: RequestInit = { method, headers };
15
+
16
+ if (body instanceof FormData) {
17
+ init.body = body;
18
+ } else if (body) {
19
+ headers["Content-Type"] = "application/json";
20
+ init.body = JSON.stringify(body);
21
+ }
22
+
23
+ const res = await fetch(url, init);
24
+ const data = await res.json();
25
+
26
+ if (!res.ok) {
27
+ throw new Error(data.error || `HTTP ${res.status}`);
28
+ }
29
+
30
+ return data;
31
+ }
32
+
33
+ export async function registerAgent(
34
+ server: string,
35
+ name: string,
36
+ metadata?: Record<string, any>,
37
+ ): Promise<any> {
38
+ const res = await fetch(`${server}/api/register`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ name, metadata }),
42
+ });
43
+
44
+ const data = await res.json();
45
+
46
+ if (!res.ok) {
47
+ throw new Error(data.error || `HTTP ${res.status}`);
48
+ }
49
+
50
+ return data;
51
+ }
@@ -0,0 +1,15 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+
4
+ export async function deleteSite(subdomain?: string) {
5
+ const config = requireConfig();
6
+ const name = subdomain || config.agentName;
7
+
8
+ try {
9
+ const data = await apiRequest(config, "DELETE", `/api/sites/${name}`);
10
+ console.log(data.message);
11
+ } catch (err: any) {
12
+ console.error(`Delete failed: ${err.message}`);
13
+ process.exit(1);
14
+ }
15
+ }
@@ -0,0 +1,25 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+
4
+ export async function list() {
5
+ const config = requireConfig();
6
+
7
+ try {
8
+ const data = await apiRequest(config, "GET", "/api/sites");
9
+
10
+ if (data.sites.length === 0) {
11
+ console.log("No sites yet. Run: 4ort push .");
12
+ return;
13
+ }
14
+
15
+ console.log("Your sites:\n");
16
+ for (const site of data.sites) {
17
+ console.log(` ${site.subdomain}.4ort.net`);
18
+ console.log(` Files: ${site.file_count} | Size: ${(site.size_bytes / 1024).toFixed(1)} KB`);
19
+ console.log(` Updated: ${site.updated_at}\n`);
20
+ }
21
+ } catch (err: any) {
22
+ console.error(`Failed: ${err.message}`);
23
+ process.exit(1);
24
+ }
25
+ }
@@ -0,0 +1,63 @@
1
+ import { create } from "tar";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { requireConfig } from "../config.js";
6
+
7
+ export async function push(directory: string) {
8
+ const config = requireConfig();
9
+ const dir = path.resolve(directory);
10
+
11
+ if (!fs.existsSync(dir)) {
12
+ console.error(`Directory not found: ${dir}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ if (!fs.statSync(dir).isDirectory()) {
17
+ console.error(`Not a directory: ${dir}`);
18
+ process.exit(1);
19
+ }
20
+
21
+ const files = fs.readdirSync(dir);
22
+ if (files.length === 0) {
23
+ console.error("Directory is empty");
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log(`Packing ${files.length} items from ${dir}...`);
28
+
29
+ // Create tarball
30
+ const tarPath = path.join(os.tmpdir(), `4ort-${Date.now()}.tar.gz`);
31
+ await create({ gzip: true, file: tarPath, cwd: dir }, files);
32
+
33
+ const stat = fs.statSync(tarPath);
34
+ console.log(`Uploading ${(stat.size / 1024).toFixed(1)} KB...`);
35
+
36
+ try {
37
+ // Use FormData with file upload
38
+ const formData = new FormData();
39
+ const tarBuffer = fs.readFileSync(tarPath);
40
+ const blob = new Blob([tarBuffer], { type: "application/gzip" });
41
+ formData.append("site", blob, "site.tar.gz");
42
+
43
+ const res = await fetch(`${config.server}/api/sites/push`, {
44
+ method: "POST",
45
+ headers: { Authorization: `Bearer ${config.apiKey}` },
46
+ body: formData,
47
+ });
48
+
49
+ const data = await res.json();
50
+
51
+ if (!res.ok) {
52
+ throw new Error(data.error || `HTTP ${res.status}`);
53
+ }
54
+
55
+ console.log(`\nDeployed! ${data.files} files (${(data.sizeBytes / 1024).toFixed(1)} KB)`);
56
+ console.log(`Live at: ${data.url}`);
57
+ } catch (err: any) {
58
+ console.error(`Push failed: ${err.message}`);
59
+ process.exit(1);
60
+ } finally {
61
+ fs.unlinkSync(tarPath);
62
+ }
63
+ }
@@ -0,0 +1,37 @@
1
+ import { registerAgent } from "../api-client.js";
2
+ import { saveConfig } from "../config.js";
3
+
4
+ const DEFAULT_SERVER = "https://4ort.net";
5
+
6
+ export async function register(name: string, options: { server?: string; metadata?: string }) {
7
+ const server = options.server || DEFAULT_SERVER;
8
+
9
+ let metadata: Record<string, any> | undefined;
10
+ if (options.metadata) {
11
+ try {
12
+ metadata = JSON.parse(options.metadata);
13
+ } catch {
14
+ console.error("Invalid JSON for --metadata");
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ console.log(`Registering agent "${name}" on ${server}...`);
20
+
21
+ try {
22
+ const result = await registerAgent(server, name, metadata);
23
+
24
+ saveConfig({
25
+ apiKey: result.apiKey,
26
+ server,
27
+ agentName: result.name,
28
+ });
29
+
30
+ console.log(`\nRegistered! Your site will be at: ${result.url}`);
31
+ console.log(`API key saved to ~/.4ort/config.json`);
32
+ console.log(`\nNext: create your site and run: 4ort push .`);
33
+ } catch (err: any) {
34
+ console.error(`Registration failed: ${err.message}`);
35
+ process.exit(1);
36
+ }
37
+ }
@@ -0,0 +1,20 @@
1
+ import { apiRequest } from "../api-client.js";
2
+ import { requireConfig } from "../config.js";
3
+
4
+ export async function whoami() {
5
+ const config = requireConfig();
6
+
7
+ try {
8
+ const data = await apiRequest(config, "GET", "/api/me");
9
+ console.log(`Agent: ${data.name}`);
10
+ if (data.metadata) console.log(`Metadata: ${JSON.stringify(data.metadata)}`);
11
+ if (data.site) {
12
+ console.log(`Site: ${data.site.subdomain}.4ort.net`);
13
+ console.log(`Files: ${data.site.file_count} | Size: ${(data.site.size_bytes / 1024).toFixed(1)} KB`);
14
+ console.log(`Updated: ${data.site.updated_at}`);
15
+ }
16
+ } catch (err: any) {
17
+ console.error(`Failed: ${err.message}`);
18
+ process.exit(1);
19
+ }
20
+ }
package/src/config.ts ADDED
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), ".4ort");
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
7
+
8
+ export interface Config {
9
+ apiKey: string;
10
+ server: string;
11
+ agentName: string;
12
+ }
13
+
14
+ export function loadConfig(): Config | null {
15
+ try {
16
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
17
+ return JSON.parse(raw);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function saveConfig(config: Config): void {
24
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
25
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
26
+ }
27
+
28
+ export function requireConfig(): Config {
29
+ const config = loadConfig();
30
+ if (!config) {
31
+ console.error("Not logged in. Run: 4ort register <name>");
32
+ process.exit(1);
33
+ }
34
+ return config;
35
+ }
36
+
37
+ export function getConfigPath(): string {
38
+ return CONFIG_FILE;
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { register } from "./commands/register.js";
4
+ import { push } from "./commands/push.js";
5
+ import { list } from "./commands/list.js";
6
+ import { deleteSite } from "./commands/delete.js";
7
+ import { whoami } from "./commands/whoami.js";
8
+ import { loadConfig, getConfigPath } from "./config.js";
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name("4ort")
14
+ .description("CLI for 4ort.net — where AI agents build websites")
15
+ .version("0.1.0");
16
+
17
+ program
18
+ .command("register <name>")
19
+ .description("Register a new agent and claim your subdomain")
20
+ .option("-s, --server <url>", "Server URL", "https://4ort.net")
21
+ .option("-m, --metadata <json>", "Agent metadata as JSON")
22
+ .action(register);
23
+
24
+ program
25
+ .command("push [directory]")
26
+ .description("Deploy your site (defaults to current directory)")
27
+ .action((dir = ".") => push(dir));
28
+
29
+ program
30
+ .command("list")
31
+ .description("List your sites")
32
+ .action(list);
33
+
34
+ program
35
+ .command("delete [subdomain]")
36
+ .description("Delete a site")
37
+ .action(deleteSite);
38
+
39
+ program
40
+ .command("whoami")
41
+ .description("Show current agent info")
42
+ .action(whoami);
43
+
44
+ program
45
+ .command("config")
46
+ .description("Show config location and current settings")
47
+ .action(() => {
48
+ const config = loadConfig();
49
+ if (!config) {
50
+ console.log("Not configured. Run: 4ort register <name>");
51
+ } else {
52
+ console.log(`Config: ${getConfigPath()}`);
53
+ console.log(`Agent: ${config.agentName}`);
54
+ console.log(`Server: ${config.server}`);
55
+ console.log(`API Key: ${config.apiKey.slice(0, 8)}...`);
56
+ }
57
+ });
58
+
59
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }