@kiipu/cli 0.0.6 → 0.0.7
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/dist/commands/help.js +31 -2
- package/dist/commands/post.js +315 -21
- package/dist/index.js +31 -27
- package/dist/lib/kiipu-user-client.js +44 -0
- package/dist/lib/post-formatters.js +74 -0
- package/package.json +1 -1
package/dist/commands/help.js
CHANGED
|
@@ -11,6 +11,13 @@ export function getHelpResult(command) {
|
|
|
11
11
|
'Usage:',
|
|
12
12
|
' kiipu post create --content "<text>"',
|
|
13
13
|
' kiipu post create "<text>"',
|
|
14
|
+
' kiipu post list [--tag <tag>] [--sort <updatedAt|createdAt|title>] [--starred] [--deleted]',
|
|
15
|
+
' kiipu post search <query>',
|
|
16
|
+
' kiipu post search --query "<query>"',
|
|
17
|
+
' kiipu post show --id <postId>',
|
|
18
|
+
' kiipu post update --id <postId> --content "<text>" [--title "<title>"] [--visibility public|private] [--tags <a,b,c>]',
|
|
19
|
+
' kiipu post star --id <postId>',
|
|
20
|
+
' kiipu post pin --id <postId>',
|
|
14
21
|
' kiipu post delete --id <postId>',
|
|
15
22
|
' kiipu post restore --id <postId>',
|
|
16
23
|
' kiipu post purge --id <postId>',
|
|
@@ -20,16 +27,36 @@ export function getHelpResult(command) {
|
|
|
20
27
|
'',
|
|
21
28
|
'Actions:',
|
|
22
29
|
' create Create a new post',
|
|
30
|
+
' list List your published, starred, or deleted posts',
|
|
31
|
+
' search Search your published posts',
|
|
32
|
+
' show Show one post with full metadata',
|
|
33
|
+
' update Update post content and metadata',
|
|
34
|
+
' star Toggle a post star',
|
|
35
|
+
' pin Toggle a post pin',
|
|
23
36
|
' delete Soft-delete an existing post by explicit id',
|
|
24
37
|
' restore Restore a deleted post by explicit id',
|
|
25
38
|
' purge Permanently delete a post by explicit id',
|
|
26
39
|
'',
|
|
27
40
|
'Options:',
|
|
28
41
|
' --content "<text>" Post content for create',
|
|
29
|
-
' --id <postId> Required for delete, restore, and purge',
|
|
42
|
+
' --id <postId> Required for show, update, star, pin, delete, restore, and purge',
|
|
43
|
+
' --query "<query>" Search query for post search',
|
|
44
|
+
' --tag <tag> Filter post list results by tag',
|
|
45
|
+
' --sort <field> One of updatedAt, createdAt, or title',
|
|
46
|
+
' --starred List only starred posts',
|
|
47
|
+
' --deleted List only deleted posts',
|
|
48
|
+
' --title "<title>" Optional title for post update',
|
|
49
|
+
' --visibility <value> Optional visibility for post update',
|
|
50
|
+
' --tags <a,b,c> Optional comma-separated tags for post update',
|
|
30
51
|
'',
|
|
31
52
|
'Examples:',
|
|
32
53
|
' kiipu post create "Hello Kiipu"',
|
|
54
|
+
' kiipu post list --sort updatedAt',
|
|
55
|
+
' kiipu post search "roadmap"',
|
|
56
|
+
' kiipu post show --id 123',
|
|
57
|
+
' kiipu post update --id 123 --content "Updated text" --tags work,#notes',
|
|
58
|
+
' kiipu post star --id 123',
|
|
59
|
+
' kiipu post pin --id 123',
|
|
33
60
|
' kiipu post create --content "Hello Kiipu"',
|
|
34
61
|
' kiipu post delete --id 123',
|
|
35
62
|
' kiipu post restore --id 123',
|
|
@@ -112,13 +139,15 @@ export function getHelpResult(command) {
|
|
|
112
139
|
' kiipu <command> [options]',
|
|
113
140
|
'',
|
|
114
141
|
'Core commands:',
|
|
115
|
-
' post Create,
|
|
142
|
+
' post Create, browse, update, and delete posts with direct CLI arguments',
|
|
116
143
|
' auth Manage local API key authentication',
|
|
117
144
|
' doctor Check local setup, API access, and wrapper readiness',
|
|
118
145
|
' skills Show where the Claude Code plugin package lives',
|
|
119
146
|
'',
|
|
120
147
|
'Core examples:',
|
|
121
148
|
' kiipu post create "Hello Kiipu"',
|
|
149
|
+
' kiipu post list --starred',
|
|
150
|
+
' kiipu post search "Hello"',
|
|
122
151
|
' kiipu post delete --id 123',
|
|
123
152
|
' kiipu auth login',
|
|
124
153
|
' kiipu doctor',
|
package/dist/commands/post.js
CHANGED
|
@@ -1,27 +1,321 @@
|
|
|
1
|
+
import { formatPostCollection, formatPostDetail } from '../lib/post-formatters.js';
|
|
2
|
+
import { KiipuUserApiClient } from '../lib/kiipu-user-client.js';
|
|
1
3
|
import { executePostAction } from '../lib/post-actions.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
import { hasFlag, readFlag } from '../utils/args.js';
|
|
5
|
+
const actions = new Set([
|
|
6
|
+
'create',
|
|
7
|
+
'delete',
|
|
8
|
+
'restore',
|
|
9
|
+
'purge',
|
|
10
|
+
'list',
|
|
11
|
+
'search',
|
|
12
|
+
'show',
|
|
13
|
+
'update',
|
|
14
|
+
'star',
|
|
15
|
+
'pin',
|
|
16
|
+
]);
|
|
17
|
+
const sortValues = new Set(['updatedAt', 'createdAt', 'title']);
|
|
18
|
+
function usage(action) {
|
|
19
|
+
switch (action) {
|
|
20
|
+
case 'create':
|
|
21
|
+
return 'Usage: kiipu post create --content "<text>"\n or: kiipu post create "<text>"';
|
|
22
|
+
case 'list':
|
|
23
|
+
return 'Usage: kiipu post list [--tag <tag>] [--sort <updatedAt|createdAt|title>] [--starred] [--deleted]';
|
|
24
|
+
case 'search':
|
|
25
|
+
return 'Usage: kiipu post search <query>\n or: kiipu post search --query "<query>"';
|
|
26
|
+
case 'show':
|
|
27
|
+
return 'Usage: kiipu post show --id <postId>';
|
|
28
|
+
case 'update':
|
|
29
|
+
return 'Usage: kiipu post update --id <postId> --content "<text>" [--title "<title>"] [--visibility public|private] [--tags <a,b,c>]';
|
|
30
|
+
case 'star':
|
|
31
|
+
return 'Usage: kiipu post star --id <postId>';
|
|
32
|
+
case 'pin':
|
|
33
|
+
return 'Usage: kiipu post pin --id <postId>';
|
|
34
|
+
default:
|
|
35
|
+
return `Usage: kiipu post ${action} --id <postId>`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function error(message) {
|
|
39
|
+
return { ok: false, message };
|
|
40
|
+
}
|
|
41
|
+
function getUserClient(config) {
|
|
42
|
+
const apiKey = config.apiKey ?? process.env.KIIPU_API_KEY ?? '';
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return new KiipuUserApiClient({
|
|
47
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
48
|
+
apiKey,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function requireUserClient(config) {
|
|
52
|
+
const client = getUserClient(config);
|
|
53
|
+
if (!client) {
|
|
18
54
|
return {
|
|
19
|
-
|
|
20
|
-
message: `Usage: kiipu post ${input.action} --id <postId>`,
|
|
55
|
+
error: error('Kiipu API key is missing. Run `kiipu auth login` first.'),
|
|
21
56
|
};
|
|
22
57
|
}
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
58
|
+
return { client };
|
|
59
|
+
}
|
|
60
|
+
function stripKnownFlags(args, flagsWithValues) {
|
|
61
|
+
const positional = [];
|
|
62
|
+
const flags = new Set(flagsWithValues);
|
|
63
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
64
|
+
const arg = args[index];
|
|
65
|
+
if (!arg) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (flags.has(arg)) {
|
|
69
|
+
index += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!arg.startsWith('--')) {
|
|
73
|
+
positional.push(arg);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return positional;
|
|
77
|
+
}
|
|
78
|
+
function validateSort(sort) {
|
|
79
|
+
if (!sort) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return sortValues.has(sort) ? sort : null;
|
|
83
|
+
}
|
|
84
|
+
function parsePostId(args, action) {
|
|
85
|
+
const postId = readFlag(args, '--id')?.trim();
|
|
86
|
+
return postId ? postId : error(usage(action));
|
|
87
|
+
}
|
|
88
|
+
function parseTags(input) {
|
|
89
|
+
if (!input) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const unique = new Map();
|
|
93
|
+
for (const chunk of input.split(',')) {
|
|
94
|
+
const cleaned = chunk.trim().replace(/^#+/, '').replace(/\s+/g, ' ');
|
|
95
|
+
if (!cleaned) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const normalized = cleaned.toLowerCase();
|
|
99
|
+
if (!unique.has(normalized)) {
|
|
100
|
+
unique.set(normalized, cleaned);
|
|
101
|
+
}
|
|
102
|
+
if (unique.size === 8) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(unique.values());
|
|
107
|
+
}
|
|
108
|
+
function toCliPost(post) {
|
|
109
|
+
return {
|
|
110
|
+
id: post.id,
|
|
111
|
+
title: post.title,
|
|
112
|
+
rawText: post.rawText,
|
|
113
|
+
finalText: post.finalText,
|
|
114
|
+
visibility: post.visibility,
|
|
115
|
+
tags: post.tags,
|
|
116
|
+
folder: post.folder ?? null,
|
|
117
|
+
isPinned: post.isPinned,
|
|
118
|
+
isStarred: post.isStarred,
|
|
119
|
+
createdAt: post.createdAt,
|
|
120
|
+
updatedAt: post.updatedAt,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function handleMutationAction(config, action, args) {
|
|
124
|
+
if (action === 'create') {
|
|
125
|
+
const content = (readFlag(args, '--content') ?? stripKnownFlags(args, ['--content'])[0])?.trim();
|
|
126
|
+
if (!content) {
|
|
127
|
+
return error(usage('create'));
|
|
128
|
+
}
|
|
129
|
+
return executePostAction(config, { action: 'create', content });
|
|
130
|
+
}
|
|
131
|
+
const postId = parsePostId(args, action);
|
|
132
|
+
if (typeof postId !== 'string') {
|
|
133
|
+
return postId;
|
|
134
|
+
}
|
|
135
|
+
return executePostAction(config, { action, postId });
|
|
136
|
+
}
|
|
137
|
+
async function handleList(config, args) {
|
|
138
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
139
|
+
if (!client) {
|
|
140
|
+
return clientError;
|
|
141
|
+
}
|
|
142
|
+
const sort = validateSort(readFlag(args, '--sort'));
|
|
143
|
+
if (sort === null) {
|
|
144
|
+
return error(`Invalid --sort value. ${usage('list')}`);
|
|
145
|
+
}
|
|
146
|
+
const starred = hasFlag(args, '--starred');
|
|
147
|
+
const deleted = hasFlag(args, '--deleted');
|
|
148
|
+
if (starred && deleted) {
|
|
149
|
+
return error(`--starred and --deleted cannot be used together.\n${usage('list')}`);
|
|
150
|
+
}
|
|
151
|
+
const tag = readFlag(args, '--tag');
|
|
152
|
+
let posts;
|
|
153
|
+
let responseData;
|
|
154
|
+
if (starred) {
|
|
155
|
+
const response = await client.listStarredPosts({ tag, sort });
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
return error(response.error.message);
|
|
158
|
+
}
|
|
159
|
+
posts = response.data.map((entry) => toCliPost(entry.post));
|
|
160
|
+
responseData = response.data;
|
|
161
|
+
}
|
|
162
|
+
else if (deleted) {
|
|
163
|
+
const response = await client.listDeletedPosts({ sort });
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
return error(response.error.message);
|
|
166
|
+
}
|
|
167
|
+
posts = response.data.map(toCliPost);
|
|
168
|
+
responseData = response.data;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const response = await client.listPosts({ tag, sort });
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
return error(response.error.message);
|
|
174
|
+
}
|
|
175
|
+
posts = response.data.map(toCliPost);
|
|
176
|
+
responseData = response.data;
|
|
177
|
+
}
|
|
178
|
+
const title = starred ? 'Starred posts' : deleted ? 'Deleted posts' : 'Posts';
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
message: formatPostCollection(title, posts),
|
|
182
|
+
data: responseData,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function handleSearch(config, args) {
|
|
186
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
187
|
+
if (!client) {
|
|
188
|
+
return clientError;
|
|
189
|
+
}
|
|
190
|
+
const query = (readFlag(args, '--query') ?? stripKnownFlags(args, ['--query'])[0])?.trim();
|
|
191
|
+
if (!query) {
|
|
192
|
+
return error(usage('search'));
|
|
193
|
+
}
|
|
194
|
+
const response = await client.searchPosts(query);
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
return error(response.error.message);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
message: formatPostCollection(`Search results for "${query}"`, response.data.map(toCliPost)),
|
|
201
|
+
data: response.data,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function handleShow(config, args) {
|
|
205
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
206
|
+
if (!client) {
|
|
207
|
+
return clientError;
|
|
208
|
+
}
|
|
209
|
+
const postId = parsePostId(args, 'show');
|
|
210
|
+
if (typeof postId !== 'string') {
|
|
211
|
+
return postId;
|
|
212
|
+
}
|
|
213
|
+
const response = await client.getPost(postId);
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
return error(response.error.message);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
message: formatPostDetail(toCliPost(response.data)),
|
|
220
|
+
data: response.data,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async function handleUpdate(config, args) {
|
|
224
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
225
|
+
if (!client) {
|
|
226
|
+
return clientError;
|
|
227
|
+
}
|
|
228
|
+
const postId = parsePostId(args, 'update');
|
|
229
|
+
if (typeof postId !== 'string') {
|
|
230
|
+
return postId;
|
|
231
|
+
}
|
|
232
|
+
const content = readFlag(args, '--content')?.trim();
|
|
233
|
+
if (!content) {
|
|
234
|
+
return error(usage('update'));
|
|
235
|
+
}
|
|
236
|
+
const visibility = readFlag(args, '--visibility');
|
|
237
|
+
if (visibility && visibility !== 'public' && visibility !== 'private') {
|
|
238
|
+
return error(`Invalid --visibility value. ${usage('update')}`);
|
|
239
|
+
}
|
|
240
|
+
const title = readFlag(args, '--title');
|
|
241
|
+
const tags = readFlag(args, '--tags');
|
|
242
|
+
const response = await client.updatePost(postId, {
|
|
243
|
+
rawText: content,
|
|
244
|
+
...(title !== undefined ? { title: title || null } : {}),
|
|
245
|
+
...(visibility === 'public' || visibility === 'private' ? { visibility } : {}),
|
|
246
|
+
...(tags ? { tags: parseTags(tags) } : {}),
|
|
26
247
|
});
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
return error(response.error.message);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
message: `Post updated.\n\n${formatPostDetail(toCliPost(response.data))}`,
|
|
254
|
+
data: response.data,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async function handleStar(config, args) {
|
|
258
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
259
|
+
if (!client) {
|
|
260
|
+
return clientError;
|
|
261
|
+
}
|
|
262
|
+
const postId = parsePostId(args, 'star');
|
|
263
|
+
if (typeof postId !== 'string') {
|
|
264
|
+
return postId;
|
|
265
|
+
}
|
|
266
|
+
const response = await client.toggleStar(postId);
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
return error(response.error.message);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
message: response.data.isStarred ? `Post starred. ${response.data.id}` : `Post unstarred. ${response.data.id}`,
|
|
273
|
+
data: response.data,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function handlePin(config, args) {
|
|
277
|
+
const { client, error: clientError } = requireUserClient(config);
|
|
278
|
+
if (!client) {
|
|
279
|
+
return clientError;
|
|
280
|
+
}
|
|
281
|
+
const postId = parsePostId(args, 'pin');
|
|
282
|
+
if (typeof postId !== 'string') {
|
|
283
|
+
return postId;
|
|
284
|
+
}
|
|
285
|
+
const response = await client.togglePin(postId);
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
return error(response.error.message);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
ok: true,
|
|
291
|
+
message: response.data.isPinned ? `Post pinned. ${response.data.id}` : `Post unpinned. ${response.data.id}`,
|
|
292
|
+
data: response.data,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export async function runPostCommand(config, args) {
|
|
296
|
+
const action = args[1];
|
|
297
|
+
if (!action || !actions.has(action)) {
|
|
298
|
+
return error('Usage: kiipu post <create|delete|restore|purge|list|search|show|update|star|pin> [options]');
|
|
299
|
+
}
|
|
300
|
+
const actionArgs = args.slice(2);
|
|
301
|
+
switch (action) {
|
|
302
|
+
case 'create':
|
|
303
|
+
case 'delete':
|
|
304
|
+
case 'restore':
|
|
305
|
+
case 'purge':
|
|
306
|
+
return handleMutationAction(config, action, actionArgs);
|
|
307
|
+
case 'list':
|
|
308
|
+
return handleList(config, actionArgs);
|
|
309
|
+
case 'search':
|
|
310
|
+
return handleSearch(config, actionArgs);
|
|
311
|
+
case 'show':
|
|
312
|
+
return handleShow(config, actionArgs);
|
|
313
|
+
case 'update':
|
|
314
|
+
return handleUpdate(config, actionArgs);
|
|
315
|
+
case 'star':
|
|
316
|
+
return handleStar(config, actionArgs);
|
|
317
|
+
case 'pin':
|
|
318
|
+
return handlePin(config, actionArgs);
|
|
319
|
+
}
|
|
320
|
+
return error('Unsupported post action.');
|
|
27
321
|
}
|
package/dist/index.js
CHANGED
|
@@ -22,27 +22,43 @@ function printResult(result, asJson) {
|
|
|
22
22
|
async function main() {
|
|
23
23
|
const args = process.argv.slice(2);
|
|
24
24
|
const normalizedArgs = args[0] === '--' ? args.slice(1) : args;
|
|
25
|
+
const commandArgs = normalizedArgs.filter((arg) => arg !== '--json' && arg !== '--help' && arg !== '-h' && arg !== '--version' && arg !== '-v');
|
|
25
26
|
const asJson = hasFlag(normalizedArgs, '--json');
|
|
26
27
|
const wantsHelp = hasFlag(normalizedArgs, '--help') || hasFlag(normalizedArgs, '-h');
|
|
27
28
|
const wantsVersion = hasFlag(normalizedArgs, '--version') || hasFlag(normalizedArgs, '-v');
|
|
29
|
+
const flagsWithValues = new Set([
|
|
30
|
+
'--scheduled-at',
|
|
31
|
+
'--package-root',
|
|
32
|
+
'--skills-dir',
|
|
33
|
+
'--text',
|
|
34
|
+
'--content',
|
|
35
|
+
'--id',
|
|
36
|
+
'--api-key',
|
|
37
|
+
'--device-name',
|
|
38
|
+
'--config-path',
|
|
39
|
+
'--wrapper-path',
|
|
40
|
+
'--conversation-id',
|
|
41
|
+
'--tag',
|
|
42
|
+
'--sort',
|
|
43
|
+
'--query',
|
|
44
|
+
'--title',
|
|
45
|
+
'--visibility',
|
|
46
|
+
'--tags',
|
|
47
|
+
]);
|
|
28
48
|
const positionalArgs = normalizedArgs.filter((arg, index, all) => {
|
|
29
|
-
if (arg === '--json' ||
|
|
49
|
+
if (arg === '--json' ||
|
|
50
|
+
arg === '--help' ||
|
|
51
|
+
arg === '-h' ||
|
|
52
|
+
arg === '--version' ||
|
|
53
|
+
arg === '-v' ||
|
|
54
|
+
arg === '--no-browser' ||
|
|
55
|
+
arg.startsWith('--')) {
|
|
30
56
|
return false;
|
|
57
|
+
}
|
|
31
58
|
const prev = all[index - 1];
|
|
32
|
-
return (prev
|
|
33
|
-
prev !== '--package-root' &&
|
|
34
|
-
prev !== '--skills-dir' &&
|
|
35
|
-
prev !== '--text' &&
|
|
36
|
-
prev !== '--content' &&
|
|
37
|
-
prev !== '--id' &&
|
|
38
|
-
prev !== '--api-key' &&
|
|
39
|
-
prev !== '--device-name' &&
|
|
40
|
-
prev !== '--config-path' &&
|
|
41
|
-
prev !== '--wrapper-path' &&
|
|
42
|
-
prev !== '--conversation-id' &&
|
|
43
|
-
prev !== '--device-name');
|
|
59
|
+
return prev ? !flagsWithValues.has(prev) : true;
|
|
44
60
|
});
|
|
45
|
-
const [command, subcommand
|
|
61
|
+
const [command, subcommand] = positionalArgs;
|
|
46
62
|
if (wantsVersion) {
|
|
47
63
|
console.log(CLI_VERSION);
|
|
48
64
|
return;
|
|
@@ -65,19 +81,7 @@ async function main() {
|
|
|
65
81
|
return printResult(result, asJson);
|
|
66
82
|
}
|
|
67
83
|
if (command === 'post') {
|
|
68
|
-
|
|
69
|
-
if (!action || !['create', 'delete', 'restore', 'purge'].includes(action)) {
|
|
70
|
-
result = getHelpResult('post');
|
|
71
|
-
return printResult(result, asJson);
|
|
72
|
-
}
|
|
73
|
-
result = await runPostCommand(config, {
|
|
74
|
-
action,
|
|
75
|
-
content: readFlag(normalizedArgs, '--content') ?? subject,
|
|
76
|
-
postId: readFlag(normalizedArgs, '--id'),
|
|
77
|
-
});
|
|
78
|
-
if (!result.ok) {
|
|
79
|
-
return printResult(action === 'create' && !(readFlag(normalizedArgs, '--content') ?? subject) ? getHelpResult('post') : result, asJson);
|
|
80
|
-
}
|
|
84
|
+
result = await runPostCommand(config, commandArgs);
|
|
81
85
|
return printResult(result, asJson);
|
|
82
86
|
}
|
|
83
87
|
if (command === 'auth') {
|
|
@@ -12,6 +12,19 @@ export class KiipuUserApiClient {
|
|
|
12
12
|
constructor(config) {
|
|
13
13
|
this.config = config;
|
|
14
14
|
}
|
|
15
|
+
buildPath(path, params) {
|
|
16
|
+
if (!params) {
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
const search = new URLSearchParams();
|
|
20
|
+
for (const [key, value] of Object.entries(params)) {
|
|
21
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
22
|
+
search.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const query = search.toString();
|
|
26
|
+
return query ? `${path}?${query}` : path;
|
|
27
|
+
}
|
|
15
28
|
async request(path, init) {
|
|
16
29
|
let response;
|
|
17
30
|
try {
|
|
@@ -59,4 +72,35 @@ export class KiipuUserApiClient {
|
|
|
59
72
|
body: JSON.stringify(input),
|
|
60
73
|
});
|
|
61
74
|
}
|
|
75
|
+
listPosts(input) {
|
|
76
|
+
return this.request(this.buildPath('/posts/me', input));
|
|
77
|
+
}
|
|
78
|
+
searchPosts(query) {
|
|
79
|
+
return this.request(this.buildPath('/posts/me/search', { q: query }));
|
|
80
|
+
}
|
|
81
|
+
listStarredPosts(input) {
|
|
82
|
+
return this.request(this.buildPath('/posts/me/starred', input));
|
|
83
|
+
}
|
|
84
|
+
listDeletedPosts(input) {
|
|
85
|
+
return this.request(this.buildPath('/posts/me/deleted', input));
|
|
86
|
+
}
|
|
87
|
+
getPost(id) {
|
|
88
|
+
return this.request(`/posts/${id}`);
|
|
89
|
+
}
|
|
90
|
+
updatePost(id, input) {
|
|
91
|
+
return this.request(`/posts/${id}/content`, {
|
|
92
|
+
method: 'PATCH',
|
|
93
|
+
body: JSON.stringify(input),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
toggleStar(id) {
|
|
97
|
+
return this.request(`/posts/${id}/star`, {
|
|
98
|
+
method: 'PATCH',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
togglePin(id) {
|
|
102
|
+
return this.request(`/posts/${id}/pin`, {
|
|
103
|
+
method: 'PATCH',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
62
106
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function formatTimestamp(value) {
|
|
2
|
+
if (!value) {
|
|
3
|
+
return 'unknown time';
|
|
4
|
+
}
|
|
5
|
+
const parsed = new Date(value);
|
|
6
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
return parsed.toLocaleString('en-US', {
|
|
10
|
+
year: 'numeric',
|
|
11
|
+
month: 'short',
|
|
12
|
+
day: '2-digit',
|
|
13
|
+
hour: '2-digit',
|
|
14
|
+
minute: '2-digit',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function formatTags(tags, limit) {
|
|
18
|
+
const normalized = Array.isArray(tags)
|
|
19
|
+
? tags
|
|
20
|
+
.map((tag) => tag.tagName?.trim())
|
|
21
|
+
.filter((tag) => Boolean(tag))
|
|
22
|
+
.slice(0, limit)
|
|
23
|
+
: [];
|
|
24
|
+
return normalized.length > 0 ? normalized.map((tag) => `#${tag}`).join(', ') : 'none';
|
|
25
|
+
}
|
|
26
|
+
function getPostPreview(post) {
|
|
27
|
+
const content = (post.title?.trim() || post.finalText?.trim() || post.rawText?.trim() || '').replace(/\s+/g, ' ');
|
|
28
|
+
if (!content) {
|
|
29
|
+
return '(empty)';
|
|
30
|
+
}
|
|
31
|
+
return content.length > 100 ? `${content.slice(0, 97)}...` : content;
|
|
32
|
+
}
|
|
33
|
+
function getStatusFlags(post) {
|
|
34
|
+
const flags = [];
|
|
35
|
+
if (post.isPinned) {
|
|
36
|
+
flags.push('pinned');
|
|
37
|
+
}
|
|
38
|
+
if (post.isStarred) {
|
|
39
|
+
flags.push('starred');
|
|
40
|
+
}
|
|
41
|
+
return flags.length > 0 ? flags.join(', ') : 'none';
|
|
42
|
+
}
|
|
43
|
+
export function formatPostCollection(title, posts) {
|
|
44
|
+
const lines = [title, ''];
|
|
45
|
+
if (posts.length === 0) {
|
|
46
|
+
lines.push('No posts found.');
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
for (const post of posts) {
|
|
50
|
+
lines.push(`${post.id} ${formatTimestamp(post.updatedAt ?? post.createdAt)}`);
|
|
51
|
+
lines.push(` flags: ${getStatusFlags(post)} visibility: ${post.visibility} tags: ${formatTags(post.tags, 3)}`);
|
|
52
|
+
lines.push(` ${getPostPreview(post)}`);
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
return lines.slice(0, -1).join('\n');
|
|
56
|
+
}
|
|
57
|
+
export function formatPostDetail(post) {
|
|
58
|
+
const title = post.title?.trim() || '(untitled)';
|
|
59
|
+
const body = post.finalText?.trim() || post.rawText?.trim() || '(empty)';
|
|
60
|
+
return [
|
|
61
|
+
title,
|
|
62
|
+
'',
|
|
63
|
+
body,
|
|
64
|
+
'',
|
|
65
|
+
`id: ${post.id}`,
|
|
66
|
+
`visibility: ${post.visibility}`,
|
|
67
|
+
`created: ${formatTimestamp(post.createdAt)}`,
|
|
68
|
+
`updated: ${formatTimestamp(post.updatedAt)}`,
|
|
69
|
+
`tags: ${formatTags(post.tags)}`,
|
|
70
|
+
`folder: ${post.folder ? `${post.folder.name} (${post.folder.id})` : 'none'}`,
|
|
71
|
+
`pinned: ${post.isPinned ? 'yes' : 'no'}`,
|
|
72
|
+
`starred: ${post.isStarred ? 'yes' : 'no'}`,
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|