@indexnetwork/cli 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/index.js +732 -26
- 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,101 @@ 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. The CLI automatically finds shared networks, gathers profiles and intents, then creates the introduction.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Introduce two users to each other
|
|
196
|
+
index opportunity discover --introduce <user-id-a> <user-id-b>
|
|
197
|
+
|
|
198
|
+
# Provide a reason for the introduction
|
|
199
|
+
index opportunity discover --introduce <user-id-a> <user-id-b> "both working on privacy-preserving ML"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Scenario 4: Review and act
|
|
203
|
+
|
|
204
|
+
After discovery creates draft opportunities, review and accept/reject them.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# List pending opportunities
|
|
208
|
+
index opportunity list --status pending
|
|
209
|
+
|
|
210
|
+
# See full details (reasoning, scores, mutual intents)
|
|
211
|
+
index opportunity show <id>
|
|
212
|
+
|
|
213
|
+
# Accept — starts a conversation thread
|
|
214
|
+
index opportunity accept <id>
|
|
215
|
+
|
|
216
|
+
# Or reject
|
|
217
|
+
index opportunity reject <id>
|
|
218
|
+
```
|
|
219
|
+
|
|
120
220
|
## Options
|
|
121
221
|
|
|
122
222
|
| Flag | Short | Description |
|
|
@@ -129,6 +229,13 @@ index network invite <id> user@email # Invite a user by email
|
|
|
129
229
|
| `--status <status>` | | Filter opportunities by status |
|
|
130
230
|
| `--limit <n>` | | Limit number of results |
|
|
131
231
|
| `--prompt <text>` | `-p` | Network description (for `network create`) |
|
|
232
|
+
| `--title <text>` | | Network title (for `network update`) |
|
|
233
|
+
| `--name <name>` | | Display name (for `contact add`) |
|
|
234
|
+
| `--gmail` | | Import from Gmail (for `contact import`) |
|
|
235
|
+
| `--target <id>` | | Target user ID (for `opportunity discover`) |
|
|
236
|
+
| `--introduce <id>` | | Introduce two users (for `opportunity discover`) |
|
|
237
|
+
| `--objective <text>` | | Focus objective (for `scrape`) |
|
|
238
|
+
| `--json` | | Output raw JSON to stdout |
|
|
132
239
|
| `--help` | `-h` | Show help |
|
|
133
240
|
| `--version` | `-v` | Show version |
|
|
134
241
|
|
|
@@ -170,6 +277,9 @@ cli/
|
|
|
170
277
|
intent.command.ts Intent (signal) subcommand handlers
|
|
171
278
|
opportunity.command.ts Opportunity subcommand handlers
|
|
172
279
|
network.command.ts Network subcommand handlers
|
|
280
|
+
contact.command.ts Contact subcommand handlers
|
|
281
|
+
scrape.command.ts URL scraping command handler
|
|
282
|
+
sync.command.ts Context sync command handler
|
|
173
283
|
output/ Terminal formatting, colors, markdown renderer
|
|
174
284
|
api.client.ts Typed HTTP client for the protocol API
|
|
175
285
|
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 (
|
|
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
|
-
|
|
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,84 +1515,330 @@ 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
|
|
1364
|
-
index opportunity list --status <s>
|
|
1365
|
-
index opportunity list --limit <n>
|
|
1366
|
-
index opportunity show <id>
|
|
1367
|
-
index opportunity accept <id>
|
|
1368
|
-
index opportunity reject <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
|
-
|
|
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
|
+
if (options.introduce) {
|
|
1692
|
+
const userA = options.introduce;
|
|
1693
|
+
const userB = options.positionals?.[0];
|
|
1694
|
+
if (!userB) {
|
|
1695
|
+
error("Usage: index opportunity discover --introduce <userA> <userB>", 1);
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const hint = options.positionals?.slice(1).join(" ") || undefined;
|
|
1699
|
+
await discoverIntroduction(client, userA, userB, hint, options.json);
|
|
1700
|
+
} else if (options.target) {
|
|
1701
|
+
info("Discovering opportunities...");
|
|
1702
|
+
const result = await client.callTool("create_opportunities", {
|
|
1703
|
+
targetUserId: options.target,
|
|
1704
|
+
searchQuery: query
|
|
1705
|
+
});
|
|
1706
|
+
if (options.json) {
|
|
1707
|
+
console.log(JSON.stringify(result));
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (!result.success) {
|
|
1711
|
+
error(result.error ?? "Discovery failed", 1);
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
success("Discovery complete.");
|
|
1715
|
+
const data = result.data;
|
|
1716
|
+
if (data?.message)
|
|
1717
|
+
dim(` ${data.message}`);
|
|
1718
|
+
} else {
|
|
1719
|
+
info("Discovering opportunities...");
|
|
1720
|
+
const result = await client.callTool("create_opportunities", { searchQuery: query });
|
|
1721
|
+
if (options.json) {
|
|
1722
|
+
console.log(JSON.stringify(result));
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (!result.success) {
|
|
1726
|
+
error(result.error ?? "Discovery failed", 1);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
success("Discovery complete.");
|
|
1730
|
+
const data = result.data;
|
|
1731
|
+
if (data?.message)
|
|
1732
|
+
dim(` ${data.message}`);
|
|
1733
|
+
}
|
|
1399
1734
|
return;
|
|
1735
|
+
}
|
|
1400
1736
|
default:
|
|
1401
1737
|
error(`Unknown subcommand: ${subcommand}`, 1);
|
|
1402
1738
|
return;
|
|
1403
1739
|
}
|
|
1404
1740
|
}
|
|
1405
|
-
async function opportunityList(client, status2, limit) {
|
|
1741
|
+
async function opportunityList(client, status2, limit, json) {
|
|
1406
1742
|
const opportunities = await client.listOpportunities({ status: status2, limit });
|
|
1743
|
+
if (json) {
|
|
1744
|
+
console.log(JSON.stringify(opportunities));
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1407
1747
|
heading("Opportunities");
|
|
1408
1748
|
opportunityTable(opportunities);
|
|
1409
1749
|
console.log();
|
|
1410
1750
|
}
|
|
1411
|
-
async function opportunityShow(client, id) {
|
|
1751
|
+
async function opportunityShow(client, id, json) {
|
|
1412
1752
|
const opportunity = await client.getOpportunity(id);
|
|
1753
|
+
if (json) {
|
|
1754
|
+
console.log(JSON.stringify(opportunity));
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1413
1757
|
opportunityCard(opportunity);
|
|
1414
1758
|
}
|
|
1415
|
-
async function opportunityStatusUpdate(client, id, status2) {
|
|
1416
|
-
await client.updateOpportunityStatus(id, status2);
|
|
1759
|
+
async function opportunityStatusUpdate(client, id, status2, json) {
|
|
1760
|
+
const result = await client.updateOpportunityStatus(id, status2);
|
|
1761
|
+
if (json) {
|
|
1762
|
+
console.log(JSON.stringify(result));
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1417
1765
|
const label = status2 === "accepted" ? "accepted" : "rejected";
|
|
1418
1766
|
success(`Opportunity ${label}.`);
|
|
1419
1767
|
}
|
|
1768
|
+
async function discoverIntroduction(client, userA, userB, hint, json) {
|
|
1769
|
+
info("Gathering data for introduction...");
|
|
1770
|
+
const [membershipsA, membershipsB] = await Promise.all([
|
|
1771
|
+
client.callTool("read_index_memberships", { userId: userA }),
|
|
1772
|
+
client.callTool("read_index_memberships", { userId: userB })
|
|
1773
|
+
]);
|
|
1774
|
+
if (!membershipsA.success || !membershipsB.success) {
|
|
1775
|
+
const err = membershipsA.error ?? membershipsB.error ?? "Failed to read memberships";
|
|
1776
|
+
if (json) {
|
|
1777
|
+
console.log(JSON.stringify({ success: false, error: err }));
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
error(err, 1);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const indexesA = membershipsA.data?.memberships ?? membershipsA.data?.indexes ?? [];
|
|
1784
|
+
const indexesB = membershipsB.data?.memberships ?? membershipsB.data?.indexes ?? [];
|
|
1785
|
+
const idsA = new Set(indexesA.map((m) => m.indexId));
|
|
1786
|
+
const shared = indexesB.filter((m) => idsA.has(m.indexId));
|
|
1787
|
+
if (shared.length === 0) {
|
|
1788
|
+
const err = "No shared indexes found between these users. They must be members of at least one common network.";
|
|
1789
|
+
if (json) {
|
|
1790
|
+
console.log(JSON.stringify({ success: false, error: err }));
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
error(err, 1);
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const sharedIndexId = shared[0].indexId;
|
|
1797
|
+
dim(` Found shared network: ${sharedIndexId}`);
|
|
1798
|
+
const [profileA, profileB, intentsA, intentsB] = await Promise.all([
|
|
1799
|
+
client.callTool("read_user_profiles", { userId: userA }),
|
|
1800
|
+
client.callTool("read_user_profiles", { userId: userB }),
|
|
1801
|
+
client.callTool("read_intents", { userId: userA, indexId: sharedIndexId }),
|
|
1802
|
+
client.callTool("read_intents", { userId: userB, indexId: sharedIndexId })
|
|
1803
|
+
]);
|
|
1804
|
+
const extractProfile = (result2) => {
|
|
1805
|
+
if (!result2.success || !result2.data)
|
|
1806
|
+
return;
|
|
1807
|
+
const d = result2.data;
|
|
1808
|
+
if (d.profile)
|
|
1809
|
+
return d.profile;
|
|
1810
|
+
const profiles = d.profiles;
|
|
1811
|
+
return profiles?.[0]?.profile;
|
|
1812
|
+
};
|
|
1813
|
+
const extractIntents = (result2) => {
|
|
1814
|
+
if (!result2.success || !result2.data)
|
|
1815
|
+
return;
|
|
1816
|
+
const d = result2.data;
|
|
1817
|
+
return d.intents?.map((i) => ({ intentId: i.intentId ?? i.id ?? "", payload: i.payload, summary: i.summary }));
|
|
1818
|
+
};
|
|
1819
|
+
const entities = [
|
|
1820
|
+
{ userId: userA, profile: extractProfile(profileA), intents: extractIntents(intentsA), indexId: sharedIndexId },
|
|
1821
|
+
{ userId: userB, profile: extractProfile(profileB), intents: extractIntents(intentsB), indexId: sharedIndexId }
|
|
1822
|
+
];
|
|
1823
|
+
dim(" Profiles and intents gathered. Creating introduction...");
|
|
1824
|
+
const result = await client.callTool("create_opportunities", {
|
|
1825
|
+
partyUserIds: [userA, userB],
|
|
1826
|
+
entities,
|
|
1827
|
+
...hint ? { hint } : {}
|
|
1828
|
+
});
|
|
1829
|
+
if (json) {
|
|
1830
|
+
console.log(JSON.stringify(result));
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (!result.success) {
|
|
1834
|
+
error(result.error ?? "Introduction failed", 1);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
success("Introduction created.");
|
|
1838
|
+
const data = result.data;
|
|
1839
|
+
if (data?.message)
|
|
1840
|
+
dim(` ${data.message}`);
|
|
1841
|
+
}
|
|
1420
1842
|
|
|
1421
1843
|
// src/network.command.ts
|
|
1422
1844
|
var NETWORK_HELP = `
|
|
@@ -1425,6 +1847,9 @@ Network Commands:
|
|
|
1425
1847
|
index network create <name> Create a new network
|
|
1426
1848
|
index network create <name> --prompt Create with a description
|
|
1427
1849
|
index network show <id|key> Show network details and members
|
|
1850
|
+
index network update <id> [--title <t>] [--prompt <p>]
|
|
1851
|
+
Update a network
|
|
1852
|
+
index network delete <id> Delete a network
|
|
1428
1853
|
index network join <id|key> Join a public network
|
|
1429
1854
|
index network leave <id|key> Leave a network
|
|
1430
1855
|
index network invite <id|key> <email> Invite a user by email
|
|
@@ -1450,6 +1875,12 @@ async function handleNetwork(client, subcommand, positionals, options) {
|
|
|
1450
1875
|
case "leave":
|
|
1451
1876
|
await networkLeave(client, positionals[0]);
|
|
1452
1877
|
return;
|
|
1878
|
+
case "update":
|
|
1879
|
+
await networkUpdate(client, positionals[0], options);
|
|
1880
|
+
return;
|
|
1881
|
+
case "delete":
|
|
1882
|
+
await networkDelete(client, positionals[0]);
|
|
1883
|
+
return;
|
|
1453
1884
|
case "invite":
|
|
1454
1885
|
await networkInvite(client, positionals[0], positionals[1]);
|
|
1455
1886
|
return;
|
|
@@ -1505,6 +1936,39 @@ async function networkLeave(client, id) {
|
|
|
1505
1936
|
await client.leaveNetwork(id);
|
|
1506
1937
|
success("Left network.");
|
|
1507
1938
|
}
|
|
1939
|
+
async function networkUpdate(client, id, options) {
|
|
1940
|
+
if (!id) {
|
|
1941
|
+
error("Usage: index network update <id> [--title <t>] [--prompt <p>]", 1);
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
const settings = {};
|
|
1945
|
+
if (options.title)
|
|
1946
|
+
settings.title = options.title;
|
|
1947
|
+
if (options.prompt)
|
|
1948
|
+
settings.prompt = options.prompt;
|
|
1949
|
+
if (Object.keys(settings).length === 0) {
|
|
1950
|
+
error("Provide at least --title or --prompt. Usage: index network update <id> [--title <t>] [--prompt <p>]", 1);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
const result = await client.callTool("update_index", { indexId: id, settings });
|
|
1954
|
+
if (!result.success) {
|
|
1955
|
+
error(result.error ?? "Network update failed", 1);
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
success("Network updated.");
|
|
1959
|
+
}
|
|
1960
|
+
async function networkDelete(client, id) {
|
|
1961
|
+
if (!id) {
|
|
1962
|
+
error("Usage: index network delete <id>", 1);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
const result = await client.callTool("delete_index", { indexId: id });
|
|
1966
|
+
if (!result.success) {
|
|
1967
|
+
error(result.error ?? "Network deletion failed", 1);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
success("Network deleted.");
|
|
1971
|
+
}
|
|
1508
1972
|
async function networkInvite(client, id, email) {
|
|
1509
1973
|
if (!id || !email) {
|
|
1510
1974
|
error("Usage: index network invite <id> <email>", 1);
|
|
@@ -1891,6 +2355,175 @@ async function handleStreamError(response) {
|
|
|
1891
2355
|
error(body.error ?? `HTTP ${response.status}`, 1);
|
|
1892
2356
|
}
|
|
1893
2357
|
|
|
2358
|
+
// src/contact.command.ts
|
|
2359
|
+
var CONTACT_HELP = `
|
|
2360
|
+
Usage:
|
|
2361
|
+
index contact list List your contacts
|
|
2362
|
+
index contact add <email> [--name <n>] Add a contact by email
|
|
2363
|
+
index contact remove <email> Remove a contact
|
|
2364
|
+
index contact import --gmail Import contacts from Gmail
|
|
2365
|
+
`;
|
|
2366
|
+
async function handleContact(client, subcommand, positionals, options) {
|
|
2367
|
+
if (!subcommand) {
|
|
2368
|
+
if (options.json) {
|
|
2369
|
+
console.log(JSON.stringify({ error: "No subcommand provided" }));
|
|
2370
|
+
} else {
|
|
2371
|
+
console.log(CONTACT_HELP);
|
|
2372
|
+
}
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
switch (subcommand) {
|
|
2376
|
+
case "list": {
|
|
2377
|
+
const result = await client.callTool("list_contacts", {});
|
|
2378
|
+
if (options.json) {
|
|
2379
|
+
console.log(JSON.stringify(result));
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
if (!result.success) {
|
|
2383
|
+
error(result.error ?? "Failed to list contacts", 1);
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
const data = result.data;
|
|
2387
|
+
heading("Contacts");
|
|
2388
|
+
contactTable(data.contacts);
|
|
2389
|
+
dim(`
|
|
2390
|
+
${data.count} contact${data.count !== 1 ? "s" : ""}`);
|
|
2391
|
+
console.log();
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
case "add": {
|
|
2395
|
+
const email = positionals[0];
|
|
2396
|
+
if (!email) {
|
|
2397
|
+
error("Missing email. Usage: index contact add <email>", 1);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
const result = await client.callTool("add_contact", { email, name: options.name });
|
|
2401
|
+
if (options.json) {
|
|
2402
|
+
console.log(JSON.stringify(result));
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (!result.success) {
|
|
2406
|
+
error(result.error ?? "Failed to add contact", 1);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
const data = result.data;
|
|
2410
|
+
success(data.message);
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
case "remove": {
|
|
2414
|
+
const email = positionals[0];
|
|
2415
|
+
if (!email) {
|
|
2416
|
+
error("Missing email. Usage: index contact remove <email>", 1);
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
const listResult = await client.callTool("list_contacts", {});
|
|
2420
|
+
if (!listResult.success) {
|
|
2421
|
+
error("Failed to resolve contact", 1);
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const contacts = listResult.data.contacts;
|
|
2425
|
+
const match = contacts.find((c) => c.email.toLowerCase() === email.toLowerCase());
|
|
2426
|
+
if (!match) {
|
|
2427
|
+
error(`No contact found with email: ${email}`, 1);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
const result = await client.callTool("remove_contact", { contactUserId: match.userId });
|
|
2431
|
+
if (options.json) {
|
|
2432
|
+
console.log(JSON.stringify(result));
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (!result.success) {
|
|
2436
|
+
error(result.error ?? "Failed to remove contact", 1);
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
success(`Removed ${email} from contacts.`);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
case "import": {
|
|
2443
|
+
if (options.gmail) {
|
|
2444
|
+
const result = await client.callTool("import_gmail_contacts", {});
|
|
2445
|
+
if (options.json) {
|
|
2446
|
+
console.log(JSON.stringify(result));
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
if (!result.success) {
|
|
2450
|
+
error(result.error ?? "Failed to import Gmail contacts", 1);
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const data = result.data;
|
|
2454
|
+
success(data.message);
|
|
2455
|
+
} else {
|
|
2456
|
+
error("Specify import source: --gmail", 1);
|
|
2457
|
+
}
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
default: {
|
|
2461
|
+
if (options.json) {
|
|
2462
|
+
console.log(JSON.stringify({ error: `Unknown subcommand: ${subcommand}` }));
|
|
2463
|
+
} else {
|
|
2464
|
+
error(`Unknown subcommand: ${subcommand}`);
|
|
2465
|
+
console.log(CONTACT_HELP);
|
|
2466
|
+
}
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/scrape.command.ts
|
|
2473
|
+
async function handleScrape(client, positionals, options) {
|
|
2474
|
+
const url = positionals[0];
|
|
2475
|
+
if (!url) {
|
|
2476
|
+
error("Usage: index scrape <url> [--objective <text>]", 1);
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
info(`Scraping ${url}...`);
|
|
2480
|
+
const result = await client.callTool("scrape_url", { url, objective: options.objective });
|
|
2481
|
+
if (options.json) {
|
|
2482
|
+
console.log(JSON.stringify(result));
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (!result.success) {
|
|
2486
|
+
error(result.error ?? "Scrape failed", 1);
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
const data = result.data;
|
|
2490
|
+
heading(`Content from ${data.url}`);
|
|
2491
|
+
console.log(data.content);
|
|
2492
|
+
dim(`
|
|
2493
|
+
${data.contentLength} characters extracted`);
|
|
2494
|
+
console.log();
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// src/sync.command.ts
|
|
2498
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2499
|
+
import { homedir as homedir2 } from "node:os";
|
|
2500
|
+
import { join as join2 } from "node:path";
|
|
2501
|
+
async function handleSync(client, options) {
|
|
2502
|
+
if (!options.json)
|
|
2503
|
+
info("Syncing context...");
|
|
2504
|
+
const [profile, networks, intents, contacts] = await Promise.all([
|
|
2505
|
+
client.callTool("read_user_profiles", {}),
|
|
2506
|
+
client.callTool("read_indexes", {}),
|
|
2507
|
+
client.callTool("read_intents", {}),
|
|
2508
|
+
client.callTool("list_contacts", {})
|
|
2509
|
+
]);
|
|
2510
|
+
const context = {
|
|
2511
|
+
syncedAt: new Date().toISOString(),
|
|
2512
|
+
profile: profile.success ? profile.data : null,
|
|
2513
|
+
networks: networks.success ? networks.data : null,
|
|
2514
|
+
intents: intents.success ? intents.data : null,
|
|
2515
|
+
contacts: contacts.success ? contacts.data : null
|
|
2516
|
+
};
|
|
2517
|
+
if (options.json) {
|
|
2518
|
+
console.log(JSON.stringify(context));
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const dir = join2(homedir2(), ".index");
|
|
2522
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2523
|
+
writeFileSync(join2(dir, "context.json"), JSON.stringify(context, null, 2), { mode: 384 });
|
|
2524
|
+
success("Context synced to ~/.index/context.json");
|
|
2525
|
+
}
|
|
2526
|
+
|
|
1894
2527
|
// src/main.ts
|
|
1895
2528
|
var DEFAULT_API_URL = "https://protocol.index.network";
|
|
1896
2529
|
var DEFAULT_APP_URL = "https://index.network";
|
|
@@ -1914,22 +2547,43 @@ Usage:
|
|
|
1914
2547
|
index profile Show your profile
|
|
1915
2548
|
index profile show <user-id> Show another user's profile
|
|
1916
2549
|
index profile sync Regenerate your profile
|
|
2550
|
+
index profile search <query> Search user profiles
|
|
2551
|
+
index profile create [--linkedin <url>] [--github <url>] [--twitter <url>]
|
|
2552
|
+
Create your profile from social URLs
|
|
2553
|
+
index profile update <action> [--details <text>]
|
|
2554
|
+
Update your profile
|
|
1917
2555
|
index intent list [--archived] [--limit <n>] List your signals
|
|
1918
2556
|
index intent show <id> Show signal details
|
|
1919
2557
|
index intent create <content> Create a signal from natural language
|
|
2558
|
+
index intent update <id> <content> Update a signal's description
|
|
1920
2559
|
index intent archive <id> Archive a signal
|
|
2560
|
+
index intent link <id> <network-id> Link a signal to a network
|
|
2561
|
+
index intent unlink <id> <network-id> Unlink a signal from a network
|
|
2562
|
+
index intent links <id> Show linked networks for a signal
|
|
1921
2563
|
index opportunity list List your opportunities
|
|
1922
2564
|
index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
|
|
1923
2565
|
index opportunity list --limit <n> Limit results
|
|
1924
2566
|
index opportunity show <id> Show full opportunity details
|
|
1925
2567
|
index opportunity accept <id> Accept an opportunity
|
|
1926
2568
|
index opportunity reject <id> Reject an opportunity
|
|
2569
|
+
index opportunity discover <query> Discover opportunities by search query
|
|
1927
2570
|
index network list List your networks
|
|
1928
2571
|
index network create <name> Create a new network
|
|
1929
2572
|
index network show <id> Show network details and members
|
|
2573
|
+
index network update <id> [--title <t>] [--prompt <p>]
|
|
2574
|
+
Update a network
|
|
2575
|
+
index network delete <id> Delete a network
|
|
1930
2576
|
index network join <id> Join a public network
|
|
1931
2577
|
index network leave <id> Leave a network
|
|
1932
2578
|
index network invite <id> <email> Invite a user by email
|
|
2579
|
+
index contact list List your contacts
|
|
2580
|
+
index contact add <email> Add a contact by email
|
|
2581
|
+
index contact remove <email> Remove a contact
|
|
2582
|
+
index contact import --gmail Import contacts from Gmail
|
|
2583
|
+
index scrape <url> Extract content from a URL
|
|
2584
|
+
index onboarding complete Mark onboarding as complete
|
|
2585
|
+
index sync Sync context to ~/.index/context.json
|
|
2586
|
+
index sync --json Output synced context to stdout
|
|
1933
2587
|
index --help Show this help message
|
|
1934
2588
|
index --version Show version
|
|
1935
2589
|
|
|
@@ -1941,6 +2595,17 @@ Options:
|
|
|
1941
2595
|
--archived Include archived signals (intent list)
|
|
1942
2596
|
--status <status> Filter opportunities by status
|
|
1943
2597
|
--limit <n> Limit number of results
|
|
2598
|
+
--json Output raw JSON instead of formatted text
|
|
2599
|
+
--name <name> Name for contact add
|
|
2600
|
+
--gmail Import source flag for contact import
|
|
2601
|
+
--objective <text> Objective for scrape command
|
|
2602
|
+
--target <uid> Target user for opportunity discover
|
|
2603
|
+
--introduce <uid> Source user for introduction discovery
|
|
2604
|
+
--linkedin <url> LinkedIn URL for profile create
|
|
2605
|
+
--github <url> GitHub URL for profile create
|
|
2606
|
+
--twitter <url> Twitter URL for profile create
|
|
2607
|
+
--title <text> Title for network update
|
|
2608
|
+
--details <text> Details for profile update
|
|
1944
2609
|
`;
|
|
1945
2610
|
async function requireAuth(apiUrlOverride) {
|
|
1946
2611
|
const store = new CredentialStore;
|
|
@@ -2023,26 +2688,39 @@ async function main() {
|
|
|
2023
2688
|
const client = await requireAuth(args.apiUrl);
|
|
2024
2689
|
switch (args.command) {
|
|
2025
2690
|
case "profile":
|
|
2026
|
-
await handleProfile(client, args.subcommand, args.userId ? [args.userId] : []
|
|
2691
|
+
await handleProfile(client, args.subcommand, args.subcommand === "search" || args.subcommand === "update" ? args.positionals ?? [] : args.userId ? [args.userId] : [], {
|
|
2692
|
+
json: args.json,
|
|
2693
|
+
linkedin: args.linkedin,
|
|
2694
|
+
github: args.github,
|
|
2695
|
+
twitter: args.twitter,
|
|
2696
|
+
details: args.details
|
|
2697
|
+
});
|
|
2027
2698
|
return;
|
|
2028
2699
|
case "intent":
|
|
2029
2700
|
await handleIntent(client, args.subcommand, {
|
|
2030
2701
|
intentId: args.intentId,
|
|
2031
2702
|
intentContent: args.intentContent,
|
|
2032
2703
|
archived: args.archived,
|
|
2033
|
-
limit: args.limit
|
|
2704
|
+
limit: args.limit,
|
|
2705
|
+
json: args.json,
|
|
2706
|
+
targetId: args.targetId
|
|
2034
2707
|
});
|
|
2035
2708
|
return;
|
|
2036
2709
|
case "opportunity":
|
|
2037
2710
|
await handleOpportunity(client, args.subcommand, {
|
|
2038
2711
|
targetId: args.targetId,
|
|
2039
2712
|
status: args.status,
|
|
2040
|
-
limit: args.limit
|
|
2713
|
+
limit: args.limit,
|
|
2714
|
+
json: args.json,
|
|
2715
|
+
positionals: args.positionals,
|
|
2716
|
+
target: args.target,
|
|
2717
|
+
introduce: args.introduce
|
|
2041
2718
|
});
|
|
2042
2719
|
return;
|
|
2043
2720
|
case "network":
|
|
2044
2721
|
await handleNetwork(client, args.subcommand, args.positionals ?? [], {
|
|
2045
|
-
prompt: args.prompt
|
|
2722
|
+
prompt: args.prompt,
|
|
2723
|
+
title: args.title
|
|
2046
2724
|
});
|
|
2047
2725
|
return;
|
|
2048
2726
|
case "conversation":
|
|
@@ -2052,6 +2730,34 @@ async function main() {
|
|
|
2052
2730
|
message: args.message
|
|
2053
2731
|
});
|
|
2054
2732
|
return;
|
|
2733
|
+
case "contact":
|
|
2734
|
+
await handleContact(client, args.subcommand, args.positionals ?? [], {
|
|
2735
|
+
json: args.json,
|
|
2736
|
+
name: args.name,
|
|
2737
|
+
gmail: args.gmail
|
|
2738
|
+
});
|
|
2739
|
+
return;
|
|
2740
|
+
case "scrape":
|
|
2741
|
+
await handleScrape(client, args.positionals ?? [], {
|
|
2742
|
+
json: args.json,
|
|
2743
|
+
objective: args.objective
|
|
2744
|
+
});
|
|
2745
|
+
return;
|
|
2746
|
+
case "onboarding":
|
|
2747
|
+
if (args.subcommand === "complete") {
|
|
2748
|
+
const result = await client.callTool("complete_onboarding", {});
|
|
2749
|
+
if (!result.success) {
|
|
2750
|
+
error(result.error ?? "Onboarding completion failed", 1);
|
|
2751
|
+
} else {
|
|
2752
|
+
success("Onboarding completed.");
|
|
2753
|
+
}
|
|
2754
|
+
} else {
|
|
2755
|
+
error("Usage: index onboarding complete", 1);
|
|
2756
|
+
}
|
|
2757
|
+
return;
|
|
2758
|
+
case "sync":
|
|
2759
|
+
await handleSync(client, { json: args.json });
|
|
2760
|
+
return;
|
|
2055
2761
|
}
|
|
2056
2762
|
}
|
|
2057
2763
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indexnetwork/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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.
|
|
24
|
-
"@indexnetwork/cli-linux-arm64": "0.
|
|
25
|
-
"@indexnetwork/cli-darwin-x64": "0.
|
|
26
|
-
"@indexnetwork/cli-darwin-arm64": "0.
|
|
23
|
+
"@indexnetwork/cli-linux-x64": "0.9.1",
|
|
24
|
+
"@indexnetwork/cli-linux-arm64": "0.9.1",
|
|
25
|
+
"@indexnetwork/cli-darwin-x64": "0.9.1",
|
|
26
|
+
"@indexnetwork/cli-darwin-arm64": "0.9.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/bun": "latest"
|