@xmtp/convos-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/dev.js +4 -0
- package/bin/run.js +4 -0
- package/dist/baseCommand.d.ts +46 -0
- package/dist/baseCommand.js +171 -0
- package/dist/commands/agent/serve.d.ts +67 -0
- package/dist/commands/agent/serve.js +662 -0
- package/dist/commands/conversation/add-members.d.ts +19 -0
- package/dist/commands/conversation/add-members.js +39 -0
- package/dist/commands/conversation/consent-state.d.ts +18 -0
- package/dist/commands/conversation/consent-state.js +24 -0
- package/dist/commands/conversation/download-attachment.d.ts +28 -0
- package/dist/commands/conversation/download-attachment.js +164 -0
- package/dist/commands/conversation/explode.d.ts +24 -0
- package/dist/commands/conversation/explode.js +156 -0
- package/dist/commands/conversation/info.d.ts +22 -0
- package/dist/commands/conversation/info.js +79 -0
- package/dist/commands/conversation/invite.d.ts +26 -0
- package/dist/commands/conversation/invite.js +137 -0
- package/dist/commands/conversation/lock.d.ts +24 -0
- package/dist/commands/conversation/lock.js +98 -0
- package/dist/commands/conversation/members.d.ts +22 -0
- package/dist/commands/conversation/members.js +39 -0
- package/dist/commands/conversation/messages.d.ts +31 -0
- package/dist/commands/conversation/messages.js +141 -0
- package/dist/commands/conversation/permissions.d.ts +18 -0
- package/dist/commands/conversation/permissions.js +33 -0
- package/dist/commands/conversation/profiles.d.ts +22 -0
- package/dist/commands/conversation/profiles.js +80 -0
- package/dist/commands/conversation/remove-members.d.ts +19 -0
- package/dist/commands/conversation/remove-members.js +36 -0
- package/dist/commands/conversation/send-attachment.d.ts +30 -0
- package/dist/commands/conversation/send-attachment.js +187 -0
- package/dist/commands/conversation/send-reaction.d.ts +21 -0
- package/dist/commands/conversation/send-reaction.js +38 -0
- package/dist/commands/conversation/send-remote-attachment.d.ts +30 -0
- package/dist/commands/conversation/send-remote-attachment.js +96 -0
- package/dist/commands/conversation/send-reply.d.ts +32 -0
- package/dist/commands/conversation/send-reply.js +170 -0
- package/dist/commands/conversation/send-text.d.ts +24 -0
- package/dist/commands/conversation/send-text.js +64 -0
- package/dist/commands/conversation/stream.d.ts +24 -0
- package/dist/commands/conversation/stream.js +81 -0
- package/dist/commands/conversation/sync.d.ts +18 -0
- package/dist/commands/conversation/sync.js +25 -0
- package/dist/commands/conversation/update-consent.d.ts +19 -0
- package/dist/commands/conversation/update-consent.js +35 -0
- package/dist/commands/conversation/update-description.d.ts +19 -0
- package/dist/commands/conversation/update-description.js +28 -0
- package/dist/commands/conversation/update-name.d.ts +19 -0
- package/dist/commands/conversation/update-name.js +29 -0
- package/dist/commands/conversation/update-profile.d.ts +24 -0
- package/dist/commands/conversation/update-profile.js +97 -0
- package/dist/commands/conversations/create.d.ts +26 -0
- package/dist/commands/conversations/create.js +165 -0
- package/dist/commands/conversations/join.d.ts +27 -0
- package/dist/commands/conversations/join.js +232 -0
- package/dist/commands/conversations/list.d.ts +20 -0
- package/dist/commands/conversations/list.js +109 -0
- package/dist/commands/conversations/process-join-requests.d.ts +26 -0
- package/dist/commands/conversations/process-join-requests.js +261 -0
- package/dist/commands/conversations/sync.d.ts +19 -0
- package/dist/commands/conversations/sync.js +50 -0
- package/dist/commands/identity/create.d.ts +21 -0
- package/dist/commands/identity/create.js +56 -0
- package/dist/commands/identity/info.d.ts +22 -0
- package/dist/commands/identity/info.js +63 -0
- package/dist/commands/identity/list.d.ts +19 -0
- package/dist/commands/identity/list.js +59 -0
- package/dist/commands/identity/remove.d.ts +23 -0
- package/dist/commands/identity/remove.js +51 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.js +91 -0
- package/dist/commands/reset.d.ts +17 -0
- package/dist/commands/reset.js +93 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +31 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +15 -0
- package/dist/utils/client.d.ts +8 -0
- package/dist/utils/client.js +58 -0
- package/dist/utils/config.d.ts +15 -0
- package/dist/utils/config.js +1 -0
- package/dist/utils/identities.d.ts +49 -0
- package/dist/utils/identities.js +92 -0
- package/dist/utils/invite.d.ts +70 -0
- package/dist/utils/invite.js +339 -0
- package/dist/utils/metadata.d.ts +39 -0
- package/dist/utils/metadata.js +180 -0
- package/dist/utils/mime.d.ts +2 -0
- package/dist/utils/mime.js +42 -0
- package/dist/utils/random.d.ts +5 -0
- package/dist/utils/random.js +19 -0
- package/dist/utils/upload.d.ts +14 -0
- package/dist/utils/upload.js +51 -0
- package/dist/utils/xmtp.d.ts +45 -0
- package/dist/utils/xmtp.js +298 -0
- package/oclif.manifest.json +5562 -0
- package/package.json +124 -0
- package/skills/convos-cli/SKILL.md +588 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { requireGroup } from "../../utils/xmtp.js";
|
|
3
|
+
import qrcode from "qrcode-terminal";
|
|
4
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
5
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
6
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
7
|
+
import { createInviteSlug } from "../../utils/invite.js";
|
|
8
|
+
import { parseAppData, serializeAppData } from "../../utils/metadata.js";
|
|
9
|
+
import { randomAlphanumeric } from "../../utils/random.js";
|
|
10
|
+
export default class ConversationInvite extends ConvosBaseCommand {
|
|
11
|
+
static description = `Generate an invite link for a conversation.
|
|
12
|
+
|
|
13
|
+
Creates a cryptographic invite slug that others can use to request
|
|
14
|
+
to join this conversation (per ADR 001).
|
|
15
|
+
|
|
16
|
+
The invite contains:
|
|
17
|
+
- An encrypted conversation ID (only the creator can decrypt)
|
|
18
|
+
- The creator's inbox ID (for routing the join request DM)
|
|
19
|
+
- An invite tag (for verification and revocation)
|
|
20
|
+
- Optional metadata (name, description)
|
|
21
|
+
- A secp256k1 ECDSA signature
|
|
22
|
+
|
|
23
|
+
Displays a QR code in the terminal that can be scanned by the
|
|
24
|
+
Convos app. The QR code encodes the full invite URL.
|
|
25
|
+
|
|
26
|
+
Invite URLs use the format:
|
|
27
|
+
- Dev: dev.convos.org/v2?i=<slug>
|
|
28
|
+
- Production: popup.convos.org/v2?i=<slug>`;
|
|
29
|
+
static examples = [
|
|
30
|
+
{
|
|
31
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id>",
|
|
32
|
+
description: "Generate an invite with QR code (default)",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --no-qr",
|
|
36
|
+
description: "Generate an invite without QR code",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --expires-in 3600",
|
|
40
|
+
description: "Generate an invite that expires in 1 hour",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --single-use --json",
|
|
44
|
+
description: "Generate a single-use invite with JSON output",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
static args = {
|
|
48
|
+
id: Args.string({ description: "The conversation ID", required: true }),
|
|
49
|
+
};
|
|
50
|
+
static flags = {
|
|
51
|
+
...ConvosBaseCommand.baseFlags,
|
|
52
|
+
qr: Flags.boolean({
|
|
53
|
+
description: "Display a QR code in the terminal",
|
|
54
|
+
default: true,
|
|
55
|
+
allowNo: true,
|
|
56
|
+
}),
|
|
57
|
+
"expires-in": Flags.integer({
|
|
58
|
+
description: "Invite expires in N seconds from now",
|
|
59
|
+
helpValue: "<seconds>",
|
|
60
|
+
}),
|
|
61
|
+
"single-use": Flags.boolean({
|
|
62
|
+
description: "Invite expires after first use",
|
|
63
|
+
default: false,
|
|
64
|
+
}),
|
|
65
|
+
"include-metadata": Flags.boolean({
|
|
66
|
+
description: "Include conversation name/description in invite",
|
|
67
|
+
default: true,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
async run() {
|
|
71
|
+
const { args, flags } = await this.parse(ConversationInvite);
|
|
72
|
+
const config = this.getConvosConfig();
|
|
73
|
+
const store = createIdentityStore();
|
|
74
|
+
const identity = store.getByConversationId(args.id);
|
|
75
|
+
if (!identity) {
|
|
76
|
+
this.error(`No identity found for conversation: ${args.id}`);
|
|
77
|
+
}
|
|
78
|
+
const client = await createClientForIdentity(identity, config);
|
|
79
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
80
|
+
if (!conversation) {
|
|
81
|
+
this.error(`Conversation not found: ${args.id}`);
|
|
82
|
+
}
|
|
83
|
+
const group = requireGroup(conversation);
|
|
84
|
+
// Get or create invite tag from appData (using proper protobuf metadata)
|
|
85
|
+
let appData = "";
|
|
86
|
+
try {
|
|
87
|
+
appData = group.appData ?? "";
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// No appData yet
|
|
91
|
+
}
|
|
92
|
+
let metadata = parseAppData(appData);
|
|
93
|
+
let inviteTag = metadata.tag;
|
|
94
|
+
if (!inviteTag) {
|
|
95
|
+
inviteTag = randomAlphanumeric(10);
|
|
96
|
+
metadata = { ...metadata, tag: inviteTag };
|
|
97
|
+
await group.updateAppData(serializeAppData(metadata));
|
|
98
|
+
}
|
|
99
|
+
const expiresAt = flags["expires-in"]
|
|
100
|
+
? new Date(Date.now() + flags["expires-in"] * 1000)
|
|
101
|
+
: undefined;
|
|
102
|
+
const slug = await createInviteSlug(args.id, client.inboxId, inviteTag, identity.walletKey, {
|
|
103
|
+
name: flags["include-metadata"] ? group.name || undefined : undefined,
|
|
104
|
+
description: flags["include-metadata"]
|
|
105
|
+
? group.description || undefined
|
|
106
|
+
: undefined,
|
|
107
|
+
expiresAt,
|
|
108
|
+
expiresAfterUse: flags["single-use"],
|
|
109
|
+
});
|
|
110
|
+
// Build invite URL based on environment
|
|
111
|
+
const env = config.env ?? "dev";
|
|
112
|
+
const baseUrl = env === "production"
|
|
113
|
+
? "https://popup.convos.org/v2"
|
|
114
|
+
: "https://dev.convos.org/v2";
|
|
115
|
+
const inviteUrl = `${baseUrl}?i=${encodeURIComponent(slug)}`;
|
|
116
|
+
// Display QR code unless --no-qr or --json
|
|
117
|
+
if (flags.qr && !flags.json) {
|
|
118
|
+
this.log(""); // blank line before QR
|
|
119
|
+
await new Promise((resolve) => {
|
|
120
|
+
qrcode.generate(inviteUrl, { small: true }, (code) => {
|
|
121
|
+
this.log(code);
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
this.log(` ${inviteUrl}\n`);
|
|
126
|
+
}
|
|
127
|
+
this.output({
|
|
128
|
+
conversationId: args.id,
|
|
129
|
+
slug,
|
|
130
|
+
url: inviteUrl,
|
|
131
|
+
tag: inviteTag,
|
|
132
|
+
env,
|
|
133
|
+
expiresAt: expiresAt?.toISOString() ?? null,
|
|
134
|
+
singleUse: flags["single-use"],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
2
|
+
export default class ConversationLock extends ConvosBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: {
|
|
5
|
+
command: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}[];
|
|
8
|
+
static args: {
|
|
9
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static flags: {
|
|
12
|
+
unlock: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
"log-level": import("@oclif/core/interfaces").OptionFlag<"off" | "error" | "warn" | "info" | "debug" | "trace" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
"structured-logging": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
"app-version": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
"env-file": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
env: import("@oclif/core/interfaces").OptionFlag<"local" | "dev" | "production" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
"gateway-host": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
};
|
|
23
|
+
run(): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { requireGroup } from "../../utils/xmtp.js";
|
|
3
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
4
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
5
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
6
|
+
import { parseAppData, serializeAppData } from "../../utils/metadata.js";
|
|
7
|
+
import { randomAlphanumeric } from "../../utils/random.js";
|
|
8
|
+
export default class ConversationLock extends ConvosBaseCommand {
|
|
9
|
+
static description = `Lock or unlock a conversation.
|
|
10
|
+
|
|
11
|
+
Locking prevents new members from joining (per ADR 006) by:
|
|
12
|
+
1. Rotating the invite tag (cryptographically invalidates all existing invites)
|
|
13
|
+
2. Setting the XMTP addMember permission to 'deny'
|
|
14
|
+
|
|
15
|
+
Unlocking restores the ability to add members. Previously shared
|
|
16
|
+
invites remain invalid — generate new ones after unlocking.
|
|
17
|
+
|
|
18
|
+
Only super admins can lock/unlock.`;
|
|
19
|
+
static examples = [
|
|
20
|
+
{
|
|
21
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id>",
|
|
22
|
+
description: "Lock a conversation",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --unlock",
|
|
26
|
+
description: "Unlock a conversation",
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
static args = {
|
|
30
|
+
id: Args.string({ description: "The conversation ID", required: true }),
|
|
31
|
+
};
|
|
32
|
+
static flags = {
|
|
33
|
+
...ConvosBaseCommand.baseFlags,
|
|
34
|
+
unlock: Flags.boolean({
|
|
35
|
+
description: "Unlock instead of locking",
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
force: Flags.boolean({
|
|
39
|
+
char: "f",
|
|
40
|
+
description: "Skip confirmation prompt",
|
|
41
|
+
default: false,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
async run() {
|
|
45
|
+
const { args, flags } = await this.parse(ConversationLock);
|
|
46
|
+
const config = this.getConvosConfig();
|
|
47
|
+
const store = createIdentityStore();
|
|
48
|
+
const identity = store.getByConversationId(args.id);
|
|
49
|
+
if (!identity)
|
|
50
|
+
this.error(`No identity found for conversation: ${args.id}`);
|
|
51
|
+
if (!flags.unlock) {
|
|
52
|
+
await this.confirmAction("Locking prevents new members from joining and invalidates all existing invites.", flags.force);
|
|
53
|
+
}
|
|
54
|
+
const client = await createClientForIdentity(identity, config);
|
|
55
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
56
|
+
if (!conversation)
|
|
57
|
+
this.error(`Conversation not found: ${args.id}`);
|
|
58
|
+
const group = requireGroup(conversation);
|
|
59
|
+
if (flags.unlock) {
|
|
60
|
+
// Rotate invite tag first so the new invite is ready before members can add others
|
|
61
|
+
let appData = "";
|
|
62
|
+
try {
|
|
63
|
+
appData = group.appData ?? "";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// No appData yet
|
|
67
|
+
}
|
|
68
|
+
const metadata = parseAppData(appData);
|
|
69
|
+
metadata.tag = randomAlphanumeric(10);
|
|
70
|
+
await group.updateAppData(serializeAppData(metadata));
|
|
71
|
+
await group.updatePermission(0 /* PermissionUpdateType.AddMember */, 0 /* PermissionPolicy.Allow */);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Step 1: Rotate the invite tag to invalidate all existing invites
|
|
75
|
+
let appData = "";
|
|
76
|
+
try {
|
|
77
|
+
appData = group.appData ?? "";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// No appData yet
|
|
81
|
+
}
|
|
82
|
+
const metadata = parseAppData(appData);
|
|
83
|
+
metadata.tag = randomAlphanumeric(10);
|
|
84
|
+
await group.updateAppData(serializeAppData(metadata));
|
|
85
|
+
// Step 2: Set addMember permission to deny
|
|
86
|
+
await group.updatePermission(0 /* PermissionUpdateType.AddMember */, 1 /* PermissionPolicy.Deny */);
|
|
87
|
+
}
|
|
88
|
+
const action = flags.unlock ? "unlocked" : "locked";
|
|
89
|
+
this.output({
|
|
90
|
+
success: true,
|
|
91
|
+
conversationId: args.id,
|
|
92
|
+
action,
|
|
93
|
+
message: flags.unlock
|
|
94
|
+
? "Conversation unlocked. Generate new invites to allow members."
|
|
95
|
+
: "Conversation locked. Invite tag rotated and all existing invites invalidated.",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
2
|
+
export default class ConversationMembers extends ConvosBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: {
|
|
5
|
+
command: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}[];
|
|
8
|
+
static args: {
|
|
9
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static flags: {
|
|
12
|
+
"log-level": import("@oclif/core/interfaces").OptionFlag<"off" | "error" | "warn" | "info" | "debug" | "trace" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"structured-logging": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
"app-version": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
"env-file": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
env: import("@oclif/core/interfaces").OptionFlag<"local" | "dev" | "production" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
"gateway-host": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
};
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Args } from "@oclif/core";
|
|
2
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
3
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
4
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
5
|
+
export default class ConversationMembers extends ConvosBaseCommand {
|
|
6
|
+
static description = `List members of a conversation.
|
|
7
|
+
|
|
8
|
+
Shows all members with their inbox IDs, identifiers, and permission levels.`;
|
|
9
|
+
static examples = [
|
|
10
|
+
{
|
|
11
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id>",
|
|
12
|
+
description: "List members",
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
static args = {
|
|
16
|
+
id: Args.string({ description: "The conversation ID", required: true }),
|
|
17
|
+
};
|
|
18
|
+
static flags = { ...ConvosBaseCommand.baseFlags };
|
|
19
|
+
async run() {
|
|
20
|
+
const { args } = await this.parse(ConversationMembers);
|
|
21
|
+
const config = this.getConvosConfig();
|
|
22
|
+
const store = createIdentityStore();
|
|
23
|
+
const identity = store.getByConversationId(args.id);
|
|
24
|
+
if (!identity) {
|
|
25
|
+
this.error(`No identity found for conversation: ${args.id}`);
|
|
26
|
+
}
|
|
27
|
+
const client = await createClientForIdentity(identity, config);
|
|
28
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
29
|
+
if (!conversation) {
|
|
30
|
+
this.error(`Conversation not found: ${args.id}`);
|
|
31
|
+
}
|
|
32
|
+
const members = await conversation.members();
|
|
33
|
+
this.output(members.map((m) => ({
|
|
34
|
+
inboxId: m.inboxId,
|
|
35
|
+
accountIdentifiers: m.accountIdentifiers,
|
|
36
|
+
permissionLevel: m.permissionLevel,
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
2
|
+
export default class ConversationMessages extends ConvosBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: {
|
|
5
|
+
command: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}[];
|
|
8
|
+
static args: {
|
|
9
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static flags: {
|
|
12
|
+
sync: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
direction: import("@oclif/core/interfaces").OptionFlag<"ascending" | "descending", import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
"sent-before": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
"sent-after": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
"delivery-status": import("@oclif/core/interfaces").OptionFlag<"unpublished" | "published" | "failed" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
kind: import("@oclif/core/interfaces").OptionFlag<"application" | "membership-change" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
"content-type": import("@oclif/core/interfaces").OptionFlag<("text" | "markdown" | "reaction" | "reply" | "attachment" | "remote-attachment" | "actions" | "custom" | "group-membership-change" | "group-updated" | "intent" | "leave-request" | "multi-remote-attachment" | "read-receipt" | "transaction-reference" | "wallet-send-calls")[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
"exclude-content-type": import("@oclif/core/interfaces").OptionFlag<("text" | "markdown" | "reaction" | "reply" | "attachment" | "remote-attachment" | "actions" | "custom" | "group-membership-change" | "group-updated" | "intent" | "leave-request" | "multi-remote-attachment" | "read-receipt" | "transaction-reference" | "wallet-send-calls")[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
|
+
"log-level": import("@oclif/core/interfaces").OptionFlag<"off" | "error" | "warn" | "info" | "debug" | "trace" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
|
+
"structured-logging": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
"app-version": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
+
"env-file": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
|
+
env: import("@oclif/core/interfaces").OptionFlag<"local" | "dev" | "production" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
"gateway-host": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
29
|
+
};
|
|
30
|
+
run(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
3
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
4
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
5
|
+
import { buildProfileMap, normalizeMessageContent, requireGroup, } from "../../utils/xmtp.js";
|
|
6
|
+
const contentTypeOptions = [
|
|
7
|
+
"actions", "attachment", "custom", "group-membership-change",
|
|
8
|
+
"group-updated", "intent", "leave-request", "markdown",
|
|
9
|
+
"multi-remote-attachment", "reaction", "read-receipt",
|
|
10
|
+
"remote-attachment", "reply", "text", "transaction-reference",
|
|
11
|
+
"wallet-send-calls",
|
|
12
|
+
];
|
|
13
|
+
const contentTypeMap = {
|
|
14
|
+
actions: 0 /* ContentType.Actions */, attachment: 1 /* ContentType.Attachment */,
|
|
15
|
+
custom: 2 /* ContentType.Custom */,
|
|
16
|
+
"group-membership-change": 3 /* ContentType.GroupMembershipChange */,
|
|
17
|
+
"group-updated": 4 /* ContentType.GroupUpdated */, intent: 5 /* ContentType.Intent */,
|
|
18
|
+
"leave-request": 6 /* ContentType.LeaveRequest */, markdown: 7 /* ContentType.Markdown */,
|
|
19
|
+
"multi-remote-attachment": 8 /* ContentType.MultiRemoteAttachment */,
|
|
20
|
+
reaction: 9 /* ContentType.Reaction */, "read-receipt": 10 /* ContentType.ReadReceipt */,
|
|
21
|
+
"remote-attachment": 11 /* ContentType.RemoteAttachment */, reply: 12 /* ContentType.Reply */,
|
|
22
|
+
text: 13 /* ContentType.Text */,
|
|
23
|
+
"transaction-reference": 14 /* ContentType.TransactionReference */,
|
|
24
|
+
"wallet-send-calls": 15 /* ContentType.WalletSendCalls */,
|
|
25
|
+
};
|
|
26
|
+
export default class ConversationMessages extends ConvosBaseCommand {
|
|
27
|
+
static description = `List messages in a conversation.
|
|
28
|
+
|
|
29
|
+
Retrieves messages using the per-conversation identity.
|
|
30
|
+
Supports filtering, pagination, and sorting.`;
|
|
31
|
+
static examples = [
|
|
32
|
+
{
|
|
33
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id>",
|
|
34
|
+
description: "List messages",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --sync --limit 10",
|
|
38
|
+
description: "Sync and show last 10 messages",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
static args = {
|
|
42
|
+
id: Args.string({ description: "The conversation ID", required: true }),
|
|
43
|
+
};
|
|
44
|
+
static flags = {
|
|
45
|
+
...ConvosBaseCommand.baseFlags,
|
|
46
|
+
sync: Flags.boolean({
|
|
47
|
+
description: "Sync from network before listing",
|
|
48
|
+
default: false,
|
|
49
|
+
}),
|
|
50
|
+
limit: Flags.integer({
|
|
51
|
+
description: "Maximum number of messages",
|
|
52
|
+
helpValue: "<number>",
|
|
53
|
+
}),
|
|
54
|
+
direction: Flags.option({
|
|
55
|
+
options: ["ascending", "descending"],
|
|
56
|
+
description: "Sort direction",
|
|
57
|
+
default: "descending",
|
|
58
|
+
})(),
|
|
59
|
+
"sent-before": Flags.string({
|
|
60
|
+
description: "Only messages sent before this timestamp (nanoseconds)",
|
|
61
|
+
helpValue: "<ns>",
|
|
62
|
+
}),
|
|
63
|
+
"sent-after": Flags.string({
|
|
64
|
+
description: "Only messages sent after this timestamp (nanoseconds)",
|
|
65
|
+
helpValue: "<ns>",
|
|
66
|
+
}),
|
|
67
|
+
"delivery-status": Flags.option({
|
|
68
|
+
options: ["unpublished", "published", "failed"],
|
|
69
|
+
description: "Filter by delivery status",
|
|
70
|
+
})(),
|
|
71
|
+
kind: Flags.option({
|
|
72
|
+
options: ["application", "membership-change"],
|
|
73
|
+
description: "Filter by message kind",
|
|
74
|
+
})(),
|
|
75
|
+
"content-type": Flags.option({
|
|
76
|
+
options: contentTypeOptions,
|
|
77
|
+
description: "Filter by content type (repeatable)",
|
|
78
|
+
multiple: true,
|
|
79
|
+
})(),
|
|
80
|
+
"exclude-content-type": Flags.option({
|
|
81
|
+
options: contentTypeOptions,
|
|
82
|
+
description: "Exclude content type (repeatable)",
|
|
83
|
+
multiple: true,
|
|
84
|
+
})(),
|
|
85
|
+
};
|
|
86
|
+
async run() {
|
|
87
|
+
const { args, flags } = await this.parse(ConversationMessages);
|
|
88
|
+
const config = this.getConvosConfig();
|
|
89
|
+
const store = createIdentityStore();
|
|
90
|
+
const identity = store.getByConversationId(args.id);
|
|
91
|
+
if (!identity) {
|
|
92
|
+
this.error(`No identity found for conversation: ${args.id}`);
|
|
93
|
+
}
|
|
94
|
+
const client = await createClientForIdentity(identity, config);
|
|
95
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
96
|
+
if (!conversation) {
|
|
97
|
+
this.error(`Conversation not found: ${args.id}`);
|
|
98
|
+
}
|
|
99
|
+
if (flags.sync)
|
|
100
|
+
await conversation.sync();
|
|
101
|
+
const options = {};
|
|
102
|
+
if (flags.limit !== undefined)
|
|
103
|
+
options.limit = flags.limit;
|
|
104
|
+
options.direction = flags.direction === "ascending"
|
|
105
|
+
? 0 /* SortDirection.Ascending */ : 1 /* SortDirection.Descending */;
|
|
106
|
+
options.sentBeforeNs = this.parseBigInt(flags["sent-before"], "sent-before");
|
|
107
|
+
options.sentAfterNs = this.parseBigInt(flags["sent-after"], "sent-after");
|
|
108
|
+
if (flags["delivery-status"]) {
|
|
109
|
+
const map = {
|
|
110
|
+
unpublished: 0 /* DeliveryStatus.Unpublished */,
|
|
111
|
+
published: 1 /* DeliveryStatus.Published */,
|
|
112
|
+
failed: 2 /* DeliveryStatus.Failed */,
|
|
113
|
+
};
|
|
114
|
+
options.deliveryStatus = map[flags["delivery-status"]];
|
|
115
|
+
}
|
|
116
|
+
if (flags.kind) {
|
|
117
|
+
const map = {
|
|
118
|
+
application: 0 /* GroupMessageKind.Application */,
|
|
119
|
+
"membership-change": 1 /* GroupMessageKind.MembershipChange */,
|
|
120
|
+
};
|
|
121
|
+
options.kind = map[flags.kind];
|
|
122
|
+
}
|
|
123
|
+
if (flags["content-type"]?.length) {
|
|
124
|
+
options.contentTypes = flags["content-type"].map((ct) => contentTypeMap[ct]);
|
|
125
|
+
}
|
|
126
|
+
if (flags["exclude-content-type"]?.length) {
|
|
127
|
+
options.excludeContentTypes = flags["exclude-content-type"].map((ct) => contentTypeMap[ct]);
|
|
128
|
+
}
|
|
129
|
+
const group = requireGroup(conversation);
|
|
130
|
+
const profiles = buildProfileMap(group.appData ?? "");
|
|
131
|
+
const messages = await conversation.messages(options);
|
|
132
|
+
this.output(messages.map((m) => ({
|
|
133
|
+
id: m.id,
|
|
134
|
+
senderInboxId: m.senderInboxId,
|
|
135
|
+
contentType: m.contentType,
|
|
136
|
+
content: normalizeMessageContent(m, profiles),
|
|
137
|
+
sentAt: m.sentAt.toISOString(),
|
|
138
|
+
deliveryStatus: m.deliveryStatus,
|
|
139
|
+
})));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
2
|
+
export default class ConversationPermissions extends ConvosBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
"log-level": import("@oclif/core/interfaces").OptionFlag<"off" | "error" | "warn" | "info" | "debug" | "trace" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
"structured-logging": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
"app-version": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
"env-file": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
env: import("@oclif/core/interfaces").OptionFlag<"local" | "dev" | "production" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"gateway-host": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Args } from "@oclif/core";
|
|
2
|
+
import { requireGroup } from "../../utils/xmtp.js";
|
|
3
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
4
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
5
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
6
|
+
export default class ConversationPermissions extends ConvosBaseCommand {
|
|
7
|
+
static description = `View permissions for a conversation.`;
|
|
8
|
+
static args = {
|
|
9
|
+
id: Args.string({ description: "The conversation ID", required: true }),
|
|
10
|
+
};
|
|
11
|
+
static flags = { ...ConvosBaseCommand.baseFlags };
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(ConversationPermissions);
|
|
14
|
+
const config = this.getConvosConfig();
|
|
15
|
+
const store = createIdentityStore();
|
|
16
|
+
const identity = store.getByConversationId(args.id);
|
|
17
|
+
if (!identity)
|
|
18
|
+
this.error(`No identity found for conversation: ${args.id}`);
|
|
19
|
+
const client = await createClientForIdentity(identity, config);
|
|
20
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
21
|
+
if (!conversation)
|
|
22
|
+
this.error(`Conversation not found: ${args.id}`);
|
|
23
|
+
const group = requireGroup(conversation);
|
|
24
|
+
const permissions = group.permissions();
|
|
25
|
+
this.output({
|
|
26
|
+
conversationId: args.id,
|
|
27
|
+
policyType: permissions.policyType,
|
|
28
|
+
policySet: permissions.policySet,
|
|
29
|
+
admins: group.listAdmins(),
|
|
30
|
+
superAdmins: group.listSuperAdmins(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
2
|
+
export default class Profiles extends ConvosBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: {
|
|
5
|
+
command: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}[];
|
|
8
|
+
static args: {
|
|
9
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static flags: {
|
|
12
|
+
"log-level": import("@oclif/core/interfaces").OptionFlag<"off" | "error" | "warn" | "info" | "debug" | "trace" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
"structured-logging": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
"app-version": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
"env-file": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
env: import("@oclif/core/interfaces").OptionFlag<"local" | "dev" | "production" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
"gateway-host": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
};
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Args } from "@oclif/core";
|
|
2
|
+
import { requireGroup } from "../../utils/xmtp.js";
|
|
3
|
+
import { ConvosBaseCommand } from "../../baseCommand.js";
|
|
4
|
+
import { createClientForIdentity } from "../../utils/client.js";
|
|
5
|
+
import { createIdentityStore } from "../../utils/identities.js";
|
|
6
|
+
import { parseAppData } from "../../utils/metadata.js";
|
|
7
|
+
export default class Profiles extends ConvosBaseCommand {
|
|
8
|
+
static description = `List member profiles in a conversation.
|
|
9
|
+
|
|
10
|
+
Shows display names and avatar URLs for all members who have set
|
|
11
|
+
a profile in this conversation. Profiles are stored in the group's
|
|
12
|
+
metadata and are visible to all members (ADR 005).
|
|
13
|
+
|
|
14
|
+
Members without a profile appear as anonymous with just their inbox ID.`;
|
|
15
|
+
static examples = [
|
|
16
|
+
{
|
|
17
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id>",
|
|
18
|
+
description: "List all member profiles",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
command: "<%= config.bin %> <%= command.id %> <conversation-id> --json",
|
|
22
|
+
description: "List profiles as JSON",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
static args = {
|
|
26
|
+
id: Args.string({
|
|
27
|
+
description: "The conversation ID",
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
static flags = {
|
|
32
|
+
...ConvosBaseCommand.baseFlags,
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args } = await this.parse(Profiles);
|
|
36
|
+
const config = this.getConvosConfig();
|
|
37
|
+
const store = createIdentityStore();
|
|
38
|
+
const identity = store.getByConversationId(args.id);
|
|
39
|
+
if (!identity) {
|
|
40
|
+
this.error(`No identity found for conversation ${args.id}`);
|
|
41
|
+
}
|
|
42
|
+
const client = await createClientForIdentity(identity, config);
|
|
43
|
+
await client.conversations.sync();
|
|
44
|
+
const conversation = await client.conversations.getConversationById(args.id);
|
|
45
|
+
if (!conversation) {
|
|
46
|
+
this.error(`Conversation ${args.id} not found`);
|
|
47
|
+
}
|
|
48
|
+
const group = requireGroup(conversation);
|
|
49
|
+
// Parse metadata
|
|
50
|
+
let appData = "";
|
|
51
|
+
try {
|
|
52
|
+
appData = group.appData ?? "";
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// No appData yet
|
|
56
|
+
}
|
|
57
|
+
const metadata = parseAppData(appData);
|
|
58
|
+
// Get all members for completeness
|
|
59
|
+
const members = await group.members();
|
|
60
|
+
const memberInboxIds = members.map((m) => m.inboxId);
|
|
61
|
+
// Build profile list — include all members, mark which have profiles
|
|
62
|
+
const profileList = memberInboxIds.map((inboxId) => {
|
|
63
|
+
const profile = metadata.profiles.find((p) => p.inboxId.toLowerCase() === inboxId.toLowerCase());
|
|
64
|
+
const isMe = inboxId === client.inboxId;
|
|
65
|
+
return {
|
|
66
|
+
inboxId,
|
|
67
|
+
name: profile?.name ?? null,
|
|
68
|
+
image: profile?.image ?? null,
|
|
69
|
+
hasProfile: !!profile,
|
|
70
|
+
isMe,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
this.output({
|
|
74
|
+
conversationId: args.id,
|
|
75
|
+
memberCount: memberInboxIds.length,
|
|
76
|
+
profileCount: metadata.profiles.length,
|
|
77
|
+
profiles: profileList,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|