@indexnetwork/cli 0.8.1 → 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 +641 -26
  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
 
@@ -354,6 +427,10 @@ class ApiClient {
354
427
  }
355
428
  return res;
356
429
  }
430
+ async callTool(toolName, query = {}) {
431
+ const res = await this.post(`/api/tools/${toolName}`, { query });
432
+ return await res.json();
433
+ }
357
434
  async get(path) {
358
435
  const res = await fetch(`${this.baseUrl}${path}`, {
359
436
  headers: {
@@ -914,6 +991,19 @@ function profileCard(data) {
914
991
  console.log(output);
915
992
  return output;
916
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
+ }
917
1007
  function sessionTable(sessions) {
918
1008
  if (sessions.length === 0) {
919
1009
  dim(" No chat sessions found.");
@@ -1262,7 +1352,20 @@ function messageList(messages) {
1262
1352
  }
1263
1353
  }
1264
1354
  // src/profile.command.ts
1265
- 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
+ }
1266
1369
  if (subcommand === "sync") {
1267
1370
  await profileSync(client);
1268
1371
  return;
@@ -1276,6 +1379,35 @@ async function handleProfile(client, subcommand, positionals) {
1276
1379
  await profileShow(client, userId);
1277
1380
  return;
1278
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
+ }
1279
1411
  await profileMe(client);
1280
1412
  }
1281
1413
  async function profileMe(client) {
@@ -1294,6 +1426,34 @@ async function profileSync(client) {
1294
1426
  await client.syncProfile();
1295
1427
  success("Profile regeneration triggered. It may take a moment to complete.");
1296
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
+ }
1297
1457
 
1298
1458
  // src/intent.command.ts
1299
1459
  var INTENT_HELP = `
@@ -1301,11 +1461,19 @@ Usage:
1301
1461
  index intent list [--archived] [--limit <n>] List your signals
1302
1462
  index intent show <id> Show signal details (accepts short ID)
1303
1463
  index intent create <content> Create a signal from text
1464
+ index intent update <id> <content> Update a signal's description
1304
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
1305
1469
  `;
1306
1470
  async function handleIntent(client, subcommand, options) {
1307
1471
  if (!subcommand) {
1308
- 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
+ }
1309
1477
  return;
1310
1478
  }
1311
1479
  switch (subcommand) {
@@ -1314,6 +1482,10 @@ async function handleIntent(client, subcommand, options) {
1314
1482
  archived: options.archived,
1315
1483
  limit: options.limit
1316
1484
  });
1485
+ if (options.json) {
1486
+ console.log(JSON.stringify(result));
1487
+ return;
1488
+ }
1317
1489
  heading("Signals");
1318
1490
  intentTable(result.intents);
1319
1491
  if (result.pagination.total > 0) {
@@ -1329,6 +1501,10 @@ async function handleIntent(client, subcommand, options) {
1329
1501
  return;
1330
1502
  }
1331
1503
  const intent = await client.getIntent(options.intentId);
1504
+ if (options.json) {
1505
+ console.log(JSON.stringify(intent));
1506
+ return;
1507
+ }
1332
1508
  intentCard(intent);
1333
1509
  return;
1334
1510
  }
@@ -1339,81 +1515,236 @@ async function handleIntent(client, subcommand, options) {
1339
1515
  }
1340
1516
  info("Processing signal...");
1341
1517
  const result = await client.processIntent(options.intentContent);
1518
+ if (options.json) {
1519
+ console.log(JSON.stringify(result));
1520
+ return;
1521
+ }
1342
1522
  success("Signal processed successfully.");
1343
1523
  if (result.message) {
1344
1524
  dim(` ${result.message}`);
1345
1525
  }
1346
1526
  return;
1347
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
+ }
1348
1553
  case "archive": {
1349
1554
  if (!options.intentId) {
1350
1555
  error("Missing signal ID. Usage: index intent archive <id>", 1);
1351
1556
  return;
1352
1557
  }
1353
1558
  await client.archiveIntent(options.intentId);
1559
+ if (options.json) {
1560
+ console.log(JSON.stringify({ success: true }));
1561
+ return;
1562
+ }
1354
1563
  success(`Signal ${options.intentId} archived.`);
1355
1564
  return;
1356
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
+ }
1357
1635
  }
1358
1636
  }
1359
1637
 
1360
1638
  // src/opportunity.command.ts
1361
1639
  var OPPORTUNITY_HELP = `
1362
1640
  Usage:
1363
- index opportunity list List your opportunities
1364
- index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
1365
- index opportunity list --limit <n> Limit results
1366
- index opportunity show <id> Show full opportunity details (accepts short ID)
1367
- index opportunity accept <id> Accept an opportunity (accepts short ID)
1368
- 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
1369
1650
  `;
1370
1651
  async function handleOpportunity(client, subcommand, options) {
1371
1652
  if (!subcommand) {
1372
- 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
+ }
1373
1658
  return;
1374
1659
  }
1375
1660
  switch (subcommand) {
1376
1661
  case "list":
1377
- await opportunityList(client, options.status, options.limit);
1662
+ await opportunityList(client, options.status, options.limit, options.json);
1378
1663
  return;
1379
1664
  case "show":
1380
1665
  if (!options.targetId) {
1381
1666
  error("Missing opportunity ID. Usage: index opportunity show <id>", 1);
1382
1667
  return;
1383
1668
  }
1384
- await opportunityShow(client, options.targetId);
1669
+ await opportunityShow(client, options.targetId, options.json);
1385
1670
  return;
1386
1671
  case "accept":
1387
1672
  if (!options.targetId) {
1388
1673
  error("Missing opportunity ID. Usage: index opportunity accept <id>", 1);
1389
1674
  return;
1390
1675
  }
1391
- await opportunityStatusUpdate(client, options.targetId, "accepted");
1676
+ await opportunityStatusUpdate(client, options.targetId, "accepted", options.json);
1392
1677
  return;
1393
1678
  case "reject":
1394
1679
  if (!options.targetId) {
1395
1680
  error("Missing opportunity ID. Usage: index opportunity reject <id>", 1);
1396
1681
  return;
1397
1682
  }
1398
- 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}`);
1399
1717
  return;
1718
+ }
1400
1719
  default:
1401
1720
  error(`Unknown subcommand: ${subcommand}`, 1);
1402
1721
  return;
1403
1722
  }
1404
1723
  }
1405
- async function opportunityList(client, status2, limit) {
1724
+ async function opportunityList(client, status2, limit, json) {
1406
1725
  const opportunities = await client.listOpportunities({ status: status2, limit });
1726
+ if (json) {
1727
+ console.log(JSON.stringify(opportunities));
1728
+ return;
1729
+ }
1407
1730
  heading("Opportunities");
1408
1731
  opportunityTable(opportunities);
1409
1732
  console.log();
1410
1733
  }
1411
- async function opportunityShow(client, id) {
1734
+ async function opportunityShow(client, id, json) {
1412
1735
  const opportunity = await client.getOpportunity(id);
1736
+ if (json) {
1737
+ console.log(JSON.stringify(opportunity));
1738
+ return;
1739
+ }
1413
1740
  opportunityCard(opportunity);
1414
1741
  }
1415
- async function opportunityStatusUpdate(client, id, status2) {
1416
- 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
+ }
1417
1748
  const label = status2 === "accepted" ? "accepted" : "rejected";
1418
1749
  success(`Opportunity ${label}.`);
1419
1750
  }
@@ -1425,6 +1756,9 @@ Network Commands:
1425
1756
  index network create <name> Create a new network
1426
1757
  index network create <name> --prompt Create with a description
1427
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
1428
1762
  index network join <id|key> Join a public network
1429
1763
  index network leave <id|key> Leave a network
1430
1764
  index network invite <id|key> <email> Invite a user by email
@@ -1450,6 +1784,12 @@ async function handleNetwork(client, subcommand, positionals, options) {
1450
1784
  case "leave":
1451
1785
  await networkLeave(client, positionals[0]);
1452
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;
1453
1793
  case "invite":
1454
1794
  await networkInvite(client, positionals[0], positionals[1]);
1455
1795
  return;
@@ -1505,6 +1845,39 @@ async function networkLeave(client, id) {
1505
1845
  await client.leaveNetwork(id);
1506
1846
  success("Left network.");
1507
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
+ }
1508
1881
  async function networkInvite(client, id, email) {
1509
1882
  if (!id || !email) {
1510
1883
  error("Usage: index network invite <id> <email>", 1);
@@ -1891,6 +2264,175 @@ async function handleStreamError(response) {
1891
2264
  error(body.error ?? `HTTP ${response.status}`, 1);
1892
2265
  }
1893
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
+
1894
2436
  // src/main.ts
1895
2437
  var DEFAULT_API_URL = "https://protocol.index.network";
1896
2438
  var DEFAULT_APP_URL = "https://index.network";
@@ -1914,22 +2456,43 @@ Usage:
1914
2456
  index profile Show your profile
1915
2457
  index profile show <user-id> Show another user's profile
1916
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
1917
2464
  index intent list [--archived] [--limit <n>] List your signals
1918
2465
  index intent show <id> Show signal details
1919
2466
  index intent create <content> Create a signal from natural language
2467
+ index intent update <id> <content> Update a signal's description
1920
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
1921
2472
  index opportunity list List your opportunities
1922
2473
  index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
1923
2474
  index opportunity list --limit <n> Limit results
1924
2475
  index opportunity show <id> Show full opportunity details
1925
2476
  index opportunity accept <id> Accept an opportunity
1926
2477
  index opportunity reject <id> Reject an opportunity
2478
+ index opportunity discover <query> Discover opportunities by search query
1927
2479
  index network list List your networks
1928
2480
  index network create <name> Create a new network
1929
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
1930
2485
  index network join <id> Join a public network
1931
2486
  index network leave <id> Leave a network
1932
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
1933
2496
  index --help Show this help message
1934
2497
  index --version Show version
1935
2498
 
@@ -1941,6 +2504,17 @@ Options:
1941
2504
  --archived Include archived signals (intent list)
1942
2505
  --status <status> Filter opportunities by status
1943
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
1944
2518
  `;
1945
2519
  async function requireAuth(apiUrlOverride) {
1946
2520
  const store = new CredentialStore;
@@ -2023,26 +2597,39 @@ async function main() {
2023
2597
  const client = await requireAuth(args.apiUrl);
2024
2598
  switch (args.command) {
2025
2599
  case "profile":
2026
- 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
+ });
2027
2607
  return;
2028
2608
  case "intent":
2029
2609
  await handleIntent(client, args.subcommand, {
2030
2610
  intentId: args.intentId,
2031
2611
  intentContent: args.intentContent,
2032
2612
  archived: args.archived,
2033
- limit: args.limit
2613
+ limit: args.limit,
2614
+ json: args.json,
2615
+ targetId: args.targetId
2034
2616
  });
2035
2617
  return;
2036
2618
  case "opportunity":
2037
2619
  await handleOpportunity(client, args.subcommand, {
2038
2620
  targetId: args.targetId,
2039
2621
  status: args.status,
2040
- limit: args.limit
2622
+ limit: args.limit,
2623
+ json: args.json,
2624
+ positionals: args.positionals,
2625
+ target: args.target,
2626
+ introduce: args.introduce
2041
2627
  });
2042
2628
  return;
2043
2629
  case "network":
2044
2630
  await handleNetwork(client, args.subcommand, args.positionals ?? [], {
2045
- prompt: args.prompt
2631
+ prompt: args.prompt,
2632
+ title: args.title
2046
2633
  });
2047
2634
  return;
2048
2635
  case "conversation":
@@ -2052,6 +2639,34 @@ async function main() {
2052
2639
  message: args.message
2053
2640
  });
2054
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;
2055
2670
  }
2056
2671
  }
2057
2672
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/cli",
3
- "version": "0.8.1",
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.1",
24
- "@indexnetwork/cli-linux-arm64": "0.8.1",
25
- "@indexnetwork/cli-darwin-x64": "0.8.1",
26
- "@indexnetwork/cli-darwin-arm64": "0.8.1"
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"