workstation.md 0.1.1 → 0.2.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.
@@ -1,14 +1,2 @@
1
1
  #!/usr/bin/env node
2
- declare const API_URL: string;
3
- interface WorkstationInfo {
4
- id: string;
5
- host: string;
6
- port: number;
7
- web: string;
8
- }
9
- declare function apiRequest(path: string, options?: RequestInit): Promise<any>;
10
- declare function create(args: string[]): Promise<void>;
11
- declare function destroy(wsId: string): Promise<void>;
12
- declare function list(): Promise<void>;
13
- declare function usage(): void;
14
- declare function main(): Promise<void>;
2
+ export {};
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
2
+ import { execSync } from "child_process";
3
+ import { writeFileSync, mkdtempSync, readFileSync } from "fs";
4
+ import { tmpdir } from "os";
5
+ import { join } from "path";
3
6
  const API_URL = process.env.WORKSTATION_API_URL || "https://api.workstation.md";
4
7
  async function apiRequest(path, options = {}) {
5
8
  const res = await fetch(`${API_URL}${path}`, {
@@ -16,6 +19,42 @@ async function apiRequest(path, options = {}) {
16
19
  }
17
20
  return res.json();
18
21
  }
22
+ function findKeyPath(args) {
23
+ for (let i = 0; i < args.length; i++) {
24
+ if (args[i] === "--key" && args[i + 1]) {
25
+ return args[i + 1];
26
+ }
27
+ }
28
+ // Default: try common key paths
29
+ const home = process.env.HOME || "~";
30
+ const defaults = [
31
+ join(home, ".ssh", "id_ed25519"),
32
+ join(home, ".ssh", "id_rsa"),
33
+ ];
34
+ for (const p of defaults) {
35
+ try {
36
+ readFileSync(p);
37
+ return p;
38
+ }
39
+ catch { }
40
+ }
41
+ console.error("No SSH private key found. Use --key <path> to specify.");
42
+ process.exit(1);
43
+ }
44
+ function signWithSSHKey(keyPath, data) {
45
+ const tmp = mkdtempSync(join(tmpdir(), "ws-"));
46
+ const dataPath = join(tmp, "data");
47
+ writeFileSync(dataPath, data);
48
+ try {
49
+ execSync(`ssh-keygen -Y sign -f "${keyPath}" -n workstation.md "${dataPath}"`, { stdio: ["pipe", "pipe", "pipe"] });
50
+ const sigPath = dataPath + ".sig";
51
+ return readFileSync(sigPath, "utf-8");
52
+ }
53
+ catch (e) {
54
+ console.error("Failed to sign with SSH key:", e.stderr?.toString() || e.message);
55
+ process.exit(1);
56
+ }
57
+ }
19
58
  async function create(args) {
20
59
  let pubkey;
21
60
  for (let i = 0; i < args.length; i++) {
@@ -38,6 +77,15 @@ async function destroy(wsId) {
38
77
  const result = await apiRequest(`/${wsId}`, { method: "DELETE" });
39
78
  console.log(JSON.stringify(result, null, 2));
40
79
  }
80
+ async function extend(wsId, args) {
81
+ const keyPath = findKeyPath(args);
82
+ const signature = signWithSSHKey(keyPath, wsId);
83
+ const result = await apiRequest(`/${wsId}/extend`, {
84
+ method: "POST",
85
+ body: JSON.stringify({ signature }),
86
+ });
87
+ console.log(JSON.stringify(result, null, 2));
88
+ }
41
89
  async function list() {
42
90
  const items = await apiRequest("/list");
43
91
  if (items.length === 0) {
@@ -45,13 +93,17 @@ async function list() {
45
93
  return;
46
94
  }
47
95
  for (const ws of items) {
48
- console.log(`${ws.id}\tssh -p ${ws.port} root@${ws.host}\t${ws.web}`);
96
+ const ttl = ws.expires - Math.floor(Date.now() / 1000);
97
+ const hours = Math.max(0, Math.floor(ttl / 3600));
98
+ const mins = Math.max(0, Math.floor((ttl % 3600) / 60));
99
+ console.log(`${ws.id}\tssh -p ${ws.port} root@${ws.host}\t${ws.web}\t${hours}h${mins}m remaining`);
49
100
  }
50
101
  }
51
102
  function usage() {
52
103
  console.log(`Usage:
53
- workstation create --pubkey <public_key> Create a new workstation
104
+ workstation create --pubkey <public_key> Create a new workstation (24h TTL)
54
105
  workstation <id> destroy Destroy a workstation
106
+ workstation <id> extend [--key <path>] Extend TTL by 24h (proves key ownership)
55
107
  workstation list List active workstations`);
56
108
  }
57
109
  async function main() {
@@ -70,6 +122,9 @@ async function main() {
70
122
  else if (args.length >= 2 && args[1] === "destroy") {
71
123
  await destroy(command);
72
124
  }
125
+ else if (args.length >= 2 && args[1] === "extend") {
126
+ await extend(command, args.slice(2));
127
+ }
73
128
  else {
74
129
  usage();
75
130
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workstation.md",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Cloud Linux workstations for AI agents",
5
5
  "type": "module",
6
6
  "bin": {