pikiclaw 0.2.68 → 0.2.70
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/bot-command-ui.js +112 -14
- package/dist/bot-commands.js +6 -15
- package/dist/bot-feishu-render.js +35 -8
- package/dist/bot-telegram.js +21 -5
- package/dist/cli.js +9 -4
- package/dist/code-agent.js +0 -17
- package/dist/driver-claude.js +3 -2
- package/dist/process-control.js +1 -0
- package/dist/run.js +2 -3
- package/package.json +1 -1
package/dist/bot-command-ui.js
CHANGED
|
@@ -31,6 +31,12 @@ export function encodeCommandAction(action) {
|
|
|
31
31
|
return `mod:${action.modelId}`;
|
|
32
32
|
case 'effort.set':
|
|
33
33
|
return `eff:${action.effort}`;
|
|
34
|
+
case 'models.select.model':
|
|
35
|
+
return `md:${action.modelId}`;
|
|
36
|
+
case 'models.select.effort':
|
|
37
|
+
return `ed:${action.effort}`;
|
|
38
|
+
case 'models.confirm':
|
|
39
|
+
return 'mc';
|
|
34
40
|
case 'skill.run':
|
|
35
41
|
return `skr:${action.command}`;
|
|
36
42
|
}
|
|
@@ -70,6 +76,20 @@ export function decodeCommandAction(data) {
|
|
|
70
76
|
return null;
|
|
71
77
|
return { kind: 'effort.set', effort };
|
|
72
78
|
}
|
|
79
|
+
if (data.startsWith('md:')) {
|
|
80
|
+
const modelId = data.slice(3);
|
|
81
|
+
if (!modelId)
|
|
82
|
+
return null;
|
|
83
|
+
return { kind: 'models.select.model', modelId };
|
|
84
|
+
}
|
|
85
|
+
if (data.startsWith('ed:')) {
|
|
86
|
+
const effort = data.slice(3);
|
|
87
|
+
if (!effort)
|
|
88
|
+
return null;
|
|
89
|
+
return { kind: 'models.select.effort', effort };
|
|
90
|
+
}
|
|
91
|
+
if (data === 'mc')
|
|
92
|
+
return { kind: 'models.confirm' };
|
|
73
93
|
if (data.startsWith('skr:')) {
|
|
74
94
|
const command = data.slice(4);
|
|
75
95
|
if (!command)
|
|
@@ -134,24 +154,57 @@ export function buildAgentsCommandView(bot, chatId) {
|
|
|
134
154
|
rows: chunkRows(actions, 3),
|
|
135
155
|
};
|
|
136
156
|
}
|
|
137
|
-
|
|
157
|
+
const modelsDrafts = new Map();
|
|
158
|
+
async function initModelsDraft(bot, chatId) {
|
|
138
159
|
const data = await getModelsListData(bot, chatId);
|
|
139
|
-
const
|
|
160
|
+
const draft = { modelId: data.currentModel, effort: data.effort?.current ?? null };
|
|
161
|
+
modelsDrafts.set(String(chatId), draft);
|
|
162
|
+
return draft;
|
|
163
|
+
}
|
|
164
|
+
export async function buildModelsCommandView(bot, chatId, draft) {
|
|
165
|
+
const data = await getModelsListData(bot, chatId);
|
|
166
|
+
// Initialize draft from current state or use the provided one
|
|
167
|
+
const d = draft ?? {
|
|
168
|
+
modelId: data.currentModel,
|
|
169
|
+
effort: data.effort?.current ?? null,
|
|
170
|
+
};
|
|
171
|
+
modelsDrafts.set(String(chatId), d);
|
|
172
|
+
const isSelected = (modelId) => modelMatchesSelection(data.agent, modelId, d.modelId);
|
|
173
|
+
const models = [...data.models].sort((a, b) => Number(isSelected(b.id)) - Number(isSelected(a.id)));
|
|
140
174
|
const modelButtons = models.map(model => ({
|
|
141
175
|
label: model.alias || model.id,
|
|
142
|
-
action: { kind: 'model
|
|
143
|
-
state: buttonStateFromFlags({ isCurrent: model.
|
|
144
|
-
primary: model.
|
|
176
|
+
action: { kind: 'models.select.model', modelId: model.id },
|
|
177
|
+
state: buttonStateFromFlags({ isCurrent: isSelected(model.id) }),
|
|
178
|
+
primary: isSelected(model.id),
|
|
145
179
|
}));
|
|
146
|
-
const rows = chunkRows(modelButtons,
|
|
180
|
+
const rows = chunkRows(modelButtons, 1);
|
|
147
181
|
if (data.effort) {
|
|
148
|
-
|
|
182
|
+
const effortButtons = data.effort.levels.map(level => ({
|
|
149
183
|
label: level.label,
|
|
150
|
-
action: { kind: 'effort
|
|
151
|
-
state: buttonStateFromFlags({ isCurrent: level.
|
|
152
|
-
primary: level.
|
|
153
|
-
}))
|
|
184
|
+
action: { kind: 'models.select.effort', effort: level.id },
|
|
185
|
+
state: buttonStateFromFlags({ isCurrent: level.id === d.effort }),
|
|
186
|
+
primary: level.id === d.effort,
|
|
187
|
+
}));
|
|
188
|
+
// Section label — clicking it is harmless (triggers confirm, which is noop if nothing changed)
|
|
189
|
+
rows.push([{
|
|
190
|
+
label: '— Thinking Effort —',
|
|
191
|
+
action: { kind: 'models.confirm' },
|
|
192
|
+
state: 'default',
|
|
193
|
+
primary: false,
|
|
194
|
+
}]);
|
|
195
|
+
// ≤3 levels fit in one row; 4+ split into rows of 2 to avoid Feishu truncation
|
|
196
|
+
rows.push(...chunkRows(effortButtons, effortButtons.length <= 3 ? effortButtons.length : 2));
|
|
154
197
|
}
|
|
198
|
+
// Detect whether draft differs from current live values
|
|
199
|
+
const modelChanged = !modelMatchesSelection(data.agent, d.modelId, data.currentModel);
|
|
200
|
+
const effortChanged = !!(data.effort && d.effort !== data.effort.current);
|
|
201
|
+
const hasChanges = modelChanged || effortChanged;
|
|
202
|
+
rows.push([{
|
|
203
|
+
label: hasChanges ? '✓ Apply' : '✓ OK',
|
|
204
|
+
action: { kind: 'models.confirm' },
|
|
205
|
+
state: 'default',
|
|
206
|
+
primary: hasChanges,
|
|
207
|
+
}]);
|
|
155
208
|
return {
|
|
156
209
|
kind: 'models',
|
|
157
210
|
title: 'Models',
|
|
@@ -159,15 +212,15 @@ export async function buildModelsCommandView(bot, chatId) {
|
|
|
159
212
|
metaLines: [
|
|
160
213
|
...(data.sources.length ? [`Source: ${data.sources.join(', ')}`] : []),
|
|
161
214
|
...(data.note ? [data.note] : []),
|
|
162
|
-
...(data.effort ? [`Thinking Effort: ${
|
|
215
|
+
...(data.effort ? [`Thinking Effort: ${d.effort}`] : []),
|
|
163
216
|
],
|
|
164
217
|
items: models.map(model => ({
|
|
165
218
|
label: model.alias || model.id,
|
|
166
219
|
detail: model.alias ? model.id : null,
|
|
167
|
-
state: buttonStateFromFlags({ isCurrent: model.
|
|
220
|
+
state: buttonStateFromFlags({ isCurrent: isSelected(model.id) }),
|
|
168
221
|
})),
|
|
169
222
|
emptyText: 'No discoverable models found.',
|
|
170
|
-
helperText: data.models.length ? '
|
|
223
|
+
helperText: data.models.length ? 'Select model and effort, then tap Apply.' : null,
|
|
171
224
|
rows,
|
|
172
225
|
};
|
|
173
226
|
}
|
|
@@ -286,6 +339,51 @@ export async function executeCommandAction(bot, chatId, action, opts = {}) {
|
|
|
286
339
|
},
|
|
287
340
|
};
|
|
288
341
|
}
|
|
342
|
+
case 'models.select.model': {
|
|
343
|
+
const draft = modelsDrafts.get(String(chatId)) ?? await initModelsDraft(bot, chatId);
|
|
344
|
+
draft.modelId = action.modelId;
|
|
345
|
+
return { kind: 'view', view: await buildModelsCommandView(bot, chatId, draft), callbackText: '' };
|
|
346
|
+
}
|
|
347
|
+
case 'models.select.effort': {
|
|
348
|
+
const draft = modelsDrafts.get(String(chatId)) ?? await initModelsDraft(bot, chatId);
|
|
349
|
+
draft.effort = action.effort;
|
|
350
|
+
return { kind: 'view', view: await buildModelsCommandView(bot, chatId, draft), callbackText: '' };
|
|
351
|
+
}
|
|
352
|
+
case 'models.confirm': {
|
|
353
|
+
const chat = bot.chat(chatId);
|
|
354
|
+
const draft = modelsDrafts.get(String(chatId));
|
|
355
|
+
modelsDrafts.delete(String(chatId));
|
|
356
|
+
if (!draft)
|
|
357
|
+
return { kind: 'noop', message: 'No changes' };
|
|
358
|
+
const currentModel = bot.modelForAgent(chat.agent);
|
|
359
|
+
const currentEffort = bot.effortForAgent(chat.agent);
|
|
360
|
+
const modelChanged = !modelMatchesSelection(chat.agent, draft.modelId, currentModel);
|
|
361
|
+
const effortChanged = draft.effort != null && draft.effort !== currentEffort;
|
|
362
|
+
if (!modelChanged && !effortChanged) {
|
|
363
|
+
return { kind: 'noop', message: 'No changes' };
|
|
364
|
+
}
|
|
365
|
+
const parts = [];
|
|
366
|
+
if (modelChanged) {
|
|
367
|
+
bot.switchModelForChat(chatId, draft.modelId);
|
|
368
|
+
parts.push(`Model: ${draft.modelId}`);
|
|
369
|
+
}
|
|
370
|
+
if (effortChanged) {
|
|
371
|
+
bot.switchEffortForChat(chatId, draft.effort);
|
|
372
|
+
parts.push(`Effort: ${draft.effort}`);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
kind: 'notice',
|
|
376
|
+
callbackText: parts.join(', '),
|
|
377
|
+
notice: {
|
|
378
|
+
title: 'Configuration Updated',
|
|
379
|
+
value: parts.join('\n'),
|
|
380
|
+
detail: modelChanged
|
|
381
|
+
? `${chat.agent} · session reset`
|
|
382
|
+
: `${chat.agent} · takes effect on next message`,
|
|
383
|
+
valueMode: 'plain',
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
289
387
|
case 'skill.run': {
|
|
290
388
|
const resolved = resolveSkillPrompt(bot, chatId, action.command, '');
|
|
291
389
|
if (!resolved)
|
package/dist/bot-commands.js
CHANGED
|
@@ -249,25 +249,16 @@ export function resolveSkillPrompt(bot, chatId, cmd, args) {
|
|
|
249
249
|
const cs = bot.chat(chatId);
|
|
250
250
|
const extra = args.trim();
|
|
251
251
|
const suffix = extra ? ` Additional context: ${extra}` : '';
|
|
252
|
+
const workdirHint = `[Project directory: ${bot.workdir}]\n\n`;
|
|
252
253
|
let prompt;
|
|
253
|
-
if (skill.source === 'commands') {
|
|
254
|
-
prompt = `In this project's .claude/commands/${skill.name}.md file, there is a custom command definition. Please read and execute the instructions defined there.${suffix}`;
|
|
255
|
-
return { prompt, skillName: skill.name };
|
|
256
|
-
}
|
|
257
254
|
const paths = getProjectSkillPaths(bot.workdir, skill.name);
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
const skillFile = paths.claudeSkillFile || paths.sharedSkillFile || paths.agentsSkillFile;
|
|
256
|
+
if (skillFile) {
|
|
257
|
+
prompt = `${workdirHint}Read the skill definition at \`${skillFile}\` and execute the instructions defined there.${suffix}`;
|
|
260
258
|
}
|
|
261
259
|
else {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
: `\`.pikiclaw/skills/${skill.name}/SKILL.md\``;
|
|
265
|
-
const locationText = paths.sharedSkillFile
|
|
266
|
-
? canonicalPath
|
|
267
|
-
: paths.agentsSkillFile || paths.claudeSkillFile
|
|
268
|
-
? canonicalPath
|
|
269
|
-
: `\`${skill.name}/SKILL.md\``;
|
|
270
|
-
prompt = `In this project, the ${skill.name} skill is defined in ${locationText}. Please read that SKILL.md file and execute the instructions.${suffix}`;
|
|
260
|
+
const fallbackPath = `${bot.workdir}/.pikiclaw/skills/${skill.name}/SKILL.md`;
|
|
261
|
+
prompt = `${workdirHint}Read the skill definition at \`${fallbackPath}\` and execute the instructions defined there.${suffix}`;
|
|
271
262
|
}
|
|
272
263
|
return { prompt, skillName: skill.name };
|
|
273
264
|
}
|
|
@@ -100,6 +100,29 @@ export function renderCommandSelectionCard(view) {
|
|
|
100
100
|
rows: view.rows.map(row => ({ actions: row.map(actionButton) })),
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Strip code-fence markers (``` / ~~~) from text that is not meant to be
|
|
105
|
+
* rendered as full markdown (thinking, activity). Truncation by
|
|
106
|
+
* extractThinkingTail can leave stray fences that open unwanted code blocks.
|
|
107
|
+
*/
|
|
108
|
+
function stripCodeFences(text) {
|
|
109
|
+
return text.replace(/^(`{3,}|~{3,}).*$/gm, '');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Ensure code fences in markdown text are balanced. If an odd number of
|
|
113
|
+
* fence markers is detected, append a closing fence so partial code blocks
|
|
114
|
+
* do not swallow the rest of the card.
|
|
115
|
+
*/
|
|
116
|
+
function ensureBalancedCodeFences(text) {
|
|
117
|
+
let inCode = false;
|
|
118
|
+
for (const line of text.split('\n')) {
|
|
119
|
+
const trimmed = line.trimStart();
|
|
120
|
+
if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
|
|
121
|
+
inCode = !inCode;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return inCode ? text + '\n```' : text;
|
|
125
|
+
}
|
|
103
126
|
function escapeFeishuMarkdownText(text) {
|
|
104
127
|
return text.replace(/([\\`*_{}[\]()#+\-.!|>~])/g, '\\$1');
|
|
105
128
|
}
|
|
@@ -172,19 +195,19 @@ function buildPreviewMarkdown(input, options) {
|
|
|
172
195
|
const data = extractStreamPreviewData(input);
|
|
173
196
|
const parts = [];
|
|
174
197
|
if (data.planDisplay) {
|
|
175
|
-
parts.push(`**Plan**\n${data.planDisplay}`);
|
|
198
|
+
parts.push(`**Plan**\n${stripCodeFences(data.planDisplay)}`);
|
|
176
199
|
}
|
|
177
200
|
if (data.activityDisplay) {
|
|
178
|
-
parts.push(`**Activity**\n${trimActivityForPreview(data.activityDisplay, data.maxActivity)}`);
|
|
201
|
+
parts.push(`**Activity**\n${stripCodeFences(trimActivityForPreview(data.activityDisplay, data.maxActivity))}`);
|
|
179
202
|
}
|
|
180
203
|
if (data.thinkDisplay && !data.display) {
|
|
181
|
-
parts.push(`**${data.label}**\n${data.thinkDisplay}`);
|
|
204
|
+
parts.push(`**${data.label}**\n${stripCodeFences(data.thinkDisplay)}`);
|
|
182
205
|
}
|
|
183
206
|
else if (data.display) {
|
|
184
207
|
if (data.rawThinking) {
|
|
185
|
-
parts.push(`**${data.label}**\n${data.thinkSnippet}`);
|
|
208
|
+
parts.push(`**${data.label}**\n${stripCodeFences(data.thinkSnippet)}`);
|
|
186
209
|
}
|
|
187
|
-
parts.push(data.preview);
|
|
210
|
+
parts.push(ensureBalancedCodeFences(data.preview));
|
|
188
211
|
}
|
|
189
212
|
if (options?.includeFooter !== false) {
|
|
190
213
|
parts.push(formatPreviewFooter(input.agent, input.elapsedMs, input.meta ?? null));
|
|
@@ -213,21 +236,21 @@ export function buildFinalReplyRender(agent, result) {
|
|
|
213
236
|
let activityText = '';
|
|
214
237
|
let activityNoteText = '';
|
|
215
238
|
if (data.activityNarrative) {
|
|
216
|
-
activityText = `**Activity**\n${data.activityNarrative}\n\n`;
|
|
239
|
+
activityText = `**Activity**\n${stripCodeFences(data.activityNarrative)}\n\n`;
|
|
217
240
|
}
|
|
218
241
|
if (data.activityCommandSummary) {
|
|
219
242
|
activityNoteText = `*${data.activityCommandSummary}*\n\n`;
|
|
220
243
|
}
|
|
221
244
|
let thinkingText = '';
|
|
222
245
|
if (data.thinkingDisplay) {
|
|
223
|
-
thinkingText = `**${data.thinkLabel}**\n${data.thinkingDisplay}\n\n`;
|
|
246
|
+
thinkingText = `**${data.thinkLabel}**\n${stripCodeFences(data.thinkingDisplay)}\n\n`;
|
|
224
247
|
}
|
|
225
248
|
let statusText = '';
|
|
226
249
|
if (data.statusLines) {
|
|
227
250
|
statusText = `**⚠ Incomplete Response**\n${data.statusLines.join('\n')}\n\n`;
|
|
228
251
|
}
|
|
229
252
|
const headerText = `${activityText}${activityNoteText}${statusText}${thinkingText}`;
|
|
230
|
-
const bodyText = data.bodyMessage;
|
|
253
|
+
const bodyText = ensureBalancedCodeFences(data.bodyMessage);
|
|
231
254
|
return {
|
|
232
255
|
fullText: `${headerText}${bodyText}${footerText}`,
|
|
233
256
|
headerText,
|
|
@@ -583,6 +606,10 @@ function normalizeFeishuMarkdown(lines) {
|
|
|
583
606
|
pendingBlankLine = false;
|
|
584
607
|
out.push(line);
|
|
585
608
|
}
|
|
609
|
+
// Safety: close any unclosed code block so it doesn't swallow the rest of
|
|
610
|
+
// a Feishu card element (e.g. truncated body text ending mid-fence).
|
|
611
|
+
if (inCodeBlock)
|
|
612
|
+
out.push('```');
|
|
586
613
|
return out.join('\n');
|
|
587
614
|
}
|
|
588
615
|
export function adaptMarkdownForFeishu(markdown) {
|
package/dist/bot-telegram.js
CHANGED
|
@@ -651,7 +651,8 @@ export class TelegramBot extends Bot {
|
|
|
651
651
|
finalMsgId = await replacePreview(rendered.fullHtml);
|
|
652
652
|
}
|
|
653
653
|
else {
|
|
654
|
-
|
|
654
|
+
// Split: header on first message, footer on last message
|
|
655
|
+
const maxFirst = 3900 - rendered.headerHtml.length;
|
|
655
656
|
let firstBody;
|
|
656
657
|
let remaining;
|
|
657
658
|
if (maxFirst > 200) {
|
|
@@ -665,13 +666,28 @@ export class TelegramBot extends Bot {
|
|
|
665
666
|
firstBody = '';
|
|
666
667
|
remaining = rendered.bodyHtml;
|
|
667
668
|
}
|
|
668
|
-
const firstHtml = `${rendered.headerHtml}${firstBody}${rendered.footerHtml}`;
|
|
669
|
-
finalMsgId = await replacePreview(firstHtml);
|
|
670
669
|
if (remaining.trim()) {
|
|
670
|
+
// Multi-message: header on first, footer on last
|
|
671
|
+
const firstHtml = `${rendered.headerHtml}${firstBody}`;
|
|
672
|
+
finalMsgId = await replacePreview(firstHtml);
|
|
671
673
|
const chunks = splitText(remaining, 3800);
|
|
672
|
-
for (
|
|
673
|
-
|
|
674
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
675
|
+
const isLast = i === chunks.length - 1;
|
|
676
|
+
const chunkText = isLast ? `${chunks[i]}${rendered.footerHtml}` : chunks[i];
|
|
677
|
+
remember(await sendFinalText(chunkText, finalMsgId ?? phId ?? ctx.messageId));
|
|
674
678
|
}
|
|
679
|
+
// Safety: re-clear the Stop keyboard on the placeholder in case the first edit silently failed
|
|
680
|
+
if (phId != null) {
|
|
681
|
+
try {
|
|
682
|
+
await this.channel.editMessage(ctx.chatId, phId, firstHtml || '(done)', { parseMode: 'HTML', keyboard: { inline_keyboard: [] } });
|
|
683
|
+
}
|
|
684
|
+
catch { }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
// Body fits on first message; only footer pushes it over — keep together
|
|
689
|
+
const firstHtml = `${rendered.headerHtml}${firstBody}${rendered.footerHtml}`;
|
|
690
|
+
finalMsgId = await replacePreview(firstHtml);
|
|
675
691
|
}
|
|
676
692
|
}
|
|
677
693
|
return { primaryMessageId: finalMsgId, messageIds };
|
package/dist/cli.js
CHANGED
|
@@ -299,10 +299,15 @@ Docs: https://github.com/xiaotonng/pikiclaw
|
|
|
299
299
|
*/
|
|
300
300
|
function persistWorkdir(args, userConfig) {
|
|
301
301
|
if (!process.env.PIKICLAW_DAEMON_CHILD) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
302
|
+
// Only overwrite persisted workdir when the user explicitly passed -w.
|
|
303
|
+
// Falling back to cwd when no flag is given can clobber a valid saved
|
|
304
|
+
// workdir with a temp directory (e.g. after a non-daemon restart).
|
|
305
|
+
if (args.workdir) {
|
|
306
|
+
const cliWorkdir = path.resolve(args.workdir);
|
|
307
|
+
if (userConfig.workdir !== cliWorkdir) {
|
|
308
|
+
updateUserConfig({ workdir: cliWorkdir });
|
|
309
|
+
return loadUserConfig();
|
|
310
|
+
}
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
return userConfig;
|
package/dist/code-agent.js
CHANGED
|
@@ -1209,32 +1209,15 @@ export function getProjectSkillPaths(workdir, skillName) {
|
|
|
1209
1209
|
const sharedSkillFile = path.join(workdir, '.pikiclaw', 'skills', skillName, 'SKILL.md');
|
|
1210
1210
|
const agentsSkillFile = path.join(workdir, '.agents', 'skills', skillName, 'SKILL.md');
|
|
1211
1211
|
const claudeSkillFile = path.join(workdir, '.claude', 'skills', skillName, 'SKILL.md');
|
|
1212
|
-
const claudeCommandFile = path.join(workdir, '.claude', 'commands', `${skillName}.md`);
|
|
1213
1212
|
return {
|
|
1214
1213
|
sharedSkillFile: hasFile(sharedSkillFile) ? sharedSkillFile : null,
|
|
1215
1214
|
agentsSkillFile: hasFile(agentsSkillFile) ? agentsSkillFile : null,
|
|
1216
1215
|
claudeSkillFile: hasFile(claudeSkillFile) ? claudeSkillFile : null,
|
|
1217
|
-
claudeCommandFile: hasFile(claudeCommandFile) ? claudeCommandFile : null,
|
|
1218
1216
|
};
|
|
1219
1217
|
}
|
|
1220
1218
|
export function listSkills(workdir) {
|
|
1221
1219
|
const skills = [];
|
|
1222
1220
|
const seen = new Set();
|
|
1223
|
-
const commandsDir = path.join(workdir, '.claude', 'commands');
|
|
1224
|
-
for (const entry of readSortedDir(commandsDir)) {
|
|
1225
|
-
if (!entry.endsWith('.md'))
|
|
1226
|
-
continue;
|
|
1227
|
-
const name = entry.replace(/\.md$/, '');
|
|
1228
|
-
if (!name || seen.has(name))
|
|
1229
|
-
continue;
|
|
1230
|
-
let meta = { label: null, description: null };
|
|
1231
|
-
try {
|
|
1232
|
-
meta = parseSkillMeta(fs.readFileSync(path.join(commandsDir, entry), 'utf-8'));
|
|
1233
|
-
}
|
|
1234
|
-
catch { }
|
|
1235
|
-
skills.push({ name, label: meta.label, description: meta.description, source: 'commands' });
|
|
1236
|
-
seen.add(name);
|
|
1237
|
-
}
|
|
1238
1221
|
const skillRoots = [
|
|
1239
1222
|
path.join(workdir, '.pikiclaw', 'skills'),
|
|
1240
1223
|
];
|
package/dist/driver-claude.js
CHANGED
|
@@ -223,9 +223,10 @@ function getNativeClaudeSessions(workdir) {
|
|
|
223
223
|
const filePath = path.join(projectDir, entry.name);
|
|
224
224
|
try {
|
|
225
225
|
const stat = fs.statSync(filePath);
|
|
226
|
-
// Read
|
|
226
|
+
// Read enough bytes to get past the system_prompt line (can be 20KB+) and
|
|
227
|
+
// reach the first user/assistant events for title and model extraction.
|
|
227
228
|
const fd = fs.openSync(filePath, 'r');
|
|
228
|
-
const buf = Buffer.alloc(
|
|
229
|
+
const buf = Buffer.alloc(65536);
|
|
229
230
|
const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
230
231
|
fs.closeSync(fd);
|
|
231
232
|
const head = buf.toString('utf8', 0, bytesRead);
|
package/dist/process-control.js
CHANGED
package/dist/run.js
CHANGED
|
@@ -112,14 +112,13 @@ async function main() {
|
|
|
112
112
|
case 'skills': {
|
|
113
113
|
const result = listSkills(workdir);
|
|
114
114
|
if (!result.skills.length) {
|
|
115
|
-
process.stdout.write(`No custom skills found in ${workdir} (.pikiclaw/skills
|
|
115
|
+
process.stdout.write(`No custom skills found in ${workdir} (.pikiclaw/skills)\n`);
|
|
116
116
|
break;
|
|
117
117
|
}
|
|
118
118
|
process.stdout.write(`Project skills (${result.skills.length}):\n\n`);
|
|
119
119
|
for (const sk of result.skills) {
|
|
120
|
-
const src = sk.source === 'skills' ? 'skill' : 'command';
|
|
121
120
|
const desc = sk.description ? ` ${sk.description}` : '';
|
|
122
|
-
process.stdout.write(` ${sk.name}
|
|
121
|
+
process.stdout.write(` ${sk.name}${desc}\n`);
|
|
123
122
|
}
|
|
124
123
|
break;
|
|
125
124
|
}
|
package/package.json
CHANGED