@kiipu/cli 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Publish to Kiipu from your terminal.
7
7
  Use it to:
8
8
 
9
9
  - sign in on the current device
10
+ - ask questions over your saved posts
10
11
  - publish posts from the command line
11
12
  - delete, restore, or permanently remove posts by id
12
13
  - verify local authentication and API access with `kiipu doctor`
@@ -39,10 +40,17 @@ kiipu post create "Hello Kiipu"
39
40
  kiipu doctor
40
41
  ```
41
42
 
43
+ 4. Ask over your saved posts:
44
+
45
+ ```bash
46
+ kiipu ask "What did I save about the roadmap?"
47
+ ```
48
+
42
49
  ## Example Workflow
43
50
 
44
51
  ```bash
45
52
  kiipu auth login
53
+ kiipu ask "What should I follow up on?"
46
54
  kiipu post create "Ship the beta today"
47
55
  kiipu auth status
48
56
  ```
@@ -82,6 +90,23 @@ kiipu post restore --id post_123
82
90
  kiipu post purge --id post_123
83
91
  ```
84
92
 
93
+ ## Ask
94
+
95
+ Ask a new question and stream the answer:
96
+
97
+ ```bash
98
+ kiipu ask "What did I save about the roadmap?"
99
+ kiipu ask --question "What should I follow up on?"
100
+ ```
101
+
102
+ Continue a conversation or inspect Ask history:
103
+
104
+ ```bash
105
+ kiipu ask --conversation-id conv_123 "What should I do next?"
106
+ kiipu ask history --limit 10
107
+ kiipu ask show --id conv_123
108
+ ```
109
+
85
110
  ## Core Commands
86
111
 
87
112
  ```bash
@@ -89,6 +114,10 @@ kiipu auth login
89
114
  kiipu auth status
90
115
  kiipu auth logout
91
116
 
117
+ kiipu ask "What did I save about the roadmap?"
118
+ kiipu ask history --limit 10
119
+ kiipu ask show --id conv_123
120
+
92
121
  kiipu post create "Hello Kiipu"
93
122
  kiipu post delete --id post_123
94
123
  kiipu post restore --id post_123
@@ -122,5 +151,6 @@ See the full command reference in the terminal:
122
151
  ```bash
123
152
  kiipu --help
124
153
  kiipu auth --help
154
+ kiipu ask --help
125
155
  kiipu post --help
126
156
  ```
@@ -0,0 +1,234 @@
1
+ import { KiipuAskClient } from '../lib/ask-client.js';
2
+ import { formatAskFooter, formatConversationDetail, formatConversationHistory, } from '../lib/ask-formatters.js';
3
+ import { hasFlag, readFlag } from '../utils/args.js';
4
+ const sourceModes = new Set(['fresh', 'locked']);
5
+ function error(message, data) {
6
+ return {
7
+ ok: false,
8
+ message,
9
+ ...(data ? { data, json: { ok: false, message, ...data } } : {}),
10
+ };
11
+ }
12
+ function usage(action) {
13
+ if (action === 'history') {
14
+ return 'Usage: kiipu ask history [--query <q>] [--limit <n>] [--archived]';
15
+ }
16
+ if (action === 'show') {
17
+ return 'Usage: kiipu ask show --id <conversationId>';
18
+ }
19
+ return [
20
+ 'Usage: kiipu ask "question"',
21
+ ' or: kiipu ask --question "question"',
22
+ ' or: kiipu ask --conversation-id <id> "follow-up"',
23
+ ].join('\n');
24
+ }
25
+ function getAskClient(config) {
26
+ const apiKey = config.apiKey ?? process.env.KIIPU_API_KEY ?? '';
27
+ if (!apiKey) {
28
+ return {
29
+ error: error('Kiipu API key is missing. Run `kiipu auth login` first.', {
30
+ code: 'missing_api_key',
31
+ }),
32
+ };
33
+ }
34
+ return {
35
+ client: new KiipuAskClient({
36
+ apiBaseUrl: config.apiBaseUrl,
37
+ apiKey,
38
+ }),
39
+ };
40
+ }
41
+ function stripKnownFlags(args, flagsWithValues) {
42
+ const positional = [];
43
+ const flags = new Set(flagsWithValues);
44
+ for (let index = 0; index < args.length; index += 1) {
45
+ const arg = args[index];
46
+ if (!arg) {
47
+ continue;
48
+ }
49
+ if (flags.has(arg)) {
50
+ index += 1;
51
+ continue;
52
+ }
53
+ if (!arg.startsWith('--')) {
54
+ positional.push(arg);
55
+ }
56
+ }
57
+ return positional;
58
+ }
59
+ function parseTopK(args) {
60
+ const raw = readFlag(args, '--top-k');
61
+ if (!raw) {
62
+ return undefined;
63
+ }
64
+ const value = Number(raw);
65
+ if (!Number.isInteger(value) || value < 1 || value > 20) {
66
+ return null;
67
+ }
68
+ return value;
69
+ }
70
+ function parseLimit(args) {
71
+ const raw = readFlag(args, '--limit');
72
+ if (!raw) {
73
+ return undefined;
74
+ }
75
+ const value = Number(raw);
76
+ if (!Number.isInteger(value) || value < 1 || value > 50) {
77
+ return null;
78
+ }
79
+ return value;
80
+ }
81
+ function parseSourceMode(args) {
82
+ const value = readFlag(args, '--source-mode');
83
+ if (!value) {
84
+ return undefined;
85
+ }
86
+ return sourceModes.has(value) ? value : null;
87
+ }
88
+ function readQuestion(args) {
89
+ const explicit = readFlag(args, '--question')?.trim();
90
+ if (explicit) {
91
+ return explicit;
92
+ }
93
+ return stripKnownFlags(args, [
94
+ '--question',
95
+ '--conversation-id',
96
+ '--top-k',
97
+ '--source-mode',
98
+ ])
99
+ .join(' ')
100
+ .trim();
101
+ }
102
+ async function handleHistory(config, args) {
103
+ const { client, error: clientError } = getAskClient(config);
104
+ if (!client) {
105
+ return clientError;
106
+ }
107
+ const limit = parseLimit(args);
108
+ if (limit === null) {
109
+ return error(`Invalid --limit value. ${usage('history')}`, { code: 'invalid_limit' });
110
+ }
111
+ const response = await client.listConversations({
112
+ query: readFlag(args, '--query'),
113
+ limit,
114
+ archived: hasFlag(args, '--archived'),
115
+ });
116
+ if (!response.ok) {
117
+ return error(response.error.message, response.error);
118
+ }
119
+ return {
120
+ ok: true,
121
+ message: formatConversationHistory(hasFlag(args, '--archived') ? 'Archived Ask conversations' : 'Ask conversations', response.data.items, response.data.nextCursor),
122
+ data: response.data,
123
+ };
124
+ }
125
+ async function handleShow(config, args) {
126
+ const { client, error: clientError } = getAskClient(config);
127
+ if (!client) {
128
+ return clientError;
129
+ }
130
+ const id = readFlag(args, '--id')?.trim();
131
+ if (!id) {
132
+ return error(usage('show'), { code: 'missing_conversation_id' });
133
+ }
134
+ const response = await client.getConversation(id);
135
+ if (!response.ok) {
136
+ return error(response.error.message, response.error);
137
+ }
138
+ return {
139
+ ok: true,
140
+ message: formatConversationDetail(response.data),
141
+ data: response.data,
142
+ };
143
+ }
144
+ async function handleQuestion(config, args, options) {
145
+ const { client, error: clientError } = getAskClient(config);
146
+ if (!client) {
147
+ return clientError;
148
+ }
149
+ const question = readQuestion(args);
150
+ if (!question) {
151
+ return error(usage(), { code: 'missing_question' });
152
+ }
153
+ const topK = parseTopK(args);
154
+ if (topK === null) {
155
+ return error(`Invalid --top-k value. ${usage()}`, { code: 'invalid_top_k' });
156
+ }
157
+ const sourceMode = parseSourceMode(args);
158
+ if (sourceMode === null) {
159
+ return error('Invalid --source-mode value. Use fresh or locked.', {
160
+ code: 'invalid_source_mode',
161
+ });
162
+ }
163
+ const result = {
164
+ answer: '',
165
+ conversationId: readFlag(args, '--conversation-id')?.trim() || undefined,
166
+ sources: [],
167
+ };
168
+ const targetConversationId = result.conversationId ?? 'new';
169
+ for await (const event of client.streamMessage({
170
+ conversationId: targetConversationId,
171
+ question,
172
+ topK,
173
+ sourceMode,
174
+ })) {
175
+ switch (event.type) {
176
+ case 'meta':
177
+ result.conversationId = event.conversationId;
178
+ result.title = event.title;
179
+ break;
180
+ case 'sources':
181
+ result.sources = event.sources;
182
+ break;
183
+ case 'delta':
184
+ result.answer += event.text;
185
+ if (options.stream) {
186
+ options.write?.(event.text);
187
+ }
188
+ break;
189
+ case 'done':
190
+ result.turnId = event.turnId;
191
+ result.usage = {
192
+ inputTokens: event.inputTokens,
193
+ outputTokens: event.outputTokens,
194
+ latencyMs: event.latencyMs,
195
+ };
196
+ break;
197
+ case 'title':
198
+ result.conversationId = event.conversationId;
199
+ result.title = event.title;
200
+ break;
201
+ case 'error':
202
+ return error(event.message, {
203
+ code: event.code,
204
+ answer: result.answer,
205
+ conversationId: result.conversationId,
206
+ title: result.title,
207
+ turnId: result.turnId,
208
+ sources: result.sources,
209
+ usage: result.usage,
210
+ });
211
+ }
212
+ }
213
+ const footer = formatAskFooter(result);
214
+ return {
215
+ ok: true,
216
+ message: options.stream ? footer : `${result.answer}${footer}`,
217
+ data: result,
218
+ json: {
219
+ ok: true,
220
+ ...result,
221
+ },
222
+ };
223
+ }
224
+ export async function runAskCommand(config, args, options = {}) {
225
+ const action = args[1];
226
+ const actionArgs = args.slice(2);
227
+ if (action === 'history') {
228
+ return handleHistory(config, actionArgs);
229
+ }
230
+ if (action === 'show') {
231
+ return handleShow(config, actionArgs);
232
+ }
233
+ return handleQuestion(config, args.slice(1), options);
234
+ }
@@ -2,6 +2,40 @@ function block(lines) {
2
2
  return lines.join('\n');
3
3
  }
4
4
  export function getHelpResult(command) {
5
+ if (command === 'ask') {
6
+ return {
7
+ ok: true,
8
+ message: block([
9
+ 'Kiipu CLI',
10
+ '',
11
+ 'Usage:',
12
+ ' kiipu ask "question"',
13
+ ' kiipu ask --question "question"',
14
+ ' kiipu ask --conversation-id <conversationId> "follow-up"',
15
+ ' kiipu ask history [--query <q>] [--limit <n>] [--archived]',
16
+ ' kiipu ask show --id <conversationId>',
17
+ '',
18
+ 'Description:',
19
+ ' Ask questions over your Kiipu posts using the same conversation-aware Ask surface as the web app.',
20
+ '',
21
+ 'Options:',
22
+ ' --question "<text>" Question to ask when not using a positional argument',
23
+ ' --conversation-id <id> Continue an existing Ask conversation',
24
+ ' --top-k <1-20> Optional retrieval count for Ask',
25
+ ' --source-mode <value> One of fresh or locked',
26
+ ' --query "<query>" Filter Ask history rows',
27
+ ' --limit <1-50> Limit Ask history rows',
28
+ ' --archived List archived Ask conversations',
29
+ ' --id <conversationId> Conversation id for ask show',
30
+ '',
31
+ 'Examples:',
32
+ ' kiipu ask "What did I save about the roadmap?"',
33
+ ' kiipu ask --conversation-id conv_123 "What should I do next?"',
34
+ ' kiipu ask history --limit 10',
35
+ ' kiipu ask show --id conv_123',
36
+ ]),
37
+ };
38
+ }
5
39
  if (command === 'post') {
6
40
  return {
7
41
  ok: true,
@@ -11,6 +45,13 @@ export function getHelpResult(command) {
11
45
  'Usage:',
12
46
  ' kiipu post create --content "<text>"',
13
47
  ' kiipu post create "<text>"',
48
+ ' kiipu post list [--tag <tag>] [--sort <updatedAt|createdAt|title>] [--starred] [--deleted]',
49
+ ' kiipu post search <query>',
50
+ ' kiipu post search --query "<query>"',
51
+ ' kiipu post show --id <postId>',
52
+ ' kiipu post update --id <postId> --content "<text>" [--title "<title>"] [--visibility public|private] [--tags <a,b,c>]',
53
+ ' kiipu post star --id <postId>',
54
+ ' kiipu post pin --id <postId>',
14
55
  ' kiipu post delete --id <postId>',
15
56
  ' kiipu post restore --id <postId>',
16
57
  ' kiipu post purge --id <postId>',
@@ -20,16 +61,36 @@ export function getHelpResult(command) {
20
61
  '',
21
62
  'Actions:',
22
63
  ' create Create a new post',
64
+ ' list List your published, starred, or deleted posts',
65
+ ' search Search your published posts',
66
+ ' show Show one post with full metadata',
67
+ ' update Update post content and metadata',
68
+ ' star Toggle a post star',
69
+ ' pin Toggle a post pin',
23
70
  ' delete Soft-delete an existing post by explicit id',
24
71
  ' restore Restore a deleted post by explicit id',
25
72
  ' purge Permanently delete a post by explicit id',
26
73
  '',
27
74
  'Options:',
28
75
  ' --content "<text>" Post content for create',
29
- ' --id <postId> Required for delete, restore, and purge',
76
+ ' --id <postId> Required for show, update, star, pin, delete, restore, and purge',
77
+ ' --query "<query>" Search query for post search',
78
+ ' --tag <tag> Filter post list results by tag',
79
+ ' --sort <field> One of updatedAt, createdAt, or title',
80
+ ' --starred List only starred posts',
81
+ ' --deleted List only deleted posts',
82
+ ' --title "<title>" Optional title for post update',
83
+ ' --visibility <value> Optional visibility for post update',
84
+ ' --tags <a,b,c> Optional comma-separated tags for post update',
30
85
  '',
31
86
  'Examples:',
32
87
  ' kiipu post create "Hello Kiipu"',
88
+ ' kiipu post list --sort updatedAt',
89
+ ' kiipu post search "roadmap"',
90
+ ' kiipu post show --id 123',
91
+ ' kiipu post update --id 123 --content "Updated text" --tags work,#notes',
92
+ ' kiipu post star --id 123',
93
+ ' kiipu post pin --id 123',
33
94
  ' kiipu post create --content "Hello Kiipu"',
34
95
  ' kiipu post delete --id 123',
35
96
  ' kiipu post restore --id 123',
@@ -112,13 +173,18 @@ export function getHelpResult(command) {
112
173
  ' kiipu <command> [options]',
113
174
  '',
114
175
  'Core commands:',
115
- ' post Create, delete, restore, or purge posts with direct CLI arguments',
176
+ ' ask Ask questions over your Kiipu posts and browse Ask conversations',
177
+ ' post Create, browse, update, and delete posts with direct CLI arguments',
116
178
  ' auth Manage local API key authentication',
117
179
  ' doctor Check local setup, API access, and wrapper readiness',
118
180
  ' skills Show where the Claude Code plugin package lives',
119
181
  '',
120
182
  'Core examples:',
183
+ ' kiipu ask "What changed in my roadmap notes?"',
184
+ ' kiipu ask history --limit 10',
121
185
  ' kiipu post create "Hello Kiipu"',
186
+ ' kiipu post list --starred',
187
+ ' kiipu post search "Hello"',
122
188
  ' kiipu post delete --id 123',
123
189
  ' kiipu auth login',
124
190
  ' kiipu doctor',