agent-factorio 0.3.4 → 0.4.1

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,11 +1,31 @@
1
1
  /**
2
- * agent-factorio login — Connect to hub + join organization (with email verification)
2
+ * agent-factorio login — Browser-based authentication (like Claude Code /login)
3
3
  */
4
4
  import { ask, choose } from "../lib/prompt.mjs";
5
5
  import { readGlobalConfig, upsertOrg } from "../lib/config.mjs";
6
6
  import { apiCall, checkHub } from "../lib/api.mjs";
7
7
  import { success, error, info, dim } from "../lib/log.mjs";
8
8
 
9
+ /**
10
+ * Try to open a URL in the default browser
11
+ * @param {string} url
12
+ */
13
+ async function openBrowser(url) {
14
+ const { exec } = await import("child_process");
15
+ const { platform } = await import("os");
16
+
17
+ const cmd =
18
+ platform() === "darwin"
19
+ ? `open "${url}"`
20
+ : platform() === "win32"
21
+ ? `start "" "${url}"`
22
+ : `xdg-open "${url}"`;
23
+
24
+ return new Promise((resolve) => {
25
+ exec(cmd, (err) => resolve(!err));
26
+ });
27
+ }
28
+
9
29
  /**
10
30
  * Poll verification status until verified or expired
11
31
  * @param {string} hubUrl
@@ -23,7 +43,7 @@ async function waitForVerification(hubUrl, loginToken) {
23
43
  });
24
44
 
25
45
  if (res.status === 410) {
26
- throw new Error("Verification link expired. Please try again.");
46
+ throw new Error("Login session expired. Please try again.");
27
47
  }
28
48
 
29
49
  if (!res.ok) {
@@ -34,11 +54,10 @@ async function waitForVerification(hubUrl, loginToken) {
34
54
  return { userId: res.data.userId, email: res.data.email };
35
55
  }
36
56
 
37
- // Wait before next poll
38
57
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
39
58
  }
40
59
 
41
- throw new Error("Verification timed out. Please try again.");
60
+ throw new Error("Login timed out. Please try again.");
42
61
  }
43
62
 
44
63
  const DEFAULT_HUB_URL = "https://agent-factorio.vercel.app";
@@ -54,45 +73,46 @@ export async function loginCommand(options = {}) {
54
73
  process.exit(1);
55
74
  }
56
75
 
57
- // 2. Email input
58
- const email = await ask("Your email (used as your identifier)");
59
- if (!email) {
60
- error("Email is required.");
76
+ // 1. Initialize browser login session
77
+ const initRes = await apiCall(hubUrl, "/api/cli/login", {
78
+ body: { action: "init-browser-login" },
79
+ });
80
+
81
+ if (!initRes.ok) {
82
+ error(`Failed to initialize login: ${initRes.data?.error || "Unknown error"}`);
61
83
  process.exit(1);
62
84
  }
63
85
 
64
- // 3. Send verification email
65
- info("Sending verification email...");
66
- const sendRes = await apiCall(hubUrl, "/api/cli/login", {
67
- body: { action: "send-verification", email },
68
- });
86
+ const { loginToken, loginUrl } = initRes.data;
69
87
 
70
- if (!sendRes.ok) {
71
- error(`Failed to send verification email: ${sendRes.data?.error || "Unknown error"}`);
72
- process.exit(1);
88
+ // 2. Open browser
89
+ const opened = await openBrowser(loginUrl);
90
+ if (!opened) {
91
+ console.log();
92
+ info("Browser didn't open? Use the url below to sign in:");
93
+ console.log(` ${loginUrl}`);
73
94
  }
74
95
 
75
- const { loginToken } = sendRes.data;
76
- success("Verification email sent!");
77
- info("Check your inbox and click the verification link.");
78
- dim("Waiting for verification...");
96
+ console.log();
97
+ dim("Waiting for sign-in...");
79
98
 
80
- // 4. Poll for verification
81
- let userId;
99
+ // 3. Poll for verification
100
+ let userId, email;
82
101
  try {
83
102
  const result = await waitForVerification(hubUrl, loginToken);
84
103
  userId = result.userId;
104
+ email = result.email;
85
105
  } catch (err) {
86
106
  error(err.message);
87
107
  process.exit(1);
88
108
  }
89
109
 
90
- success("Email verified!");
110
+ success(`Logged in as ${email}`);
91
111
 
92
- // 5. Name input
112
+ // 4. Name input
93
113
  const memberName = await ask("Your name (displayed in the org)", "CLI User");
94
114
 
95
- // 6. Create or Join
115
+ // 5. Create or Join
96
116
  const { index: actionIdx } = await choose("Create or join an organization?", [
97
117
  "Create new",
98
118
  "Join existing (invite code)",
@@ -116,7 +136,7 @@ export async function loginCommand(options = {}) {
116
136
  }
117
137
 
118
138
  const { orgId, orgName: name, inviteCode, memberId, authToken } = res.data;
119
- upsertOrg({ hubUrl, orgId, orgName: name, inviteCode, memberName, email, memberId, userId, authToken });
139
+ upsertOrg({ hubUrl, orgId, orgName: name, inviteCode, memberName, email, memberId, userId, authToken }, { setAsDefault: true });
120
140
 
121
141
  success(`Created "${name}" (${orgId})`);
122
142
  info(`Invite code: ${inviteCode} — share with your team!`);
@@ -138,10 +158,10 @@ export async function loginCommand(options = {}) {
138
158
  }
139
159
 
140
160
  const { orgId, orgName, memberId, authToken } = res.data;
141
- upsertOrg({ hubUrl, orgId, orgName, inviteCode: inviteCode.toUpperCase(), memberName, email, memberId, userId, authToken });
161
+ upsertOrg({ hubUrl, orgId, orgName, inviteCode: inviteCode.toUpperCase(), memberName, email, memberId, userId, authToken }, { setAsDefault: true });
142
162
 
143
163
  success(`Joined "${orgName}" (${orgId})`);
144
164
  }
145
165
 
146
- console.log("\nLogged in! Run `agent-factorio push` in any project to register an agent.");
166
+ console.log("\nLogin successful! Run `agent-factorio push` in any project to register an agent.");
147
167
  }
package/commands/org.mjs CHANGED
@@ -89,7 +89,7 @@ export async function orgCreateCommand(name) {
89
89
  memberId,
90
90
  userId: org.userId,
91
91
  authToken,
92
- });
92
+ }, { setAsDefault: true });
93
93
 
94
94
  success(`Created "${createdName}" (${orgId})`);
95
95
  info(`Invite code: ${inviteCode} — share with your team!`);
@@ -137,7 +137,7 @@ export async function orgJoinCommand(code) {
137
137
  memberId,
138
138
  userId: org.userId,
139
139
  authToken,
140
- });
140
+ }, { setAsDefault: true });
141
141
 
142
142
  success(`Joined "${orgName}" (${orgId})`);
143
143
  }
package/lib/config.mjs CHANGED
@@ -60,7 +60,7 @@ export function getDefaultOrg() {
60
60
  * Add or update an organization in global config
61
61
  * @param {OrgEntry} org
62
62
  */
63
- export function upsertOrg(org) {
63
+ export function upsertOrg(org, { setAsDefault = false } = {}) {
64
64
  const config = readGlobalConfig() || { organizations: [] };
65
65
  const idx = config.organizations.findIndex(
66
66
  (o) => o.orgId === org.orgId && o.hubUrl === org.hubUrl
@@ -70,7 +70,7 @@ export function upsertOrg(org) {
70
70
  } else {
71
71
  config.organizations.push(org);
72
72
  }
73
- if (!config.defaultOrg) {
73
+ if (!config.defaultOrg || setAsDefault) {
74
74
  config.defaultOrg = org.orgId;
75
75
  }
76
76
  writeGlobalConfig(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-factorio",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "CLI for AgentFactorio — AI Agent Fleet Management hub",
5
5
  "type": "module",
6
6
  "bin": {