agent-factorio 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # agent-factorio
2
2
 
3
- CLI for [AgentFactorio](https://github.com/your-org/agent-factorio) — AI Agent Fleet Management hub.
3
+ CLI for [AgentFactorio](https://github.com/gmuffiness/agent-factorio) — All your team's agents, one place.
4
4
 
5
- Register and manage your AI agents from any project.
5
+ Register and manage your AI agents (Claude Code, Cursor, Codex, etc.) from any project.
6
6
 
7
7
  ## Install
8
8
 
package/bin.js CHANGED
@@ -4,11 +4,24 @@
4
4
  * AgentFactorio CLI — register and manage agents from any project
5
5
  *
6
6
  * Usage:
7
- * npx agent-factorio login # Connect to hub + join organization
8
- * npx agent-factorio push # Push agent config to hub
9
- * npx agent-factorio status # Show registration status
10
- * npx agent-factorio whoami # Show login info
11
- * npx agent-factorio logout # Remove global config
7
+ * npx agent-factorio login # Connect to hub + join organization
8
+ * npx agent-factorio push # Push agent config to hub
9
+ * npx agent-factorio status # Show registration status
10
+ * npx agent-factorio whoami # Show login info
11
+ * npx agent-factorio logout # Remove global config
12
+ * npx agent-factorio connect # Poll hub and relay messages
13
+ *
14
+ * npx agent-factorio org list # List organizations
15
+ * npx agent-factorio org create # Create a new organization
16
+ * npx agent-factorio org join # Join via invite code
17
+ * npx agent-factorio org switch # Change default organization
18
+ * npx agent-factorio org info # Show current org details
19
+ *
20
+ * npx agent-factorio agent list # List agents in current org
21
+ * npx agent-factorio agent info # Show agent details
22
+ * npx agent-factorio agent edit # Edit agent properties
23
+ * npx agent-factorio agent pull # Sync hub config to local
24
+ * npx agent-factorio agent delete # Delete an agent
12
25
  */
13
26
 
14
27
  import { readFileSync } from "fs";
@@ -21,6 +34,20 @@ import { statusCommand } from "./commands/status.mjs";
21
34
  import { whoamiCommand } from "./commands/whoami.mjs";
22
35
  import { logoutCommand } from "./commands/logout.mjs";
23
36
  import { connectCommand } from "./commands/connect.mjs";
37
+ import {
38
+ orgListCommand,
39
+ orgCreateCommand,
40
+ orgJoinCommand,
41
+ orgSwitchCommand,
42
+ orgInfoCommand,
43
+ } from "./commands/org.mjs";
44
+ import {
45
+ agentListCommand,
46
+ agentInfoCommand,
47
+ agentEditCommand,
48
+ agentPullCommand,
49
+ agentDeleteCommand,
50
+ } from "./commands/agent.mjs";
24
51
 
25
52
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
53
  const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf-8"));
@@ -32,6 +59,8 @@ program
32
59
  .description("AgentFactorio CLI — AI Agent Fleet Management")
33
60
  .version(pkg.version);
34
61
 
62
+ // --- Existing top-level commands ---
63
+
35
64
  program
36
65
  .command("login")
37
66
  .description("Connect to an AgentFactorio hub and join an organization")
@@ -62,4 +91,77 @@ program
62
91
  .description("Poll hub and relay messages to local OpenClaw Gateway")
63
92
  .action(connectCommand);
64
93
 
94
+ // --- org subcommands ---
95
+
96
+ const org = program
97
+ .command("org")
98
+ .description("Manage organizations");
99
+
100
+ org
101
+ .command("list")
102
+ .description("List all organizations you belong to")
103
+ .action(orgListCommand);
104
+
105
+ org
106
+ .command("create")
107
+ .description("Create a new organization")
108
+ .argument("[name]", "Organization name")
109
+ .action(orgCreateCommand);
110
+
111
+ org
112
+ .command("join")
113
+ .description("Join an organization via invite code")
114
+ .argument("[inviteCode]", "Invite code")
115
+ .action(orgJoinCommand);
116
+
117
+ org
118
+ .command("switch")
119
+ .description("Change the default organization")
120
+ .action(orgSwitchCommand);
121
+
122
+ org
123
+ .command("info")
124
+ .description("Show details about the current organization")
125
+ .action(orgInfoCommand);
126
+
127
+ // --- agent subcommands ---
128
+
129
+ const agent = program
130
+ .command("agent")
131
+ .description("Manage agents");
132
+
133
+ agent
134
+ .command("list")
135
+ .description("List all agents in the current organization")
136
+ .action(agentListCommand);
137
+
138
+ agent
139
+ .command("info")
140
+ .description("Show agent details")
141
+ .argument("[id]", "Agent ID (defaults to local project agent)")
142
+ .action(agentInfoCommand);
143
+
144
+ agent
145
+ .command("edit")
146
+ .description("Edit agent properties")
147
+ .argument("[id]", "Agent ID (defaults to local project agent)")
148
+ .option("--name <name>", "Agent name")
149
+ .option("--vendor <vendor>", "Vendor (e.g. anthropic, openai)")
150
+ .option("--model <model>", "Model (e.g. claude-sonnet-4-20250514)")
151
+ .option("--description <desc>", "Description")
152
+ .option("--status <status>", "Status (active, idle, error)")
153
+ .action(agentEditCommand);
154
+
155
+ agent
156
+ .command("pull")
157
+ .description("Sync agent config from hub to local project")
158
+ .argument("[id]", "Agent ID (defaults to local project agent)")
159
+ .action(agentPullCommand);
160
+
161
+ agent
162
+ .command("delete")
163
+ .description("Delete an agent")
164
+ .argument("[id]", "Agent ID (defaults to local project agent)")
165
+ .action(agentDeleteCommand);
166
+
65
167
  program.parse();
@@ -0,0 +1,272 @@
1
+ /**
2
+ * agent-factorio agent — Agent management subcommands
3
+ */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { ask, confirm } from "../lib/prompt.mjs";
7
+ import { getDefaultOrg, readLocalConfig, writeLocalConfig, findProjectRoot } from "../lib/config.mjs";
8
+ import { authApiCall } from "../lib/api.mjs";
9
+ import { success, error, info, label, heading, dim } from "../lib/log.mjs";
10
+
11
+ /**
12
+ * Resolve agent ID from argument or local config
13
+ * @param {string} [idArg]
14
+ * @returns {string}
15
+ */
16
+ function resolveAgentId(idArg) {
17
+ if (idArg) return idArg;
18
+
19
+ const local = readLocalConfig();
20
+ if (local?.agentId) return local.agentId;
21
+
22
+ error("No agent ID specified and no local .agent-factorio/config.json found.");
23
+ info("Run this command with an agent ID, or run it from a project with a registered agent.");
24
+ process.exit(1);
25
+ }
26
+
27
+ /**
28
+ * agent list — List all agents in the current organization
29
+ */
30
+ export async function agentListCommand() {
31
+ const org = getDefaultOrg();
32
+ if (!org) {
33
+ error("Not logged in. Run `agent-factorio login` first.");
34
+ process.exit(1);
35
+ }
36
+
37
+ try {
38
+ const res = await authApiCall(`/api/cli/agents?orgId=${org.orgId}`);
39
+ if (!res.ok) {
40
+ error(res.data?.error || "Failed to fetch agents");
41
+ process.exit(1);
42
+ }
43
+
44
+ const { agents } = res.data;
45
+ if (!agents.length) {
46
+ info("No agents in this organization.");
47
+ info("Run `agent-factorio push` from a project to register an agent.");
48
+ return;
49
+ }
50
+
51
+ heading(`Agents in "${org.orgName}"`);
52
+ console.log("");
53
+
54
+ // Find max name length for alignment
55
+ const maxName = Math.max(...agents.map((a) => a.name.length), 4);
56
+
57
+ // Header
58
+ const header = ` ${"NAME".padEnd(maxName + 2)}${"VENDOR".padEnd(12)}${"MODEL".padEnd(20)}${"STATUS".padEnd(10)}DEPARTMENT`;
59
+ dim(header);
60
+
61
+ for (const agent of agents) {
62
+ const status = agent.status === "active" ? "\x1b[32mactive\x1b[0m" : agent.status;
63
+ console.log(
64
+ ` ${agent.name.padEnd(maxName + 2)}${(agent.vendor || "-").padEnd(12)}${(agent.model || "-").padEnd(20)}${(agent.status || "-").padEnd(10)}${agent.departmentName || "-"}`
65
+ );
66
+ }
67
+
68
+ console.log("");
69
+ dim(` ${agents.length} agent(s) total`);
70
+ } catch (err) {
71
+ error(err.message);
72
+ process.exit(1);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * agent info [id] — Show agent details
78
+ */
79
+ export async function agentInfoCommand(id) {
80
+ const agentId = resolveAgentId(id);
81
+
82
+ try {
83
+ const res = await authApiCall(`/api/cli/agents/${agentId}`);
84
+ if (!res.ok) {
85
+ error(res.data?.error || "Failed to fetch agent");
86
+ process.exit(1);
87
+ }
88
+
89
+ const a = res.data;
90
+
91
+ heading(`Agent: ${a.name}`);
92
+ console.log("");
93
+ label(" ID", a.id);
94
+ label(" Vendor", a.vendor);
95
+ label(" Model", a.model);
96
+ label(" Status", a.status);
97
+ label(" Description", a.description || "-");
98
+ label(" Department", a.departmentName);
99
+ label(" Runtime", a.runtimeType || "api");
100
+ label(" Last Active", a.lastActive || "-");
101
+ label(" Created", a.createdAt || "-");
102
+ label(" Monthly Cost", `$${a.monthlyCost ?? 0}`);
103
+ label(" Tokens Used", String(a.tokensUsed ?? 0));
104
+
105
+ if (a.skills?.length) {
106
+ console.log("");
107
+ heading(" Skills");
108
+ for (const s of a.skills) {
109
+ console.log(` - ${s.name} (${s.category})`);
110
+ }
111
+ }
112
+
113
+ if (a.mcpTools?.length) {
114
+ console.log("");
115
+ heading(" MCP Tools");
116
+ for (const t of a.mcpTools) {
117
+ const server = t.server ? ` [${t.server}]` : "";
118
+ console.log(` - ${t.name}${server}`);
119
+ }
120
+ }
121
+
122
+ if (a.resources?.length) {
123
+ console.log("");
124
+ heading(" Resources");
125
+ for (const r of a.resources) {
126
+ console.log(` - ${r.type}: ${r.name} (${r.url || "-"})`);
127
+ }
128
+ }
129
+
130
+ if (a.context?.length) {
131
+ console.log("");
132
+ heading(" Context");
133
+ for (const c of a.context) {
134
+ const source = c.sourceFile ? ` (${c.sourceFile})` : "";
135
+ const preview = c.content.length > 80 ? c.content.slice(0, 80) + "..." : c.content;
136
+ console.log(` - [${c.type}]${source} ${preview}`);
137
+ }
138
+ }
139
+ } catch (err) {
140
+ error(err.message);
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * agent edit [id] — Edit agent properties
147
+ */
148
+ export async function agentEditCommand(id, options) {
149
+ const agentId = resolveAgentId(id);
150
+
151
+ const updates = {};
152
+ if (options.name) updates.name = options.name;
153
+ if (options.vendor) updates.vendor = options.vendor;
154
+ if (options.model) updates.model = options.model;
155
+ if (options.description) updates.description = options.description;
156
+ if (options.status) updates.status = options.status;
157
+
158
+ if (Object.keys(updates).length === 0) {
159
+ error("No fields to update. Use --name, --vendor, --model, --description, or --status.");
160
+ process.exit(1);
161
+ }
162
+
163
+ try {
164
+ const res = await authApiCall(`/api/cli/agents/${agentId}`, {
165
+ method: "PATCH",
166
+ body: updates,
167
+ });
168
+
169
+ if (!res.ok) {
170
+ error(res.data?.error || "Failed to update agent");
171
+ process.exit(1);
172
+ }
173
+
174
+ success(res.data.message || "Agent updated successfully");
175
+
176
+ // Update local config if this is the local agent
177
+ const local = readLocalConfig();
178
+ if (local?.agentId === agentId) {
179
+ if (updates.name) local.agentName = updates.name;
180
+ if (updates.vendor) local.vendor = updates.vendor;
181
+ if (updates.model) local.model = updates.model;
182
+ writeLocalConfig(local);
183
+ dim(" Local config updated.");
184
+ }
185
+ } catch (err) {
186
+ error(err.message);
187
+ process.exit(1);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * agent pull [id] — Pull agent config from hub to local config
193
+ */
194
+ export async function agentPullCommand(id) {
195
+ const agentId = resolveAgentId(id);
196
+
197
+ try {
198
+ const res = await authApiCall(`/api/cli/agents/${agentId}`);
199
+ if (!res.ok) {
200
+ error(res.data?.error || "Failed to fetch agent");
201
+ process.exit(1);
202
+ }
203
+
204
+ const a = res.data;
205
+ const org = getDefaultOrg();
206
+
207
+ writeLocalConfig({
208
+ hubUrl: org.hubUrl,
209
+ orgId: a.orgId,
210
+ agentId: a.id,
211
+ agentName: a.name,
212
+ vendor: a.vendor,
213
+ model: a.model,
214
+ pushedAt: a.lastActive || new Date().toISOString(),
215
+ });
216
+
217
+ success(`Synced agent "${a.name}" to local config.`);
218
+ label(" Agent", a.name);
219
+ label(" Vendor", a.vendor);
220
+ label(" Model", a.model);
221
+ label(" Status", a.status);
222
+ } catch (err) {
223
+ error(err.message);
224
+ process.exit(1);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * agent delete [id] — Delete an agent
230
+ */
231
+ export async function agentDeleteCommand(id) {
232
+ const agentId = resolveAgentId(id);
233
+
234
+ // Fetch agent name for confirmation
235
+ try {
236
+ const infoRes = await authApiCall(`/api/cli/agents/${agentId}`);
237
+ const agentName = infoRes.ok ? infoRes.data.name : agentId;
238
+
239
+ const confirmed = await confirm(`Delete agent "${agentName}"? This cannot be undone`, false);
240
+ if (!confirmed) {
241
+ info("Cancelled.");
242
+ return;
243
+ }
244
+
245
+ const res = await authApiCall(`/api/cli/agents/${agentId}`, {
246
+ method: "DELETE",
247
+ });
248
+
249
+ if (!res.ok) {
250
+ error(res.data?.error || "Failed to delete agent");
251
+ process.exit(1);
252
+ }
253
+
254
+ success(res.data.message || "Agent deleted successfully");
255
+
256
+ // Remove local config if this is the local agent
257
+ const local = readLocalConfig();
258
+ if (local?.agentId === agentId) {
259
+ const root = findProjectRoot();
260
+ const configPath = path.join(root, ".agent-factorio", "config.json");
261
+ try {
262
+ fs.unlinkSync(configPath);
263
+ dim(" Local config removed.");
264
+ } catch {
265
+ // ignore
266
+ }
267
+ }
268
+ } catch (err) {
269
+ error(err.message);
270
+ process.exit(1);
271
+ }
272
+ }
@@ -46,7 +46,7 @@ export async function loginCommand() {
46
46
  const defaultUrl = existing?.organizations?.[0]?.hubUrl || "";
47
47
 
48
48
  // 1. Hub URL
49
- const hubUrl = await ask("AgentFactorio Hub URL", defaultUrl || "http://localhost:3000");
49
+ const hubUrl = await ask("AgentFactorio Hub URL", defaultUrl || "https://agent-factorio.vercel.app");
50
50
  if (!hubUrl) {
51
51
  error("Hub URL is required.");
52
52
  process.exit(1);
@@ -100,11 +100,11 @@ export async function loginCommand() {
100
100
 
101
101
  // 6. Create or Join
102
102
  const { index: actionIdx } = await choose("Create or join an organization?", [
103
- "Join existing (invite code)",
104
103
  "Create new",
104
+ "Join existing (invite code)",
105
105
  ]);
106
106
 
107
- if (actionIdx === 1) {
107
+ if (actionIdx === 0) {
108
108
  // Create new org
109
109
  const orgName = await ask("Organization name");
110
110
  if (!orgName) {
@@ -121,8 +121,8 @@ export async function loginCommand() {
121
121
  process.exit(1);
122
122
  }
123
123
 
124
- const { orgId, orgName: name, inviteCode, memberId } = res.data;
125
- upsertOrg({ hubUrl, orgId, orgName: name, inviteCode, memberName, email, memberId, userId });
124
+ const { orgId, orgName: name, inviteCode, memberId, authToken } = res.data;
125
+ upsertOrg({ hubUrl, orgId, orgName: name, inviteCode, memberName, email, memberId, userId, authToken });
126
126
 
127
127
  success(`Created "${name}" (${orgId})`);
128
128
  info(`Invite code: ${inviteCode} — share with your team!`);
@@ -143,8 +143,8 @@ export async function loginCommand() {
143
143
  process.exit(1);
144
144
  }
145
145
 
146
- const { orgId, orgName, memberId } = res.data;
147
- upsertOrg({ hubUrl, orgId, orgName, inviteCode: inviteCode.toUpperCase(), memberName, email, memberId, userId });
146
+ const { orgId, orgName, memberId, authToken } = res.data;
147
+ upsertOrg({ hubUrl, orgId, orgName, inviteCode: inviteCode.toUpperCase(), memberName, email, memberId, userId, authToken });
148
148
 
149
149
  success(`Joined "${orgName}" (${orgId})`);
150
150
  }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * agent-factorio org — Organization management subcommands
3
+ */
4
+ import { ask, choose } from "../lib/prompt.mjs";
5
+ import { readGlobalConfig, writeGlobalConfig, getDefaultOrg, upsertOrg } from "../lib/config.mjs";
6
+ import { apiCall, authApiCall } from "../lib/api.mjs";
7
+ import { success, error, info, label, heading, dim } from "../lib/log.mjs";
8
+
9
+ /**
10
+ * org list — List all organizations the user belongs to
11
+ */
12
+ export async function orgListCommand() {
13
+ try {
14
+ const res = await authApiCall("/api/cli/orgs");
15
+ if (!res.ok) {
16
+ error(res.data?.error || "Failed to fetch organizations");
17
+ process.exit(1);
18
+ }
19
+
20
+ const { organizations } = res.data;
21
+ if (!organizations.length) {
22
+ info("You are not a member of any organizations.");
23
+ info("Run `agent-factorio org create` or `agent-factorio org join` to get started.");
24
+ return;
25
+ }
26
+
27
+ const config = readGlobalConfig();
28
+ const defaultOrgId = config?.defaultOrg;
29
+
30
+ heading("Organizations");
31
+ console.log("");
32
+
33
+ for (const org of organizations) {
34
+ const isDefault = org.orgId === defaultOrgId;
35
+ const marker = isDefault ? " (default)" : "";
36
+ console.log(` ${org.orgName}${marker}`);
37
+ label(" ID", org.orgId);
38
+ label(" Role", org.role);
39
+ label(" Invite Code", org.inviteCode);
40
+ label(" Members", String(org.memberCount));
41
+ label(" Agents", String(org.agentCount));
42
+ console.log("");
43
+ }
44
+ } catch (err) {
45
+ error(err.message);
46
+ process.exit(1);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * org create <name> — Create a new organization
52
+ */
53
+ export async function orgCreateCommand(name) {
54
+ const org = getDefaultOrg();
55
+ if (!org) {
56
+ error("Not logged in. Run `agent-factorio login` first.");
57
+ process.exit(1);
58
+ }
59
+
60
+ const orgName = name || await ask("Organization name");
61
+ if (!orgName) {
62
+ error("Organization name is required.");
63
+ process.exit(1);
64
+ }
65
+
66
+ const res = await apiCall(org.hubUrl, "/api/cli/login", {
67
+ body: {
68
+ action: "create",
69
+ orgName,
70
+ memberName: org.memberName || "CLI User",
71
+ email: org.email,
72
+ userId: org.userId,
73
+ },
74
+ });
75
+
76
+ if (!res.ok) {
77
+ error(`Failed to create organization: ${res.data?.error || "Unknown error"}`);
78
+ process.exit(1);
79
+ }
80
+
81
+ const { orgId, orgName: createdName, inviteCode, memberId, authToken } = res.data;
82
+ upsertOrg({
83
+ hubUrl: org.hubUrl,
84
+ orgId,
85
+ orgName: createdName,
86
+ inviteCode,
87
+ memberName: org.memberName,
88
+ email: org.email,
89
+ memberId,
90
+ userId: org.userId,
91
+ authToken,
92
+ });
93
+
94
+ success(`Created "${createdName}" (${orgId})`);
95
+ info(`Invite code: ${inviteCode} — share with your team!`);
96
+ }
97
+
98
+ /**
99
+ * org join <inviteCode> — Join an organization via invite code
100
+ */
101
+ export async function orgJoinCommand(code) {
102
+ const org = getDefaultOrg();
103
+ if (!org) {
104
+ error("Not logged in. Run `agent-factorio login` first.");
105
+ process.exit(1);
106
+ }
107
+
108
+ const inviteCode = code || await ask("Invite code");
109
+ if (!inviteCode) {
110
+ error("Invite code is required.");
111
+ process.exit(1);
112
+ }
113
+
114
+ const res = await apiCall(org.hubUrl, "/api/cli/login", {
115
+ body: {
116
+ action: "join",
117
+ inviteCode,
118
+ memberName: org.memberName || "CLI User",
119
+ email: org.email,
120
+ userId: org.userId,
121
+ },
122
+ });
123
+
124
+ if (!res.ok) {
125
+ error(`Failed to join: ${res.data?.error || "Invalid invite code"}`);
126
+ process.exit(1);
127
+ }
128
+
129
+ const { orgId, orgName, memberId, authToken } = res.data;
130
+ upsertOrg({
131
+ hubUrl: org.hubUrl,
132
+ orgId,
133
+ orgName,
134
+ inviteCode: inviteCode.toUpperCase(),
135
+ memberName: org.memberName,
136
+ email: org.email,
137
+ memberId,
138
+ userId: org.userId,
139
+ authToken,
140
+ });
141
+
142
+ success(`Joined "${orgName}" (${orgId})`);
143
+ }
144
+
145
+ /**
146
+ * org switch — Interactively select the default organization
147
+ */
148
+ export async function orgSwitchCommand() {
149
+ const config = readGlobalConfig();
150
+ if (!config?.organizations?.length) {
151
+ error("No organizations found. Run `agent-factorio login` first.");
152
+ process.exit(1);
153
+ }
154
+
155
+ if (config.organizations.length === 1) {
156
+ info(`Only one organization: "${config.organizations[0].orgName}". Already set as default.`);
157
+ return;
158
+ }
159
+
160
+ const options = config.organizations.map((o) => {
161
+ const marker = o.orgId === config.defaultOrg ? " (current)" : "";
162
+ return `${o.orgName}${marker}`;
163
+ });
164
+
165
+ const { index } = await choose("Select default organization", options);
166
+ const selected = config.organizations[index];
167
+
168
+ config.defaultOrg = selected.orgId;
169
+ writeGlobalConfig(config);
170
+
171
+ success(`Default organization set to "${selected.orgName}" (${selected.orgId})`);
172
+ }
173
+
174
+ /**
175
+ * org info — Show details about the current default organization
176
+ */
177
+ export async function orgInfoCommand() {
178
+ const org = getDefaultOrg();
179
+ if (!org) {
180
+ error("Not logged in. Run `agent-factorio login` first.");
181
+ process.exit(1);
182
+ }
183
+
184
+ try {
185
+ const res = await authApiCall("/api/cli/orgs");
186
+ if (!res.ok) {
187
+ // Fallback to local config info
188
+ heading("Organization (local config)");
189
+ console.log("");
190
+ label(" Name", org.orgName);
191
+ label(" ID", org.orgId);
192
+ label(" Invite Code", org.inviteCode);
193
+ label(" Hub", org.hubUrl);
194
+ return;
195
+ }
196
+
197
+ const match = res.data.organizations.find((o) => o.orgId === org.orgId);
198
+ if (!match) {
199
+ info("Organization not found on hub. Showing local config.");
200
+ label(" Name", org.orgName);
201
+ label(" ID", org.orgId);
202
+ label(" Hub", org.hubUrl);
203
+ return;
204
+ }
205
+
206
+ heading("Organization");
207
+ console.log("");
208
+ label(" Name", match.orgName);
209
+ label(" ID", match.orgId);
210
+ label(" Role", match.role);
211
+ label(" Invite Code", match.inviteCode);
212
+ label(" Members", String(match.memberCount));
213
+ label(" Agents", String(match.agentCount));
214
+ label(" Hub", org.hubUrl);
215
+ } catch (err) {
216
+ error(err.message);
217
+ process.exit(1);
218
+ }
219
+ }
package/lib/api.mjs CHANGED
@@ -2,21 +2,25 @@
2
2
  * Hub API call helper
3
3
  */
4
4
 
5
+ import { getDefaultOrg } from "./config.mjs";
6
+
5
7
  /**
6
8
  * Make an API request to the AgentFactorio hub
7
9
  * @param {string} hubUrl - Base URL of the hub
8
10
  * @param {string} path - API path (e.g. "/api/cli/login")
9
- * @param {{ method?: string, body?: unknown }} [options]
11
+ * @param {{ method?: string, body?: unknown, authToken?: string }} [options]
10
12
  * @returns {Promise<{ ok: boolean, status: number, data: unknown }>}
11
13
  */
12
14
  export async function apiCall(hubUrl, path, options = {}) {
13
15
  const url = `${hubUrl.replace(/\/$/, "")}${path}`;
14
16
  const method = options.method || (options.body ? "POST" : "GET");
15
17
 
16
- const fetchOptions = {
17
- method,
18
- headers: { "Content-Type": "application/json" },
19
- };
18
+ const headers = { "Content-Type": "application/json" };
19
+ if (options.authToken) {
20
+ headers["Authorization"] = `Bearer ${options.authToken}`;
21
+ }
22
+
23
+ const fetchOptions = { method, headers };
20
24
 
21
25
  if (options.body) {
22
26
  fetchOptions.body = JSON.stringify(options.body);
@@ -33,6 +37,23 @@ export async function apiCall(hubUrl, path, options = {}) {
33
37
  return { ok: res.ok, status: res.status, data };
34
38
  }
35
39
 
40
+ /**
41
+ * Make an authenticated API call using the default org's hubUrl and authToken.
42
+ * @param {string} path - API path
43
+ * @param {{ method?: string, body?: unknown }} [options]
44
+ * @returns {Promise<{ ok: boolean, status: number, data: unknown }>}
45
+ */
46
+ export async function authApiCall(path, options = {}) {
47
+ const org = getDefaultOrg();
48
+ if (!org) {
49
+ throw new Error("Not logged in. Run `agent-factorio login` first.");
50
+ }
51
+ if (!org.authToken) {
52
+ throw new Error("Auth token missing. Run `agent-factorio login` again to get a token.");
53
+ }
54
+ return apiCall(org.hubUrl, path, { ...options, authToken: org.authToken });
55
+ }
56
+
36
57
  /**
37
58
  * Check if hub is reachable
38
59
  * @param {string} hubUrl
package/lib/config.mjs CHANGED
@@ -13,7 +13,7 @@ const LOCAL_CONFIG_NAME = "config.json";
13
13
  // --- Global config ---
14
14
 
15
15
  /**
16
- * @typedef {{ hubUrl: string, orgId: string, orgName: string, inviteCode: string, memberName?: string, email?: string, memberId?: string, userId?: string }} OrgEntry
16
+ * @typedef {{ hubUrl: string, orgId: string, orgName: string, inviteCode: string, memberName?: string, email?: string, memberId?: string, userId?: string, authToken?: string }} OrgEntry
17
17
  * @typedef {{ organizations: OrgEntry[], defaultOrg?: string }} GlobalConfig
18
18
  */
19
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-factorio",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for AgentFactorio — AI Agent Fleet Management hub",
5
5
  "type": "module",
6
6
  "bin": {