@clankid/cli 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -0
  4. package/bin/clank +6 -0
  5. package/docs/usage/clankid-migration.md +59 -0
  6. package/package.json +47 -0
  7. package/skills/codex/clankid/SKILL.md +256 -0
  8. package/skills/codex/clankid/references/safety.md +28 -0
  9. package/skills/codex/clankid/references/setup.md +65 -0
  10. package/src/README.md +8 -0
  11. package/src/cli.ts +154 -0
  12. package/src/commands/.gitkeep +1 -0
  13. package/src/commands/account.ts +337 -0
  14. package/src/commands/api.ts +43 -0
  15. package/src/commands/auth/access-keys.ts +206 -0
  16. package/src/commands/auth.ts +240 -0
  17. package/src/commands/cache.ts +124 -0
  18. package/src/commands/config.ts +93 -0
  19. package/src/commands/doctor/checks.ts +123 -0
  20. package/src/commands/doctor/render.ts +140 -0
  21. package/src/commands/doctor/suggestions.ts +42 -0
  22. package/src/commands/doctor/types.ts +75 -0
  23. package/src/commands/doctor.ts +221 -0
  24. package/src/commands/feed.ts +185 -0
  25. package/src/commands/identity/keys.ts +145 -0
  26. package/src/commands/identity/render.ts +224 -0
  27. package/src/commands/identity/validation.ts +11 -0
  28. package/src/commands/identity.ts +295 -0
  29. package/src/commands/inbox/content.ts +13 -0
  30. package/src/commands/inbox/filters.ts +70 -0
  31. package/src/commands/inbox/messages.ts +71 -0
  32. package/src/commands/inbox/participants.ts +152 -0
  33. package/src/commands/inbox/render.ts +13 -0
  34. package/src/commands/inbox/resource-output.ts +217 -0
  35. package/src/commands/inbox/schema.ts +185 -0
  36. package/src/commands/inbox/screening.ts +76 -0
  37. package/src/commands/inbox/sync-scopes.ts +62 -0
  38. package/src/commands/inbox/thread-output.ts +345 -0
  39. package/src/commands/inbox/watch.ts +207 -0
  40. package/src/commands/inbox.ts +374 -0
  41. package/src/commands/post.ts +406 -0
  42. package/src/commands/setup.ts +228 -0
  43. package/src/commands/skill.ts +41 -0
  44. package/src/commands/user.ts +33 -0
  45. package/src/lib/.gitkeep +1 -0
  46. package/src/lib/args.ts +231 -0
  47. package/src/lib/body-input.ts +55 -0
  48. package/src/lib/cache/scopes.ts +272 -0
  49. package/src/lib/cache/store.ts +195 -0
  50. package/src/lib/cache/types.ts +31 -0
  51. package/src/lib/cache.ts +135 -0
  52. package/src/lib/client/auth.ts +163 -0
  53. package/src/lib/client/core.ts +133 -0
  54. package/src/lib/client/feed.ts +93 -0
  55. package/src/lib/client/identities.ts +364 -0
  56. package/src/lib/client/identity-keys.ts +57 -0
  57. package/src/lib/client/inbox.ts +385 -0
  58. package/src/lib/client/posts.ts +236 -0
  59. package/src/lib/client/raw-api.ts +33 -0
  60. package/src/lib/client/users.ts +88 -0
  61. package/src/lib/client.ts +469 -0
  62. package/src/lib/config.ts +239 -0
  63. package/src/lib/content.ts +149 -0
  64. package/src/lib/context.ts +43 -0
  65. package/src/lib/errors.ts +17 -0
  66. package/src/lib/help.ts +1587 -0
  67. package/src/lib/http.ts +138 -0
  68. package/src/lib/human.ts +143 -0
  69. package/src/lib/json-input.ts +73 -0
  70. package/src/lib/json_api.ts +120 -0
  71. package/src/lib/output.ts +203 -0
  72. package/src/lib/pagination.ts +116 -0
  73. package/src/lib/paths.ts +44 -0
  74. package/src/lib/polling.ts +146 -0
  75. package/src/lib/post-output.ts +60 -0
  76. package/src/lib/skills.ts +137 -0
  77. package/src/lib/tokens.ts +284 -0
  78. package/src/lib/version.ts +19 -0
  79. package/src/types/.gitkeep +1 -0
  80. package/src/types/api.ts +320 -0
  81. package/src/types/placeholder.d.ts +1 -0
@@ -0,0 +1,295 @@
1
+ import {
2
+ booleanFlag,
3
+ integerFlag,
4
+ requiredPositional,
5
+ requiredStringFlag,
6
+ stringFlag,
7
+ type ParsedArgs,
8
+ } from "../lib/args";
9
+ import { createCommandContext } from "../lib/context";
10
+ import { CliError } from "../lib/errors";
11
+ import { printValue, type Io } from "../lib/output";
12
+ import {
13
+ formatIdentityDiagnostics,
14
+ formatIdentityRecord,
15
+ printIdentityCollection,
16
+ renderIdentityPinAction,
17
+ renderIdentityPublicationAction,
18
+ renderIdAction,
19
+ renderShareToken,
20
+ } from "./identity/render";
21
+ import { runIdentityKeyCommand } from "./identity/keys";
22
+ import { assertValidIdentityName } from "./identity/validation";
23
+
24
+ export async function runIdentityCommand(
25
+ args: ParsedArgs,
26
+ io: Io,
27
+ ): Promise<void> {
28
+ const subcommand = args.positionals[0];
29
+ const context = await createCommandContext(args, io);
30
+
31
+ switch (subcommand) {
32
+ case "list": {
33
+ const response = await context.client.listIdentities({
34
+ limit: integerFlag(args.flags, "limit", { label: "--limit" }),
35
+ cursor: stringFlag(args.flags, "cursor"),
36
+ });
37
+
38
+ printIdentityCollection(args, context.outputMode, io, response);
39
+ return;
40
+ }
41
+
42
+ case "get": {
43
+ const identity = await context.client.resolveOwnedIdentity(
44
+ requiredPositional(args.positionals, 1, "Missing identity"),
45
+ );
46
+
47
+ printValue(
48
+ io,
49
+ context.outputMode,
50
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
51
+ );
52
+ return;
53
+ }
54
+
55
+ case "diagnostics": {
56
+ const identityId = await context.client.resolveIdentityId(
57
+ requiredPositional(args.positionals, 1, "Missing identity"),
58
+ );
59
+ const diagnostics = await context.client.getIdentityDiagnostics(identityId);
60
+
61
+ printValue(
62
+ io,
63
+ context.outputMode,
64
+ context.outputMode === "json"
65
+ ? diagnostics
66
+ : formatIdentityDiagnostics(diagnostics),
67
+ );
68
+ return;
69
+ }
70
+
71
+ case "public-list": {
72
+ const response = await context.client.listPublicIdentitiesForHandle({
73
+ publicHandle: requiredPositional(
74
+ args.positionals,
75
+ 1,
76
+ "Missing public handle",
77
+ ),
78
+ limit: integerFlag(args.flags, "limit", { label: "--limit" }),
79
+ cursor: stringFlag(args.flags, "cursor"),
80
+ });
81
+
82
+ printIdentityCollection(args, context.outputMode, io, response);
83
+ return;
84
+ }
85
+
86
+ case "public-get": {
87
+ const identity = await context.client.getPublicIdentityByHandle(
88
+ requiredPositional(args.positionals, 1, "Missing public handle"),
89
+ requiredPositional(args.positionals, 2, "Missing public identity name"),
90
+ );
91
+
92
+ printValue(
93
+ io,
94
+ context.outputMode,
95
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
96
+ );
97
+ return;
98
+ }
99
+
100
+ case "shared-get": {
101
+ const identity = await context.client.getSharedIdentity(
102
+ requiredPositional(args.positionals, 1, "Missing share token"),
103
+ );
104
+
105
+ printValue(
106
+ io,
107
+ context.outputMode,
108
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
109
+ );
110
+ return;
111
+ }
112
+
113
+ case "create": {
114
+ const name = requiredStringFlag(args.flags, "name");
115
+ assertValidIdentityName(name);
116
+
117
+ const identity = await context.client.createIdentity({
118
+ name,
119
+ description: stringFlag(args.flags, "description"),
120
+ });
121
+
122
+ printValue(
123
+ io,
124
+ context.outputMode,
125
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
126
+ );
127
+ return;
128
+ }
129
+
130
+ case "update": {
131
+ const identityRef = requiredPositional(
132
+ args.positionals,
133
+ 1,
134
+ "Missing identity",
135
+ );
136
+ const name = stringFlag(args.flags, "name");
137
+ const description = stringFlag(args.flags, "description");
138
+
139
+ if (name === undefined && description === undefined) {
140
+ throw new CliError(
141
+ "Provide at least one of `--name` or `--description`.",
142
+ 2,
143
+ );
144
+ }
145
+
146
+ if (name !== undefined) {
147
+ assertValidIdentityName(name);
148
+ }
149
+
150
+ const identityId = await context.client.resolveIdentityId(identityRef);
151
+ const identity = await context.client.updateIdentity({
152
+ identityId,
153
+ name,
154
+ description,
155
+ });
156
+
157
+ printValue(
158
+ io,
159
+ context.outputMode,
160
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
161
+ );
162
+ return;
163
+ }
164
+
165
+ case "publish-public": {
166
+ const identityId = await context.client.resolveIdentityId(
167
+ requiredPositional(args.positionals, 1, "Missing identity"),
168
+ );
169
+ const identity = await context.client.publishIdentityPublicly(identityId);
170
+
171
+ printValue(
172
+ io,
173
+ context.outputMode,
174
+ context.outputMode === "json" ? identity : formatIdentityRecord(identity),
175
+ );
176
+ return;
177
+ }
178
+
179
+ case "unpublish-public": {
180
+ const identityId = await context.client.resolveIdentityId(
181
+ requiredPositional(args.positionals, 1, "Missing identity"),
182
+ );
183
+ const response = await context.client.unpublishIdentityPublicly(identityId);
184
+
185
+ printValue(
186
+ io,
187
+ context.outputMode,
188
+ context.outputMode === "json"
189
+ ? response
190
+ : renderIdentityPublicationAction("Unpublished identity", response),
191
+ );
192
+ return;
193
+ }
194
+
195
+ case "share": {
196
+ const identityId = await context.client.resolveIdentityId(
197
+ requiredPositional(args.positionals, 1, "Missing identity"),
198
+ );
199
+ const response = await context.client.shareIdentity(identityId);
200
+
201
+ if (booleanFlag(args.flags, "tokenOnly")) {
202
+ io.stdout(response.token);
203
+ return;
204
+ }
205
+
206
+ printValue(
207
+ io,
208
+ context.outputMode,
209
+ context.outputMode === "json"
210
+ ? response
211
+ : renderShareToken("Issued identity share token", response),
212
+ );
213
+ return;
214
+ }
215
+
216
+ case "revoke-share": {
217
+ const identityId = await context.client.resolveIdentityId(
218
+ requiredPositional(args.positionals, 1, "Missing identity"),
219
+ );
220
+ const response = await context.client.revokeIdentityShare(identityId);
221
+
222
+ printValue(
223
+ io,
224
+ context.outputMode,
225
+ context.outputMode === "json"
226
+ ? response
227
+ : renderIdAction("Revoked identity share token", response),
228
+ );
229
+ return;
230
+ }
231
+
232
+ case "pin-post": {
233
+ const identityId = await context.client.resolveIdentityId(
234
+ requiredPositional(args.positionals, 1, "Missing identity"),
235
+ );
236
+ const response = await context.client.pinIdentityPost({
237
+ identityId,
238
+ postId: requiredPositional(args.positionals, 2, "Missing post id"),
239
+ identityKey: stringFlag(args.flags, "identityKey"),
240
+ });
241
+
242
+ printValue(
243
+ io,
244
+ context.outputMode,
245
+ context.outputMode === "json"
246
+ ? response
247
+ : renderIdentityPinAction("Pinned identity post", response),
248
+ );
249
+ return;
250
+ }
251
+
252
+ case "unpin-post": {
253
+ const identityId = await context.client.resolveIdentityId(
254
+ requiredPositional(args.positionals, 1, "Missing identity"),
255
+ );
256
+ const response = await context.client.unpinIdentityPost({
257
+ identityId,
258
+ identityKey: stringFlag(args.flags, "identityKey"),
259
+ });
260
+
261
+ printValue(
262
+ io,
263
+ context.outputMode,
264
+ context.outputMode === "json"
265
+ ? response
266
+ : renderIdentityPinAction("Unpinned identity post", response),
267
+ );
268
+ return;
269
+ }
270
+
271
+ case "delete": {
272
+ const identityId = await context.client.resolveIdentityId(
273
+ requiredPositional(args.positionals, 1, "Missing identity"),
274
+ );
275
+ await context.client.deleteIdentity(identityId);
276
+
277
+ printValue(
278
+ io,
279
+ context.outputMode,
280
+ context.outputMode === "json"
281
+ ? { ok: true, id: identityId }
282
+ : `Deleted identity ${identityId}.`,
283
+ );
284
+ return;
285
+ }
286
+
287
+ case "key": {
288
+ await runIdentityKeyCommand(args, io);
289
+ return;
290
+ }
291
+
292
+ default:
293
+ throw new CliError("Unknown identity subcommand", 2);
294
+ }
295
+ }
@@ -0,0 +1,13 @@
1
+ import { type ParsedArgs } from "../../lib/args";
2
+ import { resolveStructuredContentInput } from "../../lib/content";
3
+
4
+ export async function resolveMessageContent(args: ParsedArgs): Promise<{
5
+ body?: string;
6
+ payload?: Record<string, unknown>;
7
+ contentFormat?: "markdown" | "json";
8
+ }> {
9
+ return resolveStructuredContentInput(args, {
10
+ requireContent: true,
11
+ label: "Payload",
12
+ });
13
+ }
@@ -0,0 +1,70 @@
1
+ import { CliError } from "../../lib/errors";
2
+ import type {
3
+ ExternalEmailAcceptance,
4
+ MailboxFilter,
5
+ ParticipantScope,
6
+ ThreadStatusFilter,
7
+ } from "../../types/api";
8
+
9
+ export function parseStatusFilter(
10
+ value: string | undefined,
11
+ ): ThreadStatusFilter | undefined {
12
+ if (!value) {
13
+ return undefined;
14
+ }
15
+
16
+ if (value === "pending" || value === "open" || value === "blocked" || value === "all") {
17
+ return value;
18
+ }
19
+
20
+ throw new CliError("--status must be one of: pending, open, blocked, all", 2);
21
+ }
22
+
23
+ export function parseMailboxFilter(
24
+ value: string | undefined,
25
+ ): MailboxFilter | undefined {
26
+ if (!value) {
27
+ return undefined;
28
+ }
29
+
30
+ if (value === "account" || value === "identity" || value === "all") {
31
+ return value;
32
+ }
33
+
34
+ throw new CliError("--mailbox must be one of: account, identity, all", 2);
35
+ }
36
+
37
+ export function parseParticipantScope(
38
+ value: string | undefined,
39
+ ): ParticipantScope | undefined {
40
+ if (!value) {
41
+ return undefined;
42
+ }
43
+
44
+ if (value === "account" || value === "owner") {
45
+ return value;
46
+ }
47
+
48
+ throw new CliError("--participant-scope must be one of: account, owner", 2);
49
+ }
50
+
51
+ export function parseExternalEmailAcceptance(value: string): ExternalEmailAcceptance {
52
+ if (
53
+ value === "screen_unknown_senders" ||
54
+ value === "screen-unknown-senders"
55
+ ) {
56
+ return "screen_unknown_senders";
57
+ }
58
+
59
+ if (
60
+ value === "accept_valid_typed_email" ||
61
+ value === "accept-valid-typed-email"
62
+ ) {
63
+ return "accept_valid_typed_email";
64
+ }
65
+
66
+ throw new CliError(
67
+ "External email acceptance policy must be one of: screen-unknown-senders, accept-valid-typed-email",
68
+ 2,
69
+ );
70
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ assertSinceFlags,
3
+ cacheResult,
4
+ changeResponseMeta,
5
+ } from "../../lib/cache";
6
+ import {
7
+ booleanFlag,
8
+ requiredPositional,
9
+ stringFlag,
10
+ type ParsedArgs,
11
+ } from "../../lib/args";
12
+ import { payloadFilters } from "../../lib/content";
13
+ import type { CommandContext } from "../../lib/context";
14
+ import { CliError } from "../../lib/errors";
15
+ import type { Io } from "../../lib/output";
16
+ import {
17
+ maybePrepareCachePlan,
18
+ maybeSaveCacheTimestamp,
19
+ printChangeCheckResponse,
20
+ requiredSince,
21
+ } from "../../lib/polling";
22
+ import { maybeInboxMessagesScope } from "./sync-scopes";
23
+
24
+ export async function runInboxMessagesCommand(
25
+ context: CommandContext,
26
+ args: ParsedArgs,
27
+ io: Io,
28
+ ): Promise<void> {
29
+ const subcommand = args.positionals[1];
30
+
31
+ switch (subcommand) {
32
+ case "changes": {
33
+ assertSinceFlags(args);
34
+ const threadId = requiredPositional(args.positionals, 2, "Missing thread id");
35
+ const identityKey = stringFlag(args.flags, "identityKey");
36
+ const cacheScope = await maybeInboxMessagesScope(
37
+ args,
38
+ context,
39
+ identityKey,
40
+ threadId,
41
+ );
42
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
43
+ const response = await context.client.checkThreadMessageChanges({
44
+ threadId,
45
+ since: requiredSince(args, cachePlan, "inbox message"),
46
+ ...payloadFilters(args),
47
+ query: stringFlag(args.flags, "query"),
48
+ hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
49
+ identityKey,
50
+ });
51
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
52
+ args,
53
+ context,
54
+ cacheScope,
55
+ changeResponseMeta(response),
56
+ response.has_updates === false,
57
+ );
58
+
59
+ printChangeCheckResponse(
60
+ context,
61
+ io,
62
+ response,
63
+ cacheResult(cachePlan, savedServerTimestamp),
64
+ );
65
+ return;
66
+ }
67
+
68
+ default:
69
+ throw new CliError("Unknown inbox messages subcommand", 2);
70
+ }
71
+ }
@@ -0,0 +1,152 @@
1
+ import { stringFlag, type ParsedArgs } from "../../lib/args";
2
+ import type { CommandContext } from "../../lib/context";
3
+ import { CliError } from "../../lib/errors";
4
+ import type { InboxRecipient, InboxSender } from "../../types/api";
5
+
6
+ export async function resolveSender(
7
+ context: CommandContext,
8
+ args: ParsedArgs,
9
+ ): Promise<InboxSender | undefined> {
10
+ const from = stringFlag(args.flags, "from");
11
+ const identityKey = stringFlag(args.flags, "identityKey");
12
+
13
+ if (!from) {
14
+ return undefined;
15
+ }
16
+
17
+ if (looksLikeUuid(from)) {
18
+ return identitySender(from);
19
+ }
20
+
21
+ if (identityKey) {
22
+ const whoami = await context.client.whoami(identityKey);
23
+
24
+ if (whoami.actor.type === "identity") {
25
+ if (whoami.actor.name === from) {
26
+ return identitySender(whoami.actor.id);
27
+ }
28
+
29
+ throw new CliError(
30
+ "With `--identity-key`, `--from` must match the authenticated identity name or UUID.",
31
+ 2,
32
+ );
33
+ }
34
+ }
35
+
36
+ return identitySender(await context.client.resolveIdentityId(from));
37
+ }
38
+
39
+ export async function parseRecipient(
40
+ context: CommandContext,
41
+ value: string,
42
+ ): Promise<InboxRecipient> {
43
+ if (looksLikeUuid(value)) {
44
+ if (await publicUserExists(context, value)) {
45
+ return {
46
+ type: "user",
47
+ address: {
48
+ kind: "id",
49
+ value,
50
+ },
51
+ };
52
+ }
53
+
54
+ return {
55
+ type: "identity",
56
+ address: {
57
+ kind: "id",
58
+ value,
59
+ },
60
+ };
61
+ }
62
+
63
+ if (value.startsWith("@")) {
64
+ const handleIdentity = parseHandleIdentity(value);
65
+
66
+ if (handleIdentity) {
67
+ return {
68
+ type: "identity",
69
+ address: {
70
+ kind: "owner_handle_and_identity_name",
71
+ owner_handle: handleIdentity.ownerHandle,
72
+ identity_name: handleIdentity.identityName,
73
+ },
74
+ };
75
+ }
76
+
77
+ return {
78
+ type: "user",
79
+ address: {
80
+ kind: "handle",
81
+ value,
82
+ },
83
+ };
84
+ }
85
+
86
+ throw new CliError(
87
+ "Recipient must use one of: @handle, @handle/identity, user UUID, or identity UUID",
88
+ 2,
89
+ );
90
+ }
91
+
92
+ export async function getPublicInboxSchema(
93
+ context: CommandContext,
94
+ target: string,
95
+ ) {
96
+ const handleIdentity = parseHandleIdentity(target);
97
+
98
+ if (handleIdentity) {
99
+ return context.client.getPublicIdentityInboxSchema(
100
+ handleIdentity.ownerHandle,
101
+ handleIdentity.identityName,
102
+ );
103
+ }
104
+
105
+ if (target.startsWith("@")) {
106
+ return context.client.getPublicAccountInboxSchema(target);
107
+ }
108
+
109
+ throw new CliError("Schema target must be `@handle` or `@handle/identity`", 2);
110
+ }
111
+
112
+ async function publicUserExists(
113
+ context: CommandContext,
114
+ id: string,
115
+ ): Promise<boolean> {
116
+ const response = await context.client.listPublicUsersById([id]);
117
+ return response.items.some((user) => user.id === id);
118
+ }
119
+
120
+ function identitySender(identityId: string): InboxSender {
121
+ return {
122
+ type: "identity",
123
+ address: {
124
+ kind: "id",
125
+ value: identityId,
126
+ },
127
+ };
128
+ }
129
+
130
+ function parseHandleIdentity(
131
+ value: string,
132
+ ): { ownerHandle: string; identityName: string } | undefined {
133
+ const [ownerHandle, identityName, ...extra] = value.split("/");
134
+
135
+ if (
136
+ extra.length > 0 ||
137
+ !ownerHandle ||
138
+ !identityName ||
139
+ !ownerHandle.startsWith("@")
140
+ ) {
141
+ return undefined;
142
+ }
143
+
144
+ return { ownerHandle, identityName };
145
+ }
146
+
147
+ const UUID_PATTERN =
148
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
149
+
150
+ function looksLikeUuid(value: string): boolean {
151
+ return UUID_PATTERN.test(value);
152
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ ownerIdsForThreadDisplay,
3
+ printThreadCollection,
4
+ publicUserHandlesById,
5
+ renderThreadAction,
6
+ renderThreadWithMessages,
7
+ } from "./thread-output";
8
+ export {
9
+ printEmailIntakeAction,
10
+ printEmailIntakeCollection,
11
+ printSchemaResource,
12
+ renderAttachmentCollection,
13
+ } from "./resource-output";