@indexnetwork/cli 0.8.0 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +107 -0
  2. package/dist/index.js +651 -30
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -73,6 +73,9 @@ View user profiles and trigger profile regeneration.
73
73
  index profile # Show your own profile
74
74
  index profile show <user-id> # Show another user's profile
75
75
  index profile sync # Regenerate your profile
76
+ index profile search <query> # Search profiles by name or keyword
77
+ index profile create # Generate profile from social links
78
+ index profile update <details> # Update profile details
76
79
  ```
77
80
 
78
81
  ### `index intent`
@@ -85,7 +88,11 @@ index intent list --archived # Include archived signals
85
88
  index intent list --limit 5 # Limit to 5 results
86
89
  index intent show <id> # Show full signal details
87
90
  index intent create "Looking for a CTO" # Create from natural language
91
+ index intent update <id> "revised text" # Update a signal (runs full pipeline)
88
92
  index intent archive <id> # Archive a signal
93
+ index intent link <id> <network-id> # Link a signal to a network
94
+ index intent unlink <id> <network-id> # Unlink a signal from a network
95
+ index intent links <id> # List networks linked to a signal
89
96
  ```
90
97
 
91
98
  ### `index opportunity`
@@ -99,6 +106,9 @@ index opportunity list --limit 5 # Limit results
99
106
  index opportunity show <id> # Show full details
100
107
  index opportunity accept <id> # Accept an opportunity
101
108
  index opportunity reject <id> # Reject an opportunity
109
+ index opportunity discover "query" # Discover opportunities by search
110
+ index opportunity discover --target <id> # Discover with a specific user
111
+ index opportunity discover --introduce <a> <b> # Introduce two users
102
112
  ```
103
113
 
104
114
  Status values: `pending`, `accepted`, `rejected`, `expired`.
@@ -112,11 +122,98 @@ index network list # List your networks
112
122
  index network create "My Network" # Create a network
113
123
  index network create "AI" --prompt "AI researchers" # Create with description
114
124
  index network show <id> # Show details and members
125
+ index network update <id> --title "New Name" # Update a network
126
+ index network delete <id> # Delete a network
115
127
  index network join <id> # Join a public network
116
128
  index network leave <id> # Leave a network
117
129
  index network invite <id> user@email # Invite a user by email
118
130
  ```
119
131
 
132
+ ### `index contact`
133
+
134
+ Manage your contacts. Add, list, remove, or import contacts.
135
+
136
+ ```bash
137
+ index contact list # List your contacts
138
+ index contact add user@email # Add a contact by email
139
+ index contact add user@email --name "Name" # Add with display name
140
+ index contact remove user@email # Remove a contact
141
+ index contact import --gmail # Import contacts from Gmail
142
+ ```
143
+
144
+ ### `index scrape`
145
+
146
+ Scrape and extract content from a URL.
147
+
148
+ ```bash
149
+ index scrape https://example.com # Scrape a URL
150
+ index scrape https://example.com --objective "..." # Scrape with focus
151
+ ```
152
+
153
+ ### `index sync`
154
+
155
+ Sync all user context (profile, networks, intents, contacts) to a local file.
156
+
157
+ ```bash
158
+ index sync # Sync to ~/.index/context.json
159
+ index sync --json # Output to stdout as JSON
160
+ ```
161
+
162
+ ## Examples: Opportunity Discovery
163
+
164
+ The `opportunity discover` command supports multiple modes for creating connections.
165
+
166
+ ### Scenario 1: Search-based discovery
167
+
168
+ Find people whose intents match a search query. The protocol runs HyDE-powered semantic search across your networks.
169
+
170
+ ```bash
171
+ # Find collaborators for a project
172
+ index opportunity discover "looking for an AI engineer with privacy expertise"
173
+
174
+ # Scope discovery to a specific network
175
+ index opportunity discover "need a React developer" --target <network-id>
176
+ ```
177
+
178
+ ### Scenario 2: Direct connection
179
+
180
+ Propose an opportunity with a specific person. Use when you already know who you want to connect with.
181
+
182
+ ```bash
183
+ # First, find the user
184
+ index profile search "Jane Smith"
185
+
186
+ # Then create a direct opportunity with them
187
+ index opportunity discover "collaborate on open-source LLM tooling" --target <user-id>
188
+ ```
189
+
190
+ ### Scenario 3: Introduction
191
+
192
+ Introduce two people you think should connect. You become the introducer — both parties see you as the connector.
193
+
194
+ ```bash
195
+ # Introduce two users to each other
196
+ index opportunity discover --introduce <user-id-a> <user-id-b>
197
+ ```
198
+
199
+ ### Scenario 4: Review and act
200
+
201
+ After discovery creates draft opportunities, review and accept/reject them.
202
+
203
+ ```bash
204
+ # List pending opportunities
205
+ index opportunity list --status pending
206
+
207
+ # See full details (reasoning, scores, mutual intents)
208
+ index opportunity show <id>
209
+
210
+ # Accept — starts a conversation thread
211
+ index opportunity accept <id>
212
+
213
+ # Or reject
214
+ index opportunity reject <id>
215
+ ```
216
+
120
217
  ## Options
121
218
 
122
219
  | Flag | Short | Description |
@@ -129,6 +226,13 @@ index network invite <id> user@email # Invite a user by email
129
226
  | `--status <status>` | | Filter opportunities by status |
130
227
  | `--limit <n>` | | Limit number of results |
131
228
  | `--prompt <text>` | `-p` | Network description (for `network create`) |
229
+ | `--title <text>` | | Network title (for `network update`) |
230
+ | `--name <name>` | | Display name (for `contact add`) |
231
+ | `--gmail` | | Import from Gmail (for `contact import`) |
232
+ | `--target <id>` | | Target user ID (for `opportunity discover`) |
233
+ | `--introduce <id>` | | Introduce two users (for `opportunity discover`) |
234
+ | `--objective <text>` | | Focus objective (for `scrape`) |
235
+ | `--json` | | Output raw JSON to stdout |
132
236
  | `--help` | `-h` | Show help |
133
237
  | `--version` | `-v` | Show version |
134
238
 
@@ -170,6 +274,9 @@ cli/
170
274
  intent.command.ts Intent (signal) subcommand handlers
171
275
  opportunity.command.ts Opportunity subcommand handlers
172
276
  network.command.ts Network subcommand handlers
277
+ contact.command.ts Contact subcommand handlers
278
+ scrape.command.ts URL scraping command handler
279
+ sync.command.ts Context sync command handler
173
280
  output/ Terminal formatting, colors, markdown renderer
174
281
  api.client.ts Typed HTTP client for the protocol API
175
282
  auth.store.ts Credential persistence (~/.index/credentials.json)
package/dist/index.js CHANGED
@@ -4,9 +4,9 @@
4
4
  import { execFile } from "node:child_process";
5
5
 
6
6
  // src/args.parser.ts
7
- var KNOWN_COMMANDS = new Set(["login", "logout", "profile", "intent", "opportunity", "network", "conversation", "help", "version"]);
8
- var OPPORTUNITY_SUBCOMMANDS = new Set(["list", "show", "accept", "reject"]);
9
- var NETWORK_SUBCOMMANDS = new Set(["list", "create", "show", "join", "leave", "invite"]);
7
+ var KNOWN_COMMANDS = new Set(["login", "logout", "profile", "intent", "opportunity", "network", "conversation", "contact", "scrape", "onboarding", "sync", "help", "version"]);
8
+ var OPPORTUNITY_SUBCOMMANDS = new Set(["list", "show", "accept", "reject", "discover"]);
9
+ var NETWORK_SUBCOMMANDS = new Set(["list", "create", "show", "join", "leave", "invite", "update", "delete"]);
10
10
  var CONVERSATION_SUBCOMMANDS = new Set(["list", "with", "show", "send", "stream", "sessions", "help"]);
11
11
  function parseArgs(args) {
12
12
  const result = {
@@ -62,6 +62,39 @@ function parseArgs(args) {
62
62
  } else if (arg === "--prompt" || arg === "-p") {
63
63
  result.prompt = args[i + 1];
64
64
  i += 2;
65
+ } else if (arg === "--json") {
66
+ result.json = true;
67
+ i++;
68
+ } else if (arg === "--name") {
69
+ result.name = args[i + 1];
70
+ i += 2;
71
+ } else if (arg === "--gmail") {
72
+ result.gmail = true;
73
+ i++;
74
+ } else if (arg === "--objective") {
75
+ result.objective = args[i + 1];
76
+ i += 2;
77
+ } else if (arg === "--target") {
78
+ result.target = args[i + 1];
79
+ i += 2;
80
+ } else if (arg === "--introduce") {
81
+ result.introduce = args[i + 1];
82
+ i += 2;
83
+ } else if (arg === "--linkedin") {
84
+ result.linkedin = args[i + 1];
85
+ i += 2;
86
+ } else if (arg === "--github") {
87
+ result.github = args[i + 1];
88
+ i += 2;
89
+ } else if (arg === "--twitter") {
90
+ result.twitter = args[i + 1];
91
+ i += 2;
92
+ } else if (arg === "--title") {
93
+ result.title = args[i + 1];
94
+ i += 2;
95
+ } else if (arg === "--details") {
96
+ result.details = args[i + 1];
97
+ i += 2;
65
98
  } else if (arg.startsWith("--")) {
66
99
  i++;
67
100
  } else {
@@ -73,7 +106,9 @@ function parseArgs(args) {
73
106
  const sub = positionals[0];
74
107
  if (sub && OPPORTUNITY_SUBCOMMANDS.has(sub)) {
75
108
  result.subcommand = sub;
76
- if (positionals[1]) {
109
+ if (sub === "discover") {
110
+ result.positionals = positionals.slice(1);
111
+ } else if (positionals[1]) {
77
112
  result.targetId = positionals[1];
78
113
  }
79
114
  }
@@ -88,8 +123,34 @@ function parseArgs(args) {
88
123
  }
89
124
  } else if (sub === "sync") {
90
125
  result.subcommand = "sync";
126
+ } else if (sub === "search") {
127
+ result.subcommand = "search";
128
+ result.positionals = positionals.slice(1);
129
+ } else if (sub === "create") {
130
+ result.subcommand = "create";
131
+ } else if (sub === "update") {
132
+ result.subcommand = "update";
133
+ result.positionals = positionals.slice(1);
91
134
  }
92
135
  }
136
+ if (result.command === "onboarding" && positionals.length > 0) {
137
+ const sub = positionals[0];
138
+ if (sub === "complete") {
139
+ result.subcommand = "complete";
140
+ }
141
+ }
142
+ if (result.command === "contact") {
143
+ if (positionals.length > 0) {
144
+ const sub = positionals[0];
145
+ if (["list", "add", "remove", "import"].includes(sub)) {
146
+ result.subcommand = sub;
147
+ result.positionals = positionals.slice(1);
148
+ }
149
+ }
150
+ }
151
+ if (result.command === "scrape") {
152
+ result.positionals = positionals;
153
+ }
93
154
  if (result.command === "intent") {
94
155
  parseIntentArgs(positionals, result);
95
156
  }
@@ -111,7 +172,7 @@ function parseArgs(args) {
111
172
  }
112
173
  return result;
113
174
  }
114
- var INTENT_SUBCOMMANDS = new Set(["list", "show", "create", "archive"]);
175
+ var INTENT_SUBCOMMANDS = new Set(["list", "show", "create", "archive", "update", "link", "unlink", "links"]);
115
176
  function parseIntentArgs(positionals, result) {
116
177
  if (positionals.length === 0)
117
178
  return;
@@ -123,6 +184,7 @@ function parseIntentArgs(positionals, result) {
123
184
  switch (result.subcommand) {
124
185
  case "show":
125
186
  case "archive":
187
+ case "links":
126
188
  result.intentId = rest[0];
127
189
  break;
128
190
  case "create":
@@ -130,6 +192,17 @@ function parseIntentArgs(positionals, result) {
130
192
  result.intentContent = rest.join(" ");
131
193
  }
132
194
  break;
195
+ case "update":
196
+ result.intentId = rest[0];
197
+ if (rest.length > 1) {
198
+ result.intentContent = rest.slice(1).join(" ");
199
+ }
200
+ break;
201
+ case "link":
202
+ case "unlink":
203
+ result.intentId = rest[0];
204
+ result.targetId = rest[1];
205
+ break;
133
206
  }
134
207
  }
135
208
 
@@ -265,7 +338,10 @@ class ApiClient {
265
338
  async listNetworks() {
266
339
  const res = await this.get("/api/indexes");
267
340
  const body = await res.json();
268
- return body.indexes;
341
+ return body.indexes.map((n) => ({
342
+ ...n,
343
+ joinPolicy: n.joinPolicy ?? n.permissions?.joinPolicy
344
+ }));
269
345
  }
270
346
  async createNetwork(title, prompt) {
271
347
  const res = await this.post("/api/indexes", {
@@ -278,7 +354,8 @@ class ApiClient {
278
354
  async getNetwork(id) {
279
355
  const res = await this.get(`/api/indexes/${id}`);
280
356
  const body = await res.json();
281
- return body.index;
357
+ const n = body.index;
358
+ return { ...n, joinPolicy: n.joinPolicy ?? n.permissions?.joinPolicy };
282
359
  }
283
360
  async getNetworkMembers(id) {
284
361
  const res = await this.get(`/api/indexes/${id}/members`);
@@ -350,6 +427,10 @@ class ApiClient {
350
427
  }
351
428
  return res;
352
429
  }
430
+ async callTool(toolName, query = {}) {
431
+ const res = await this.post(`/api/tools/${toolName}`, { query });
432
+ return await res.json();
433
+ }
353
434
  async get(path) {
354
435
  const res = await fetch(`${this.baseUrl}${path}`, {
355
436
  headers: {
@@ -910,6 +991,19 @@ function profileCard(data) {
910
991
  console.log(output);
911
992
  return output;
912
993
  }
994
+ function contactTable(contacts) {
995
+ if (contacts.length === 0) {
996
+ console.log(" No contacts yet.");
997
+ return;
998
+ }
999
+ const nameWidth = Math.max(6, ...contacts.map((c) => c.name.length));
1000
+ const header = ` ${"Name".padEnd(nameWidth)} Email`;
1001
+ console.log(`${BOLD}${header}${RESET}`);
1002
+ for (const c of contacts) {
1003
+ const ghost = c.isGhost ? ` ${DIM}(ghost)${RESET}` : "";
1004
+ console.log(` ${c.name.padEnd(nameWidth)} ${c.email}${ghost}`);
1005
+ }
1006
+ }
913
1007
  function sessionTable(sessions) {
914
1008
  if (sessions.length === 0) {
915
1009
  dim(" No chat sessions found.");
@@ -1034,10 +1128,12 @@ function opportunityTable(opportunities) {
1034
1128
  `);
1035
1129
  for (const opp of opportunities) {
1036
1130
  const shortId = opp.id.slice(0, 8);
1037
- const name = (opp.counterpartName ?? "Unknown").slice(0, nameW);
1131
+ const fallbackName = opp.actors?.[1]?.name ?? opp.actors?.find((a) => a.name)?.name;
1132
+ const name = (opp.counterpartName ?? fallbackName ?? "Unknown").slice(0, nameW);
1038
1133
  const category = (opp.interpretation?.category ?? "-").slice(0, catW);
1039
1134
  const st = opp.status.slice(0, statusW);
1040
- const conf = opp.interpretation?.confidence != null ? `${opp.interpretation.confidence}%` : "-";
1135
+ const rawConf = opp.interpretation?.confidence;
1136
+ const conf = rawConf != null ? `${Math.round(rawConf <= 1 ? rawConf * 100 : rawConf)}%` : "-";
1041
1137
  const date = opp.createdAt ? new Date(opp.createdAt).toLocaleDateString("en-US", {
1042
1138
  year: "numeric",
1043
1139
  month: "short",
@@ -1256,7 +1352,20 @@ function messageList(messages) {
1256
1352
  }
1257
1353
  }
1258
1354
  // src/profile.command.ts
1259
- async function handleProfile(client, subcommand, positionals) {
1355
+ async function handleProfile(client, subcommand, positionals, options = {}) {
1356
+ if (subcommand === "create") {
1357
+ await profileCreate(client, options);
1358
+ return;
1359
+ }
1360
+ if (subcommand === "update") {
1361
+ const action = positionals.join(" ");
1362
+ if (!action) {
1363
+ error("Usage: index profile update <action> [--details <text>]", 1);
1364
+ return;
1365
+ }
1366
+ await profileUpdate(client, action, options.details);
1367
+ return;
1368
+ }
1260
1369
  if (subcommand === "sync") {
1261
1370
  await profileSync(client);
1262
1371
  return;
@@ -1270,6 +1379,35 @@ async function handleProfile(client, subcommand, positionals) {
1270
1379
  await profileShow(client, userId);
1271
1380
  return;
1272
1381
  }
1382
+ if (subcommand === "search") {
1383
+ const query = positionals.join(" ");
1384
+ if (!query) {
1385
+ error("Usage: index profile search <query>", 1);
1386
+ return;
1387
+ }
1388
+ const result = await client.callTool("read_user_profiles", { query });
1389
+ if (options.json) {
1390
+ console.log(JSON.stringify(result));
1391
+ return;
1392
+ }
1393
+ if (!result.success) {
1394
+ error(result.error ?? "Search failed", 1);
1395
+ return;
1396
+ }
1397
+ const data = result.data;
1398
+ heading("Search Results");
1399
+ if (!data.profiles?.length) {
1400
+ dim(" No profiles found.");
1401
+ } else {
1402
+ for (const p of data.profiles) {
1403
+ console.log(` ${p.name} (${p.userId.slice(0, 8)})`);
1404
+ if (p.profile?.bio)
1405
+ dim(` ${p.profile.bio.slice(0, 100)}`);
1406
+ }
1407
+ }
1408
+ console.log();
1409
+ return;
1410
+ }
1273
1411
  await profileMe(client);
1274
1412
  }
1275
1413
  async function profileMe(client) {
@@ -1288,6 +1426,34 @@ async function profileSync(client) {
1288
1426
  await client.syncProfile();
1289
1427
  success("Profile regeneration triggered. It may take a moment to complete.");
1290
1428
  }
1429
+ async function profileCreate(client, options) {
1430
+ info("Creating profile...");
1431
+ const query = { confirm: true };
1432
+ if (options.linkedin)
1433
+ query.linkedinUrl = options.linkedin;
1434
+ if (options.github)
1435
+ query.githubUrl = options.github;
1436
+ if (options.twitter)
1437
+ query.twitterUrl = options.twitter;
1438
+ const result = await client.callTool("create_user_profile", query);
1439
+ if (!result.success) {
1440
+ error(result.error ?? "Profile creation failed", 1);
1441
+ return;
1442
+ }
1443
+ success("Profile created.");
1444
+ }
1445
+ async function profileUpdate(client, action, details) {
1446
+ info("Updating profile...");
1447
+ const query = { action };
1448
+ if (details)
1449
+ query.details = details;
1450
+ const result = await client.callTool("update_user_profile", query);
1451
+ if (!result.success) {
1452
+ error(result.error ?? "Profile update failed", 1);
1453
+ return;
1454
+ }
1455
+ success("Profile updated.");
1456
+ }
1291
1457
 
1292
1458
  // src/intent.command.ts
1293
1459
  var INTENT_HELP = `
@@ -1295,11 +1461,19 @@ Usage:
1295
1461
  index intent list [--archived] [--limit <n>] List your signals
1296
1462
  index intent show <id> Show signal details (accepts short ID)
1297
1463
  index intent create <content> Create a signal from text
1464
+ index intent update <id> <content> Update a signal's description
1298
1465
  index intent archive <id> Archive a signal (accepts short ID)
1466
+ index intent link <id> <network-id> Link a signal to a network
1467
+ index intent unlink <id> <network-id> Unlink a signal from a network
1468
+ index intent links <id> Show linked networks for a signal
1299
1469
  `;
1300
1470
  async function handleIntent(client, subcommand, options) {
1301
1471
  if (!subcommand) {
1302
- console.log(INTENT_HELP);
1472
+ if (options.json) {
1473
+ console.log(JSON.stringify({ error: "No subcommand provided" }));
1474
+ } else {
1475
+ console.log(INTENT_HELP);
1476
+ }
1303
1477
  return;
1304
1478
  }
1305
1479
  switch (subcommand) {
@@ -1308,6 +1482,10 @@ async function handleIntent(client, subcommand, options) {
1308
1482
  archived: options.archived,
1309
1483
  limit: options.limit
1310
1484
  });
1485
+ if (options.json) {
1486
+ console.log(JSON.stringify(result));
1487
+ return;
1488
+ }
1311
1489
  heading("Signals");
1312
1490
  intentTable(result.intents);
1313
1491
  if (result.pagination.total > 0) {
@@ -1323,6 +1501,10 @@ async function handleIntent(client, subcommand, options) {
1323
1501
  return;
1324
1502
  }
1325
1503
  const intent = await client.getIntent(options.intentId);
1504
+ if (options.json) {
1505
+ console.log(JSON.stringify(intent));
1506
+ return;
1507
+ }
1326
1508
  intentCard(intent);
1327
1509
  return;
1328
1510
  }
@@ -1333,81 +1515,236 @@ async function handleIntent(client, subcommand, options) {
1333
1515
  }
1334
1516
  info("Processing signal...");
1335
1517
  const result = await client.processIntent(options.intentContent);
1518
+ if (options.json) {
1519
+ console.log(JSON.stringify(result));
1520
+ return;
1521
+ }
1336
1522
  success("Signal processed successfully.");
1337
1523
  if (result.message) {
1338
1524
  dim(` ${result.message}`);
1339
1525
  }
1340
1526
  return;
1341
1527
  }
1528
+ case "update": {
1529
+ if (!options.intentId) {
1530
+ error("Missing signal ID. Usage: index intent update <id> <content>", 1);
1531
+ return;
1532
+ }
1533
+ if (!options.intentContent) {
1534
+ error("Missing content. Usage: index intent update <id> <content>", 1);
1535
+ return;
1536
+ }
1537
+ info("Updating signal...");
1538
+ const result = await client.callTool("update_intent", {
1539
+ intentId: options.intentId,
1540
+ newDescription: options.intentContent
1541
+ });
1542
+ if (options.json) {
1543
+ console.log(JSON.stringify(result));
1544
+ return;
1545
+ }
1546
+ if (!result.success) {
1547
+ error(result.error ?? "Failed to update signal", 1);
1548
+ return;
1549
+ }
1550
+ success("Signal updated.");
1551
+ return;
1552
+ }
1342
1553
  case "archive": {
1343
1554
  if (!options.intentId) {
1344
1555
  error("Missing signal ID. Usage: index intent archive <id>", 1);
1345
1556
  return;
1346
1557
  }
1347
1558
  await client.archiveIntent(options.intentId);
1559
+ if (options.json) {
1560
+ console.log(JSON.stringify({ success: true }));
1561
+ return;
1562
+ }
1348
1563
  success(`Signal ${options.intentId} archived.`);
1349
1564
  return;
1350
1565
  }
1566
+ case "link": {
1567
+ if (!options.intentId || !options.targetId) {
1568
+ error("Usage: index intent link <intent-id> <network-id>", 1);
1569
+ return;
1570
+ }
1571
+ const result = await client.callTool("create_intent_index", {
1572
+ intentId: options.intentId,
1573
+ indexId: options.targetId
1574
+ });
1575
+ if (options.json) {
1576
+ console.log(JSON.stringify(result));
1577
+ return;
1578
+ }
1579
+ if (!result.success) {
1580
+ error(result.error ?? "Failed to link signal", 1);
1581
+ return;
1582
+ }
1583
+ success("Signal linked to network.");
1584
+ return;
1585
+ }
1586
+ case "unlink": {
1587
+ if (!options.intentId || !options.targetId) {
1588
+ error("Usage: index intent unlink <intent-id> <network-id>", 1);
1589
+ return;
1590
+ }
1591
+ const result = await client.callTool("delete_intent_index", {
1592
+ intentId: options.intentId,
1593
+ indexId: options.targetId
1594
+ });
1595
+ if (options.json) {
1596
+ console.log(JSON.stringify(result));
1597
+ return;
1598
+ }
1599
+ if (!result.success) {
1600
+ error(result.error ?? "Failed to unlink signal", 1);
1601
+ return;
1602
+ }
1603
+ success("Signal unlinked from network.");
1604
+ return;
1605
+ }
1606
+ case "links": {
1607
+ if (!options.intentId) {
1608
+ error("Missing signal ID. Usage: index intent links <id>", 1);
1609
+ return;
1610
+ }
1611
+ const result = await client.callTool("read_intent_indexes", {
1612
+ intentId: options.intentId
1613
+ });
1614
+ if (options.json) {
1615
+ console.log(JSON.stringify(result));
1616
+ return;
1617
+ }
1618
+ if (!result.success) {
1619
+ error(result.error ?? "Failed to read linked networks", 1);
1620
+ return;
1621
+ }
1622
+ const data = result.data;
1623
+ heading("Linked Networks");
1624
+ if (!data.indexes?.length) {
1625
+ dim(" No linked networks.");
1626
+ } else {
1627
+ for (const idx of data.indexes) {
1628
+ const score = idx.relevancyScore !== undefined ? ` (${idx.relevancyScore.toFixed(2)})` : "";
1629
+ console.log(` ${idx.title} ${DIM}${idx.indexId.slice(0, 8)}${score}${RESET}`);
1630
+ }
1631
+ }
1632
+ console.log();
1633
+ return;
1634
+ }
1351
1635
  }
1352
1636
  }
1353
1637
 
1354
1638
  // src/opportunity.command.ts
1355
1639
  var OPPORTUNITY_HELP = `
1356
1640
  Usage:
1357
- index opportunity list List your opportunities
1358
- index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
1359
- index opportunity list --limit <n> Limit results
1360
- index opportunity show <id> Show full opportunity details (accepts short ID)
1361
- index opportunity accept <id> Accept an opportunity (accepts short ID)
1362
- index opportunity reject <id> Reject an opportunity (accepts short ID)
1641
+ index opportunity list List your opportunities
1642
+ index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
1643
+ index opportunity list --limit <n> Limit results
1644
+ index opportunity show <id> Show full opportunity details (accepts short ID)
1645
+ index opportunity accept <id> Accept an opportunity (accepts short ID)
1646
+ index opportunity reject <id> Reject an opportunity (accepts short ID)
1647
+ index opportunity discover <query> Discover opportunities by search query
1648
+ index opportunity discover --target <uid> <q> Discover for a specific user
1649
+ index opportunity discover --introduce <a> <b> Introduce two users
1363
1650
  `;
1364
1651
  async function handleOpportunity(client, subcommand, options) {
1365
1652
  if (!subcommand) {
1366
- console.log(OPPORTUNITY_HELP);
1653
+ if (options.json) {
1654
+ console.log(JSON.stringify({ error: "No subcommand provided" }));
1655
+ } else {
1656
+ console.log(OPPORTUNITY_HELP);
1657
+ }
1367
1658
  return;
1368
1659
  }
1369
1660
  switch (subcommand) {
1370
1661
  case "list":
1371
- await opportunityList(client, options.status, options.limit);
1662
+ await opportunityList(client, options.status, options.limit, options.json);
1372
1663
  return;
1373
1664
  case "show":
1374
1665
  if (!options.targetId) {
1375
1666
  error("Missing opportunity ID. Usage: index opportunity show <id>", 1);
1376
1667
  return;
1377
1668
  }
1378
- await opportunityShow(client, options.targetId);
1669
+ await opportunityShow(client, options.targetId, options.json);
1379
1670
  return;
1380
1671
  case "accept":
1381
1672
  if (!options.targetId) {
1382
1673
  error("Missing opportunity ID. Usage: index opportunity accept <id>", 1);
1383
1674
  return;
1384
1675
  }
1385
- await opportunityStatusUpdate(client, options.targetId, "accepted");
1676
+ await opportunityStatusUpdate(client, options.targetId, "accepted", options.json);
1386
1677
  return;
1387
1678
  case "reject":
1388
1679
  if (!options.targetId) {
1389
1680
  error("Missing opportunity ID. Usage: index opportunity reject <id>", 1);
1390
1681
  return;
1391
1682
  }
1392
- await opportunityStatusUpdate(client, options.targetId, "rejected");
1683
+ await opportunityStatusUpdate(client, options.targetId, "rejected", options.json);
1684
+ return;
1685
+ case "discover": {
1686
+ const query = options.positionals?.join(" ");
1687
+ if (!query && !options.introduce) {
1688
+ error("Usage: index opportunity discover <query>", 1);
1689
+ return;
1690
+ }
1691
+ info("Discovering opportunities...");
1692
+ const toolQuery = {};
1693
+ if (options.introduce) {
1694
+ toolQuery.mode = "introduction";
1695
+ toolQuery.sourceUserId = options.introduce;
1696
+ toolQuery.targetUserId = options.positionals?.[0];
1697
+ } else if (options.target) {
1698
+ toolQuery.mode = "direct";
1699
+ toolQuery.targetUserId = options.target;
1700
+ toolQuery.searchQuery = query;
1701
+ } else {
1702
+ toolQuery.searchQuery = query;
1703
+ }
1704
+ const result = await client.callTool("create_opportunities", toolQuery);
1705
+ if (options.json) {
1706
+ console.log(JSON.stringify(result));
1707
+ return;
1708
+ }
1709
+ if (!result.success) {
1710
+ error(result.error ?? "Discovery failed", 1);
1711
+ return;
1712
+ }
1713
+ success("Discovery complete.");
1714
+ const data = result.data;
1715
+ if (data?.message)
1716
+ dim(` ${data.message}`);
1393
1717
  return;
1718
+ }
1394
1719
  default:
1395
1720
  error(`Unknown subcommand: ${subcommand}`, 1);
1396
1721
  return;
1397
1722
  }
1398
1723
  }
1399
- async function opportunityList(client, status2, limit) {
1724
+ async function opportunityList(client, status2, limit, json) {
1400
1725
  const opportunities = await client.listOpportunities({ status: status2, limit });
1726
+ if (json) {
1727
+ console.log(JSON.stringify(opportunities));
1728
+ return;
1729
+ }
1401
1730
  heading("Opportunities");
1402
1731
  opportunityTable(opportunities);
1403
1732
  console.log();
1404
1733
  }
1405
- async function opportunityShow(client, id) {
1734
+ async function opportunityShow(client, id, json) {
1406
1735
  const opportunity = await client.getOpportunity(id);
1736
+ if (json) {
1737
+ console.log(JSON.stringify(opportunity));
1738
+ return;
1739
+ }
1407
1740
  opportunityCard(opportunity);
1408
1741
  }
1409
- async function opportunityStatusUpdate(client, id, status2) {
1410
- await client.updateOpportunityStatus(id, status2);
1742
+ async function opportunityStatusUpdate(client, id, status2, json) {
1743
+ const result = await client.updateOpportunityStatus(id, status2);
1744
+ if (json) {
1745
+ console.log(JSON.stringify(result));
1746
+ return;
1747
+ }
1411
1748
  const label = status2 === "accepted" ? "accepted" : "rejected";
1412
1749
  success(`Opportunity ${label}.`);
1413
1750
  }
@@ -1419,6 +1756,9 @@ Network Commands:
1419
1756
  index network create <name> Create a new network
1420
1757
  index network create <name> --prompt Create with a description
1421
1758
  index network show <id|key> Show network details and members
1759
+ index network update <id> [--title <t>] [--prompt <p>]
1760
+ Update a network
1761
+ index network delete <id> Delete a network
1422
1762
  index network join <id|key> Join a public network
1423
1763
  index network leave <id|key> Leave a network
1424
1764
  index network invite <id|key> <email> Invite a user by email
@@ -1444,6 +1784,12 @@ async function handleNetwork(client, subcommand, positionals, options) {
1444
1784
  case "leave":
1445
1785
  await networkLeave(client, positionals[0]);
1446
1786
  return;
1787
+ case "update":
1788
+ await networkUpdate(client, positionals[0], options);
1789
+ return;
1790
+ case "delete":
1791
+ await networkDelete(client, positionals[0]);
1792
+ return;
1447
1793
  case "invite":
1448
1794
  await networkInvite(client, positionals[0], positionals[1]);
1449
1795
  return;
@@ -1499,6 +1845,39 @@ async function networkLeave(client, id) {
1499
1845
  await client.leaveNetwork(id);
1500
1846
  success("Left network.");
1501
1847
  }
1848
+ async function networkUpdate(client, id, options) {
1849
+ if (!id) {
1850
+ error("Usage: index network update <id> [--title <t>] [--prompt <p>]", 1);
1851
+ return;
1852
+ }
1853
+ const settings = {};
1854
+ if (options.title)
1855
+ settings.title = options.title;
1856
+ if (options.prompt)
1857
+ settings.prompt = options.prompt;
1858
+ if (Object.keys(settings).length === 0) {
1859
+ error("Provide at least --title or --prompt. Usage: index network update <id> [--title <t>] [--prompt <p>]", 1);
1860
+ return;
1861
+ }
1862
+ const result = await client.callTool("update_index", { indexId: id, settings });
1863
+ if (!result.success) {
1864
+ error(result.error ?? "Network update failed", 1);
1865
+ return;
1866
+ }
1867
+ success("Network updated.");
1868
+ }
1869
+ async function networkDelete(client, id) {
1870
+ if (!id) {
1871
+ error("Usage: index network delete <id>", 1);
1872
+ return;
1873
+ }
1874
+ const result = await client.callTool("delete_index", { indexId: id });
1875
+ if (!result.success) {
1876
+ error(result.error ?? "Network deletion failed", 1);
1877
+ return;
1878
+ }
1879
+ success("Network deleted.");
1880
+ }
1502
1881
  async function networkInvite(client, id, email) {
1503
1882
  if (!id || !email) {
1504
1883
  error("Usage: index network invite <id> <email>", 1);
@@ -1885,6 +2264,175 @@ async function handleStreamError(response) {
1885
2264
  error(body.error ?? `HTTP ${response.status}`, 1);
1886
2265
  }
1887
2266
 
2267
+ // src/contact.command.ts
2268
+ var CONTACT_HELP = `
2269
+ Usage:
2270
+ index contact list List your contacts
2271
+ index contact add <email> [--name <n>] Add a contact by email
2272
+ index contact remove <email> Remove a contact
2273
+ index contact import --gmail Import contacts from Gmail
2274
+ `;
2275
+ async function handleContact(client, subcommand, positionals, options) {
2276
+ if (!subcommand) {
2277
+ if (options.json) {
2278
+ console.log(JSON.stringify({ error: "No subcommand provided" }));
2279
+ } else {
2280
+ console.log(CONTACT_HELP);
2281
+ }
2282
+ return;
2283
+ }
2284
+ switch (subcommand) {
2285
+ case "list": {
2286
+ const result = await client.callTool("list_contacts", {});
2287
+ if (options.json) {
2288
+ console.log(JSON.stringify(result));
2289
+ return;
2290
+ }
2291
+ if (!result.success) {
2292
+ error(result.error ?? "Failed to list contacts", 1);
2293
+ return;
2294
+ }
2295
+ const data = result.data;
2296
+ heading("Contacts");
2297
+ contactTable(data.contacts);
2298
+ dim(`
2299
+ ${data.count} contact${data.count !== 1 ? "s" : ""}`);
2300
+ console.log();
2301
+ return;
2302
+ }
2303
+ case "add": {
2304
+ const email = positionals[0];
2305
+ if (!email) {
2306
+ error("Missing email. Usage: index contact add <email>", 1);
2307
+ return;
2308
+ }
2309
+ const result = await client.callTool("add_contact", { email, name: options.name });
2310
+ if (options.json) {
2311
+ console.log(JSON.stringify(result));
2312
+ return;
2313
+ }
2314
+ if (!result.success) {
2315
+ error(result.error ?? "Failed to add contact", 1);
2316
+ return;
2317
+ }
2318
+ const data = result.data;
2319
+ success(data.message);
2320
+ return;
2321
+ }
2322
+ case "remove": {
2323
+ const email = positionals[0];
2324
+ if (!email) {
2325
+ error("Missing email. Usage: index contact remove <email>", 1);
2326
+ return;
2327
+ }
2328
+ const listResult = await client.callTool("list_contacts", {});
2329
+ if (!listResult.success) {
2330
+ error("Failed to resolve contact", 1);
2331
+ return;
2332
+ }
2333
+ const contacts = listResult.data.contacts;
2334
+ const match = contacts.find((c) => c.email.toLowerCase() === email.toLowerCase());
2335
+ if (!match) {
2336
+ error(`No contact found with email: ${email}`, 1);
2337
+ return;
2338
+ }
2339
+ const result = await client.callTool("remove_contact", { contactUserId: match.userId });
2340
+ if (options.json) {
2341
+ console.log(JSON.stringify(result));
2342
+ return;
2343
+ }
2344
+ if (!result.success) {
2345
+ error(result.error ?? "Failed to remove contact", 1);
2346
+ return;
2347
+ }
2348
+ success(`Removed ${email} from contacts.`);
2349
+ return;
2350
+ }
2351
+ case "import": {
2352
+ if (options.gmail) {
2353
+ const result = await client.callTool("import_gmail_contacts", {});
2354
+ if (options.json) {
2355
+ console.log(JSON.stringify(result));
2356
+ return;
2357
+ }
2358
+ if (!result.success) {
2359
+ error(result.error ?? "Failed to import Gmail contacts", 1);
2360
+ return;
2361
+ }
2362
+ const data = result.data;
2363
+ success(data.message);
2364
+ } else {
2365
+ error("Specify import source: --gmail", 1);
2366
+ }
2367
+ return;
2368
+ }
2369
+ default: {
2370
+ if (options.json) {
2371
+ console.log(JSON.stringify({ error: `Unknown subcommand: ${subcommand}` }));
2372
+ } else {
2373
+ error(`Unknown subcommand: ${subcommand}`);
2374
+ console.log(CONTACT_HELP);
2375
+ }
2376
+ return;
2377
+ }
2378
+ }
2379
+ }
2380
+
2381
+ // src/scrape.command.ts
2382
+ async function handleScrape(client, positionals, options) {
2383
+ const url = positionals[0];
2384
+ if (!url) {
2385
+ error("Usage: index scrape <url> [--objective <text>]", 1);
2386
+ return;
2387
+ }
2388
+ info(`Scraping ${url}...`);
2389
+ const result = await client.callTool("scrape_url", { url, objective: options.objective });
2390
+ if (options.json) {
2391
+ console.log(JSON.stringify(result));
2392
+ return;
2393
+ }
2394
+ if (!result.success) {
2395
+ error(result.error ?? "Scrape failed", 1);
2396
+ return;
2397
+ }
2398
+ const data = result.data;
2399
+ heading(`Content from ${data.url}`);
2400
+ console.log(data.content);
2401
+ dim(`
2402
+ ${data.contentLength} characters extracted`);
2403
+ console.log();
2404
+ }
2405
+
2406
+ // src/sync.command.ts
2407
+ import { mkdirSync, writeFileSync } from "node:fs";
2408
+ import { homedir as homedir2 } from "node:os";
2409
+ import { join as join2 } from "node:path";
2410
+ async function handleSync(client, options) {
2411
+ if (!options.json)
2412
+ info("Syncing context...");
2413
+ const [profile, networks, intents, contacts] = await Promise.all([
2414
+ client.callTool("read_user_profiles", {}),
2415
+ client.callTool("read_indexes", {}),
2416
+ client.callTool("read_intents", {}),
2417
+ client.callTool("list_contacts", {})
2418
+ ]);
2419
+ const context = {
2420
+ syncedAt: new Date().toISOString(),
2421
+ profile: profile.success ? profile.data : null,
2422
+ networks: networks.success ? networks.data : null,
2423
+ intents: intents.success ? intents.data : null,
2424
+ contacts: contacts.success ? contacts.data : null
2425
+ };
2426
+ if (options.json) {
2427
+ console.log(JSON.stringify(context));
2428
+ return;
2429
+ }
2430
+ const dir = join2(homedir2(), ".index");
2431
+ mkdirSync(dir, { recursive: true, mode: 448 });
2432
+ writeFileSync(join2(dir, "context.json"), JSON.stringify(context, null, 2), { mode: 384 });
2433
+ success("Context synced to ~/.index/context.json");
2434
+ }
2435
+
1888
2436
  // src/main.ts
1889
2437
  var DEFAULT_API_URL = "https://protocol.index.network";
1890
2438
  var DEFAULT_APP_URL = "https://index.network";
@@ -1908,22 +2456,43 @@ Usage:
1908
2456
  index profile Show your profile
1909
2457
  index profile show <user-id> Show another user's profile
1910
2458
  index profile sync Regenerate your profile
2459
+ index profile search <query> Search user profiles
2460
+ index profile create [--linkedin <url>] [--github <url>] [--twitter <url>]
2461
+ Create your profile from social URLs
2462
+ index profile update <action> [--details <text>]
2463
+ Update your profile
1911
2464
  index intent list [--archived] [--limit <n>] List your signals
1912
2465
  index intent show <id> Show signal details
1913
2466
  index intent create <content> Create a signal from natural language
2467
+ index intent update <id> <content> Update a signal's description
1914
2468
  index intent archive <id> Archive a signal
2469
+ index intent link <id> <network-id> Link a signal to a network
2470
+ index intent unlink <id> <network-id> Unlink a signal from a network
2471
+ index intent links <id> Show linked networks for a signal
1915
2472
  index opportunity list List your opportunities
1916
2473
  index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
1917
2474
  index opportunity list --limit <n> Limit results
1918
2475
  index opportunity show <id> Show full opportunity details
1919
2476
  index opportunity accept <id> Accept an opportunity
1920
2477
  index opportunity reject <id> Reject an opportunity
2478
+ index opportunity discover <query> Discover opportunities by search query
1921
2479
  index network list List your networks
1922
2480
  index network create <name> Create a new network
1923
2481
  index network show <id> Show network details and members
2482
+ index network update <id> [--title <t>] [--prompt <p>]
2483
+ Update a network
2484
+ index network delete <id> Delete a network
1924
2485
  index network join <id> Join a public network
1925
2486
  index network leave <id> Leave a network
1926
2487
  index network invite <id> <email> Invite a user by email
2488
+ index contact list List your contacts
2489
+ index contact add <email> Add a contact by email
2490
+ index contact remove <email> Remove a contact
2491
+ index contact import --gmail Import contacts from Gmail
2492
+ index scrape <url> Extract content from a URL
2493
+ index onboarding complete Mark onboarding as complete
2494
+ index sync Sync context to ~/.index/context.json
2495
+ index sync --json Output synced context to stdout
1927
2496
  index --help Show this help message
1928
2497
  index --version Show version
1929
2498
 
@@ -1935,6 +2504,17 @@ Options:
1935
2504
  --archived Include archived signals (intent list)
1936
2505
  --status <status> Filter opportunities by status
1937
2506
  --limit <n> Limit number of results
2507
+ --json Output raw JSON instead of formatted text
2508
+ --name <name> Name for contact add
2509
+ --gmail Import source flag for contact import
2510
+ --objective <text> Objective for scrape command
2511
+ --target <uid> Target user for opportunity discover
2512
+ --introduce <uid> Source user for introduction discovery
2513
+ --linkedin <url> LinkedIn URL for profile create
2514
+ --github <url> GitHub URL for profile create
2515
+ --twitter <url> Twitter URL for profile create
2516
+ --title <text> Title for network update
2517
+ --details <text> Details for profile update
1938
2518
  `;
1939
2519
  async function requireAuth(apiUrlOverride) {
1940
2520
  const store = new CredentialStore;
@@ -2017,26 +2597,39 @@ async function main() {
2017
2597
  const client = await requireAuth(args.apiUrl);
2018
2598
  switch (args.command) {
2019
2599
  case "profile":
2020
- await handleProfile(client, args.subcommand, args.userId ? [args.userId] : []);
2600
+ await handleProfile(client, args.subcommand, args.subcommand === "search" || args.subcommand === "update" ? args.positionals ?? [] : args.userId ? [args.userId] : [], {
2601
+ json: args.json,
2602
+ linkedin: args.linkedin,
2603
+ github: args.github,
2604
+ twitter: args.twitter,
2605
+ details: args.details
2606
+ });
2021
2607
  return;
2022
2608
  case "intent":
2023
2609
  await handleIntent(client, args.subcommand, {
2024
2610
  intentId: args.intentId,
2025
2611
  intentContent: args.intentContent,
2026
2612
  archived: args.archived,
2027
- limit: args.limit
2613
+ limit: args.limit,
2614
+ json: args.json,
2615
+ targetId: args.targetId
2028
2616
  });
2029
2617
  return;
2030
2618
  case "opportunity":
2031
2619
  await handleOpportunity(client, args.subcommand, {
2032
2620
  targetId: args.targetId,
2033
2621
  status: args.status,
2034
- limit: args.limit
2622
+ limit: args.limit,
2623
+ json: args.json,
2624
+ positionals: args.positionals,
2625
+ target: args.target,
2626
+ introduce: args.introduce
2035
2627
  });
2036
2628
  return;
2037
2629
  case "network":
2038
2630
  await handleNetwork(client, args.subcommand, args.positionals ?? [], {
2039
- prompt: args.prompt
2631
+ prompt: args.prompt,
2632
+ title: args.title
2040
2633
  });
2041
2634
  return;
2042
2635
  case "conversation":
@@ -2046,6 +2639,34 @@ async function main() {
2046
2639
  message: args.message
2047
2640
  });
2048
2641
  return;
2642
+ case "contact":
2643
+ await handleContact(client, args.subcommand, args.positionals ?? [], {
2644
+ json: args.json,
2645
+ name: args.name,
2646
+ gmail: args.gmail
2647
+ });
2648
+ return;
2649
+ case "scrape":
2650
+ await handleScrape(client, args.positionals ?? [], {
2651
+ json: args.json,
2652
+ objective: args.objective
2653
+ });
2654
+ return;
2655
+ case "onboarding":
2656
+ if (args.subcommand === "complete") {
2657
+ const result = await client.callTool("complete_onboarding", {});
2658
+ if (!result.success) {
2659
+ error(result.error ?? "Onboarding completion failed", 1);
2660
+ } else {
2661
+ success("Onboarding completed.");
2662
+ }
2663
+ } else {
2664
+ error("Usage: index onboarding complete", 1);
2665
+ }
2666
+ return;
2667
+ case "sync":
2668
+ await handleSync(client, { json: args.json });
2669
+ return;
2049
2670
  }
2050
2671
  }
2051
2672
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Command-line interface for Index Network",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,10 +20,10 @@
20
20
  "publish:all": "bun scripts/publish.ts"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@indexnetwork/cli-linux-x64": "0.8.0",
24
- "@indexnetwork/cli-linux-arm64": "0.8.0",
25
- "@indexnetwork/cli-darwin-x64": "0.8.0",
26
- "@indexnetwork/cli-darwin-arm64": "0.8.0"
23
+ "@indexnetwork/cli-linux-x64": "0.9.0",
24
+ "@indexnetwork/cli-linux-arm64": "0.9.0",
25
+ "@indexnetwork/cli-darwin-x64": "0.9.0",
26
+ "@indexnetwork/cli-darwin-arm64": "0.9.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/bun": "latest"