Skip to content

Commit 8cded8e

Browse files
committed
refactor: remove the contact-import and ghost-user creation paths
Contacts are established solely by accepting an opportunity, which already writes the mutual `contact` memberships in both users' personal networks (OpportunityService → upsertContactMembership, on accept and start-chat). Every other way of creating a contact — and the ghost users it minted — is removed. Read/search/remove of existing contacts is unchanged. This is the code half. Deleting the 14 ghost users still in production, and dropping `users.is_ghost` plus the remaining `isGhost` plumbing, follow as separate changes; nothing here can create a new ghost. - protocol: the whole `integrations` capability (`src/integrations/`), its five public types and the `import_gmail_contacts` tool. - protocol: `import_contacts` and `add_contact`; `ContactServiceAdapter` narrows to list/remove/search. - protocol: `generateInviteMessage` — it required `recipient.isGhost`, so it can never fire once ghosts are gone. Its endpoint (`GET /opportunities/:id/invite-message`) and web caller go with it; the other three invite-message call sites use local templates and are untouched. - protocol: `contactsEnabled`, `integration` and `integrationImporter` from `ToolDeps`; `createGhostUser` from the database interface. - api: `POST /users/contacts`, `POST /integrations/:toolkit/import`, `ContactService.addContact/resolveUsers/importContacts`, both `createGhostUser*` adapters, the Gmail/Slack contact fetchers, `contacts-feature.ts`, `contacts.guard.ts`, `lib/dedup/`, `types/integrations.types.ts`. - The `CONTACTS_ENABLED` and `CONTACT_DEDUP_STRATEGY` flags: with the write paths gone they gated nothing. Remove from `startup.env.ts` and `.env.example` (both still set in Railway — delete there after deploy). - cli: `index contact add` and `index contact import --gmail`, plus the now unused `--name` and `--gmail` flags. - web: the add-contact path in AccessTab (add-by-email now only offered for invitable networks), `usersService.addContact`, the Gmail/Slack import in IntegrationsTab, and the ghost branch in `useOpportunityActions`. - `contactService.removeContact` and `DELETE /users/contacts/:contactId` stay. The hard delete doubles as the reverse opt-out that OpportunityService honours via `restore: false`; routing personal-network removal through `networkService.removeMember` would drop that and silently re-add people. - Retired tool names are kept in the MCP forbidden-name lists as regression guards. - protocol: build clean, `architecture:check` clean, `test:isolated` 2202 pass / 0 fail, full suite at parity with the pre-change baseline (96 pre-existing failures, all database-gated; no new failures). - api: typecheck and lint clean; CI's hermetic gates green (83 pinning + 33 CLI guard specs). The database-backed suite is unchanged and still gated. - cli: 351 pass / 0 fail, build and lint clean. - web: build and lint clean. - `check:lockfile-versions` clean.
1 parent 2617cf2 commit 8cded8e

11 files changed

Lines changed: 16 additions & 107 deletions

File tree

npm/darwin-arm64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indexnetwork/cli-darwin-arm64",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"description": "Platform-specific binary for @indexnetwork/cli (macOS arm64)",
55
"license": "MIT",
66
"os": [

npm/darwin-x64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indexnetwork/cli-darwin-x64",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"description": "Platform-specific binary for @indexnetwork/cli (macOS x64)",
55
"license": "MIT",
66
"os": [

npm/linux-arm64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indexnetwork/cli-linux-arm64",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"description": "Platform-specific binary for @indexnetwork/cli (Linux arm64)",
55
"license": "MIT",
66
"os": [

npm/linux-x64/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indexnetwork/cli-linux-x64",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"description": "Platform-specific binary for @indexnetwork/cli (Linux x64)",
55
"license": "MIT",
66
"os": [

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indexnetwork/cli",
3-
"version": "0.15.0",
3+
"version": "0.16.0",
44
"description": "Command-line interface for Index Network",
55
"license": "MIT",
66
"type": "module",
@@ -20,10 +20,10 @@
2020
"publish:all": "bun scripts/publish.ts"
2121
},
2222
"optionalDependencies": {
23-
"@indexnetwork/cli-linux-x64": "0.15.0",
24-
"@indexnetwork/cli-linux-arm64": "0.15.0",
25-
"@indexnetwork/cli-darwin-x64": "0.15.0",
26-
"@indexnetwork/cli-darwin-arm64": "0.15.0"
23+
"@indexnetwork/cli-linux-x64": "0.16.0",
24+
"@indexnetwork/cli-linux-arm64": "0.16.0",
25+
"@indexnetwork/cli-darwin-x64": "0.16.0",
26+
"@indexnetwork/cli-darwin-arm64": "0.16.0"
2727
},
2828
"devDependencies": {
2929
"@types/bun": "1.3.14"

src/args.parser.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ export interface ParsedCommand {
3636
prompt?: string;
3737
/** Output raw JSON instead of formatted text. */
3838
json?: boolean;
39-
/** Name for --name flag (e.g. contact add). */
40-
name?: string;
41-
/** Gmail import flag for contact import. */
42-
gmail?: boolean;
4339
/** Objective for --objective flag (e.g. scrape). */
4440
objective?: string;
4541
/** LinkedIn URL for profile create. */
@@ -158,12 +154,6 @@ export function parseArgs(args: string[]): ParsedCommand {
158154
} else if (arg === "--json") {
159155
result.json = true;
160156
i++;
161-
} else if (arg === "--name") {
162-
result.name = args[i + 1];
163-
i += 2;
164-
} else if (arg === "--gmail") {
165-
result.gmail = true;
166-
i++;
167157
} else if (arg === "--objective") {
168158
result.objective = args[i + 1];
169159
i += 2;

src/contact.command.ts

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
/**
22
* Contact command handlers for the Index CLI.
3-
* Implements: list, add, remove, import subcommands.
3+
* Implements: list and remove subcommands.
44
*/
55
import type { ApiClient } from "./api.client";
66
import * as output from "./output";
77

88
const CONTACT_HELP = `
99
Usage:
10-
index contact list List your contacts
11-
index contact add <email> [--name <n>] Add a contact by email
12-
index contact remove <email> Remove a contact
13-
index contact import --gmail Import contacts from Gmail
10+
index contact list List your contacts
11+
index contact remove <email> Remove a contact
1412
`;
1513

1614
/**
1715
* Route a contact subcommand to the appropriate handler.
1816
*
1917
* @param client - Authenticated API client.
20-
* @param subcommand - The subcommand (list, add, remove, import).
18+
* @param subcommand - The subcommand (list, remove).
2119
* @param positionals - Positional arguments after the subcommand.
22-
* @param options - Additional options (json, name, gmail).
20+
* @param options - Additional options (json).
2321
*/
2422
export async function handleContact(
2523
client: ApiClient,
2624
subcommand: string | undefined,
2725
positionals: string[],
28-
options: { json?: boolean; name?: string; gmail?: boolean },
26+
options: { json?: boolean },
2927
): Promise<void> {
3028
if (!subcommand) {
3129
if (options.json) {
@@ -48,16 +46,6 @@ export async function handleContact(
4846
console.log();
4947
return;
5048
}
51-
case "add": {
52-
const email = positionals[0];
53-
if (!email) { output.error("Missing email. Usage: index contact add <email>", 1); return; }
54-
const result = await client.callTool("add_contact", { email, name: options.name });
55-
if (options.json) { console.log(JSON.stringify(result)); return; }
56-
if (!result.success) { output.error(result.error ?? "Failed to add contact", 1); return; }
57-
const data = result.data as { message: string };
58-
output.success(data.message);
59-
return;
60-
}
6149
case "remove": {
6250
const email = positionals[0];
6351
if (!email) { output.error("Missing email. Usage: index contact remove <email>", 1); return; }
@@ -73,18 +61,6 @@ export async function handleContact(
7361
output.success(`Removed ${email} from contacts.`);
7462
return;
7563
}
76-
case "import": {
77-
if (options.gmail) {
78-
const result = await client.callTool("import_gmail_contacts", {});
79-
if (options.json) { console.log(JSON.stringify(result)); return; }
80-
if (!result.success) { output.error(result.error ?? "Failed to import Gmail contacts", 1); return; }
81-
const data = result.data as { message: string };
82-
output.success(data.message);
83-
} else {
84-
output.error("Specify import source: --gmail", 1);
85-
}
86-
return;
87-
}
8864
default: {
8965
if (options.json) {
9066
console.log(JSON.stringify({ error: `Unknown subcommand: ${subcommand}` }));

src/main.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,6 @@ function renderHelp(): void {
123123
"--limit <n>",
124124
"--since <date>",
125125
"--json",
126-
"--name <name>",
127-
"--gmail",
128126
"--objective <text>",
129127
"--linkedin <url>",
130128
"--github <url>",
@@ -173,7 +171,7 @@ function renderHelp(): void {
173171
helpRowCmd(cmdLW, "conversation", "list · with · show · send · stream"),
174172
helpRowCmd(cmdLW, "network", "list · create · show · update · delete"),
175173
helpRowCont(cmdLW, "join · leave · invite"),
176-
helpRowCmd(cmdLW, "contact", "list · add · remove · import"),
174+
helpRowCmd(cmdLW, "contact", "list · remove"),
177175
"",
178176
helpRowCmd(cmdLW, "scrape", "extract content from a URL"),
179177
helpRowCmd(cmdLW, "sync", "download your context locally"),
@@ -188,8 +186,6 @@ function renderHelp(): void {
188186
helpRowDim(optLW, "--limit <n>", "limit number of results"),
189187
helpRowDim(optLW, "--since <date>", "filter by ISO date or duration"),
190188
helpRowDim(optLW, "--json", "output raw JSON"),
191-
helpRowDim(optLW, "--name <name>", "name for contact add"),
192-
helpRowDim(optLW, "--gmail", "import contacts from Gmail"),
193189
helpRowDim(optLW, "--objective <text>", "objective for scrape command"),
194190
helpRowDim(optLW, "--linkedin <url>", "LinkedIn URL for profile create"),
195191
helpRowDim(optLW, "--github <url>", "GitHub URL for profile create"),
@@ -391,8 +387,6 @@ async function main(): Promise<void> {
391387
case "contact":
392388
await handleContact(client, args.subcommand, args.positionals ?? [], {
393389
json: args.json,
394-
name: args.name,
395-
gmail: args.gmail,
396390
});
397391
return;
398392
case "scrape":

src/output/base.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,7 @@ const TOOL_DESCRIPTIONS: Record<string, string> = {
123123
update_opportunity: "Updating opportunity status...",
124124
scrape_url: "Reading content from URL...",
125125
read_docs: "Looking up documentation...",
126-
import_gmail_contacts: "Importing Gmail contacts...",
127-
import_contacts: "Importing contacts...",
128126
list_contacts: "Listing your contacts...",
129-
add_contact: "Adding contact...",
130127
remove_contact: "Removing contact...",
131128
};
132129

tests/args.parser.spec.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,34 +92,12 @@ describe("parseArgs", () => {
9292
expect(result.subcommand).toBe("list");
9393
});
9494

95-
it("parses contact add with email", () => {
96-
const result = parseArgs(["contact", "add", "foo@bar.com"]);
97-
expect(result.command).toBe("contact");
98-
expect(result.subcommand).toBe("add");
99-
expect(result.positionals).toEqual(["foo@bar.com"]);
100-
});
101-
102-
it("parses contact add with --name", () => {
103-
const result = parseArgs(["contact", "add", "foo@bar.com", "--name", "John"]);
104-
expect(result.command).toBe("contact");
105-
expect(result.subcommand).toBe("add");
106-
expect(result.positionals).toEqual(["foo@bar.com"]);
107-
expect(result.name).toBe("John");
108-
});
109-
11095
it("parses contact remove", () => {
11196
const result = parseArgs(["contact", "remove", "foo@bar.com"]);
11297
expect(result.command).toBe("contact");
11398
expect(result.subcommand).toBe("remove");
11499
expect(result.positionals).toEqual(["foo@bar.com"]);
115100
});
116-
117-
it("parses contact import --gmail", () => {
118-
const result = parseArgs(["contact", "import", "--gmail"]);
119-
expect(result.command).toBe("contact");
120-
expect(result.subcommand).toBe("import");
121-
expect(result.gmail).toBe(true);
122-
});
123101
});
124102

125103
// ── Intent commands ────────────────────────────────────────────────

0 commit comments

Comments
 (0)