@epic-cloudcontrol/daemon 0.3.0 → 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/dist/cli.js +88 -90
- package/dist/service-manager.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -27,113 +27,111 @@ program
|
|
|
27
27
|
// ── login ─────────────────────────────────────────────
|
|
28
28
|
program
|
|
29
29
|
.command("login")
|
|
30
|
-
.description("Log in
|
|
31
|
-
.option("--email <email>", "Log in with email (syncs all companies automatically)")
|
|
32
|
-
.option("--profile <name>", "Profile name for single-key setup", "default")
|
|
30
|
+
.description("Log in via browser (device code flow)")
|
|
33
31
|
.option("--api-url <url>", "CloudControl API URL")
|
|
34
|
-
.option("--api-key <key>", "API key (
|
|
32
|
+
.option("--api-key <key>", "Manual API key setup (skip browser)")
|
|
33
|
+
.option("--no-browser", "Don't open browser automatically (print URL instead)")
|
|
35
34
|
.option("--name <name>", "Worker name")
|
|
35
|
+
.option("--profile <name>", "Profile name for manual key setup", "default")
|
|
36
36
|
.action(async (opts) => {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
apiUrl = await ask("CloudControl API URL", existing?.apiUrl || "https://cloudcontrol.onrender.com");
|
|
46
|
-
}
|
|
47
|
-
// Email login mode — sync all companies
|
|
48
|
-
const email = opts.email || await ask("Email (or leave blank for API key setup)");
|
|
49
|
-
if (email) {
|
|
50
|
-
const password = await ask("Password");
|
|
37
|
+
const existing = loadProfile();
|
|
38
|
+
const apiUrl = opts.apiUrl || existing?.apiUrl || "https://cloudcontrol.onrender.com";
|
|
39
|
+
const workerName = opts.name || existing?.workerName || `worker-${os.hostname()}`;
|
|
40
|
+
// Manual API key mode (backwards compat)
|
|
41
|
+
if (opts.apiKey) {
|
|
42
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
const ask = (q, def) => new Promise((r) => rl.question(def ? `${q} [${def}]: ` : `${q}: `, (a) => r(a.trim() || def || "")));
|
|
44
|
+
const teamName = await ask("Company name", opts.profile);
|
|
51
45
|
rl.close();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
saveProfile({ apiUrl, apiKey: opts.apiKey, workerName, teamName }, opts.profile);
|
|
47
|
+
console.log(`\nProfile "${opts.profile}" saved.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Device code flow
|
|
51
|
+
console.log("\nRequesting device code...");
|
|
52
|
+
let code;
|
|
53
|
+
let deviceUrl;
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${apiUrl}/api/auth/device`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "content-type": "application/json" },
|
|
58
|
+
body: JSON.stringify({ action: "create" }),
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok)
|
|
61
|
+
throw new Error(`HTTP ${res.status}`);
|
|
62
|
+
const data = await res.json();
|
|
63
|
+
code = data.code;
|
|
64
|
+
deviceUrl = data.url;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`Failed to connect to ${apiUrl}: ${err.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Open browser
|
|
71
|
+
const fullUrl = `${deviceUrl}?code=${code}`;
|
|
72
|
+
if (opts.browser !== false) {
|
|
73
|
+
console.log("\nOpening browser for authentication...\n");
|
|
74
|
+
try {
|
|
75
|
+
const { exec } = await import("child_process");
|
|
76
|
+
const cmd = process.platform === "darwin" ? `open "${fullUrl}"`
|
|
77
|
+
: process.platform === "win32" ? `start "" "${fullUrl}"`
|
|
78
|
+
: `xdg-open "${fullUrl}"`;
|
|
79
|
+
exec(cmd);
|
|
55
80
|
}
|
|
56
|
-
|
|
81
|
+
catch {
|
|
82
|
+
// Browser open failed — user will use the URL
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(`If the browser didn't open, go to:`);
|
|
86
|
+
console.log(` ${deviceUrl}\n`);
|
|
87
|
+
console.log(`Enter code: ${code}\n`);
|
|
88
|
+
console.log("Waiting for authorization...");
|
|
89
|
+
// Poll until authorized or expired
|
|
90
|
+
const pollInterval = 5000;
|
|
91
|
+
const maxAttempts = 120; // 10 minutes at 5s intervals
|
|
92
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
93
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
57
94
|
try {
|
|
58
|
-
const res = await fetch(`${apiUrl}/api/auth/
|
|
95
|
+
const res = await fetch(`${apiUrl}/api/auth/device`, {
|
|
59
96
|
method: "POST",
|
|
60
97
|
headers: { "content-type": "application/json" },
|
|
61
|
-
body: JSON.stringify({
|
|
98
|
+
body: JSON.stringify({ action: "poll", code }),
|
|
62
99
|
});
|
|
63
100
|
if (!res.ok) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
if (res.status === 404) {
|
|
102
|
+
console.error("\nCode expired. Run 'cloudcontrol login' again.");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
67
106
|
}
|
|
68
107
|
const data = await res.json();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(`Found ${data.teams.length} company(s):\n`);
|
|
72
|
-
for (const team of data.teams) {
|
|
73
|
-
// Generate slug from team name
|
|
74
|
-
const slug = team.teamName
|
|
75
|
-
.toLowerCase()
|
|
76
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
77
|
-
.replace(/^-|-$/g, "");
|
|
78
|
-
saveProfile({
|
|
79
|
-
apiUrl,
|
|
80
|
-
apiKey: team.apiKey,
|
|
81
|
-
workerName,
|
|
82
|
-
teamName: team.teamName,
|
|
83
|
-
}, slug);
|
|
84
|
-
console.log(` ✓ ${team.teamName} → profile "${slug}" (${team.role})`);
|
|
108
|
+
if (data.status === "pending") {
|
|
109
|
+
continue;
|
|
85
110
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
apiKey:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
if (data.status === "authorized" && data.user && data.teams) {
|
|
112
|
+
console.log(`\n✓ Logged in as ${data.user.email}`);
|
|
113
|
+
console.log(` Found ${data.teams.length} company(s):\n`);
|
|
114
|
+
for (const team of data.teams) {
|
|
115
|
+
const slug = team.teamName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
116
|
+
saveProfile({ apiUrl, apiKey: team.apiKey, workerName, teamName: team.teamName }, slug);
|
|
117
|
+
console.log(` ✓ ${team.teamName} → profile "${slug}" (${team.role})`);
|
|
118
|
+
}
|
|
119
|
+
// Save first team as default
|
|
120
|
+
if (data.teams.length > 0) {
|
|
121
|
+
const first = data.teams[0];
|
|
122
|
+
saveProfile({ apiUrl, apiKey: first.apiKey, workerName, teamName: first.teamName });
|
|
123
|
+
}
|
|
124
|
+
console.log(`\n${data.teams.length} profile(s) saved to ${getConfigDir()}/`);
|
|
125
|
+
console.log("Run 'cloudcontrol start --all' to begin processing tasks.");
|
|
126
|
+
return;
|
|
95
127
|
}
|
|
96
|
-
console.log(`\n${data.teams.length} profile(s) saved to ${getConfigDir()}/`);
|
|
97
|
-
console.log("Run 'cloudcontrol profiles' to see them.");
|
|
98
|
-
console.log("Run 'cloudcontrol start --profile <name>' to start working.");
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
console.error(`Error: ${err.message}`);
|
|
102
|
-
process.exit(1);
|
|
103
128
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Manual API key mode (original flow)
|
|
107
|
-
const profileName = opts.profile;
|
|
108
|
-
let apiKey = opts.apiKey;
|
|
109
|
-
let workerName = opts.name;
|
|
110
|
-
let teamName;
|
|
111
|
-
const existing = loadProfile(profileName);
|
|
112
|
-
if (!teamName)
|
|
113
|
-
teamName = await ask("Company name", existing?.teamName || profileName);
|
|
114
|
-
if (!apiKey)
|
|
115
|
-
apiKey = await ask("API Key (cc_...)", existing?.apiKey);
|
|
116
|
-
if (!workerName)
|
|
117
|
-
workerName = await ask("Worker name", existing?.workerName || `worker-${os.hostname()}`);
|
|
118
|
-
rl.close();
|
|
119
|
-
if (!apiKey) {
|
|
120
|
-
console.error("Error: API key is required.");
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
console.log("\nVerifying connection...");
|
|
124
|
-
try {
|
|
125
|
-
const res = await fetch(`${apiUrl}/api/health`);
|
|
126
|
-
if (!res.ok)
|
|
127
|
-
throw new Error(`HTTP ${res.status}`);
|
|
128
|
-
console.log("Connected to CloudControl.");
|
|
129
|
-
}
|
|
130
|
-
catch (err) {
|
|
131
|
-
console.error(`Warning: Could not reach ${apiUrl} — ${err.message}`);
|
|
129
|
+
catch {
|
|
130
|
+
// Poll error — retry
|
|
132
131
|
}
|
|
133
|
-
saveProfile({ apiUrl, apiKey, workerName, teamName }, profileName);
|
|
134
|
-
console.log(`\nProfile "${profileName}" saved to ${getConfigDir()}/`);
|
|
135
|
-
console.log("Run 'cloudcontrol start' to begin processing tasks.");
|
|
136
132
|
}
|
|
133
|
+
console.error("\nAuthorization timed out. Run 'cloudcontrol login' again.");
|
|
134
|
+
process.exit(1);
|
|
137
135
|
});
|
|
138
136
|
// ── profiles ──────────────────────────────────────────
|
|
139
137
|
program
|
package/dist/service-manager.js
CHANGED
|
@@ -59,7 +59,7 @@ ${argStrings}
|
|
|
59
59
|
<key>EnvironmentVariables</key>
|
|
60
60
|
<dict>
|
|
61
61
|
<key>PATH</key>
|
|
62
|
-
<string
|
|
62
|
+
<string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin"}</string>
|
|
63
63
|
<key>HOME</key>
|
|
64
64
|
<string>${os.homedir()}</string>
|
|
65
65
|
</dict>
|