@cydm/pie 1.0.3 → 1.0.4
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/cli.js +302 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -69314,6 +69314,75 @@ function findFirstChangedLine(oldText, newText) {
|
|
|
69314
69314
|
return void 0;
|
|
69315
69315
|
}
|
|
69316
69316
|
|
|
69317
|
+
// src/todo/render.ts
|
|
69318
|
+
var TODO_STATUS_ICONS = {
|
|
69319
|
+
completed: "\u2713",
|
|
69320
|
+
"in-progress": "\u25C9",
|
|
69321
|
+
"not-started": "\u25CB"
|
|
69322
|
+
};
|
|
69323
|
+
function formatTodoTitle(todo, activeTheme) {
|
|
69324
|
+
if (todo.status === "completed") {
|
|
69325
|
+
return activeTheme.fg("dim", activeTheme.strikethrough(todo.title));
|
|
69326
|
+
}
|
|
69327
|
+
if (todo.status === "in-progress") {
|
|
69328
|
+
return activeTheme.fg("warning", todo.title);
|
|
69329
|
+
}
|
|
69330
|
+
return activeTheme.fg("muted", todo.title);
|
|
69331
|
+
}
|
|
69332
|
+
function formatTodoIcon(todo, activeTheme) {
|
|
69333
|
+
const icon = TODO_STATUS_ICONS[todo.status] ?? "?";
|
|
69334
|
+
if (todo.status === "completed") {
|
|
69335
|
+
return activeTheme.fg("success", icon);
|
|
69336
|
+
}
|
|
69337
|
+
if (todo.status === "in-progress") {
|
|
69338
|
+
return activeTheme.fg("warning", icon);
|
|
69339
|
+
}
|
|
69340
|
+
return activeTheme.fg("muted", icon);
|
|
69341
|
+
}
|
|
69342
|
+
function renderManageTodoListCall(args, activeTheme = theme) {
|
|
69343
|
+
let text = activeTheme.fg("toolTitle", activeTheme.bold("manage_todo_list "));
|
|
69344
|
+
text += activeTheme.fg("muted", args.operation || "...");
|
|
69345
|
+
if (args.operation === "write" && Array.isArray(args.todoList)) {
|
|
69346
|
+
const count = args.todoList.length;
|
|
69347
|
+
text += activeTheme.fg("dim", ` (${count} item${count === 1 ? "" : "s"})`);
|
|
69348
|
+
}
|
|
69349
|
+
return text;
|
|
69350
|
+
}
|
|
69351
|
+
function renderManageTodoListResult(result, options, activeTheme = theme) {
|
|
69352
|
+
const details = result.details;
|
|
69353
|
+
if (!details) {
|
|
69354
|
+
const firstText = result.content?.find((item) => item.type === "text" && typeof item.text === "string")?.text;
|
|
69355
|
+
return firstText || "";
|
|
69356
|
+
}
|
|
69357
|
+
if (details.error) {
|
|
69358
|
+
return activeTheme.fg("error", `\u2717 ${details.error}`);
|
|
69359
|
+
}
|
|
69360
|
+
const todos = details.todos || [];
|
|
69361
|
+
if (todos.length === 0) {
|
|
69362
|
+
return activeTheme.fg("dim", "No todos");
|
|
69363
|
+
}
|
|
69364
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
69365
|
+
let text = `${activeTheme.fg("success", "\u2713")} ${activeTheme.fg("muted", `${completed}/${todos.length} completed`)}`;
|
|
69366
|
+
if (options.expanded) {
|
|
69367
|
+
for (const todo of todos) {
|
|
69368
|
+
text += `
|
|
69369
|
+
${formatTodoIcon(todo, activeTheme)} ${activeTheme.fg("accent", `${todo.id}.`)} ${formatTodoTitle(todo, activeTheme)}`;
|
|
69370
|
+
}
|
|
69371
|
+
}
|
|
69372
|
+
return text;
|
|
69373
|
+
}
|
|
69374
|
+
function renderTodoWidgetLines(todos, activeTheme = theme) {
|
|
69375
|
+
if (todos.length === 0) {
|
|
69376
|
+
return [];
|
|
69377
|
+
}
|
|
69378
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
69379
|
+
const lines = [activeTheme.fg("muted", `Todo List - ${completed}/${todos.length} completed`)];
|
|
69380
|
+
for (const todo of todos) {
|
|
69381
|
+
lines.push(` ${formatTodoIcon(todo, activeTheme)} ${activeTheme.fg("accent", `${todo.id}.`)} ${formatTodoTitle(todo, activeTheme)}`);
|
|
69382
|
+
}
|
|
69383
|
+
return lines;
|
|
69384
|
+
}
|
|
69385
|
+
|
|
69317
69386
|
// src/components/tool-execution.ts
|
|
69318
69387
|
function formatSize3(bytes) {
|
|
69319
69388
|
if (bytes < 1024) return `${bytes}B`;
|
|
@@ -69396,7 +69465,13 @@ var ToolExecutionComponent = class extends Container {
|
|
|
69396
69465
|
formatToolExecution() {
|
|
69397
69466
|
let text = "";
|
|
69398
69467
|
const invalidArg = theme.fg("error", "[invalid arg]");
|
|
69399
|
-
if (this.toolName === "
|
|
69468
|
+
if (this.toolName === "manage_todo_list") {
|
|
69469
|
+
text = renderManageTodoListCall(this.args, theme);
|
|
69470
|
+
if (this.result) {
|
|
69471
|
+
text += `
|
|
69472
|
+
${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
|
|
69473
|
+
}
|
|
69474
|
+
} else if (this.toolName === "read") {
|
|
69400
69475
|
const rawPath = str(this.args?.file_path ?? this.args?.path);
|
|
69401
69476
|
const path18 = rawPath !== null ? shortenPath(rawPath) : null;
|
|
69402
69477
|
const offset = this.args?.offset;
|
|
@@ -69732,6 +69807,27 @@ ${theme.fg("error", "Error: " + errorOutput)}`;
|
|
|
69732
69807
|
}
|
|
69733
69808
|
};
|
|
69734
69809
|
|
|
69810
|
+
// src/components/todo-widget.ts
|
|
69811
|
+
var TodoWidgetComponent = class extends Container {
|
|
69812
|
+
state;
|
|
69813
|
+
constructor(state) {
|
|
69814
|
+
super();
|
|
69815
|
+
this.state = state;
|
|
69816
|
+
this.refresh();
|
|
69817
|
+
}
|
|
69818
|
+
refresh() {
|
|
69819
|
+
this.clear();
|
|
69820
|
+
const lines = renderTodoWidgetLines(this.state.read());
|
|
69821
|
+
if (lines.length === 0) {
|
|
69822
|
+
return;
|
|
69823
|
+
}
|
|
69824
|
+
this.addChild(new Spacer(1));
|
|
69825
|
+
for (const line of lines) {
|
|
69826
|
+
this.addChild(new Text(line, 1, 0));
|
|
69827
|
+
}
|
|
69828
|
+
}
|
|
69829
|
+
};
|
|
69830
|
+
|
|
69735
69831
|
// src/components/tree-selector.ts
|
|
69736
69832
|
var RESET = "\x1B[0m";
|
|
69737
69833
|
var INVERSE = "\x1B[7m";
|
|
@@ -74718,6 +74814,8 @@ var InteractiveMode = class {
|
|
|
74718
74814
|
footerData;
|
|
74719
74815
|
editor;
|
|
74720
74816
|
editorContainer;
|
|
74817
|
+
todoWidgetContainer;
|
|
74818
|
+
todoWidget;
|
|
74721
74819
|
keybindings;
|
|
74722
74820
|
sessionManager;
|
|
74723
74821
|
skills;
|
|
@@ -74827,6 +74925,11 @@ var InteractiveMode = class {
|
|
|
74827
74925
|
this.skillsSection = formatSkillsForPrompt(options.skills);
|
|
74828
74926
|
}
|
|
74829
74927
|
options;
|
|
74928
|
+
refreshTodoWidget() {
|
|
74929
|
+
this.todoWidget.refresh();
|
|
74930
|
+
this.todoWidgetContainer.invalidate?.();
|
|
74931
|
+
this.safeRequestRender();
|
|
74932
|
+
}
|
|
74830
74933
|
async init() {
|
|
74831
74934
|
const savedLevel = this.options.settingsManager.getDefaultThinkingLevel();
|
|
74832
74935
|
if (savedLevel) {
|
|
@@ -74847,6 +74950,8 @@ var InteractiveMode = class {
|
|
|
74847
74950
|
});
|
|
74848
74951
|
this.updateTerminalTitle();
|
|
74849
74952
|
this.setupUI();
|
|
74953
|
+
this.options.todoState.restoreFromMessages(this.sessionManager.getMessages());
|
|
74954
|
+
this.refreshTodoWidget();
|
|
74850
74955
|
this.setupAutocomplete();
|
|
74851
74956
|
this.setupAgentSubscription();
|
|
74852
74957
|
this.setupEditorSubmitHandler();
|
|
@@ -75744,6 +75849,9 @@ ${newToolsSection}`
|
|
|
75744
75849
|
contextWindow: this.options.model?.contextWindow ?? 0
|
|
75745
75850
|
});
|
|
75746
75851
|
this.editorContainer = new Container();
|
|
75852
|
+
this.todoWidgetContainer = new Container();
|
|
75853
|
+
this.todoWidget = new TodoWidgetComponent(this.options.todoState);
|
|
75854
|
+
this.todoWidgetContainer.addChild(this.todoWidget);
|
|
75747
75855
|
const editorTheme = getEditorTheme();
|
|
75748
75856
|
this.editor = new Editor(this.ui, editorTheme, { paddingX: 1 });
|
|
75749
75857
|
this.editor.onChange = () => {
|
|
@@ -75757,6 +75865,7 @@ ${newToolsSection}`
|
|
|
75757
75865
|
this.editorContainer.addChild(this.editor);
|
|
75758
75866
|
this.ui.addChild(this.chatContainer);
|
|
75759
75867
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
75868
|
+
this.ui.addChild(this.todoWidgetContainer);
|
|
75760
75869
|
this.ui.addChild(new Spacer());
|
|
75761
75870
|
this.ui.addChild(this.footer);
|
|
75762
75871
|
this.ui.addChild(this.editorContainer);
|
|
@@ -75996,7 +76105,18 @@ ${newToolsSection}`
|
|
|
75996
76105
|
thinkingContent += content2.thinking || "";
|
|
75997
76106
|
} else if (content2.type === "toolCall") {
|
|
75998
76107
|
const toolCall = content2;
|
|
75999
|
-
const
|
|
76108
|
+
const contentIndex = message.content.indexOf(content2);
|
|
76109
|
+
const partialToolId = `__partial_tool__:${contentIndex}`;
|
|
76110
|
+
const toolId = toolCall.id || partialToolId;
|
|
76111
|
+
if (toolCall.id && !this.pendingTools.has(toolCall.id) && this.pendingTools.has(partialToolId)) {
|
|
76112
|
+
const existingComponent = this.pendingTools.get(partialToolId);
|
|
76113
|
+
if (existingComponent) {
|
|
76114
|
+
this.pendingTools.delete(partialToolId);
|
|
76115
|
+
this.pendingTools.set(toolCall.id, existingComponent);
|
|
76116
|
+
existingComponent.updateArgs(toolCall.arguments);
|
|
76117
|
+
}
|
|
76118
|
+
continue;
|
|
76119
|
+
}
|
|
76000
76120
|
if (!this.pendingTools.has(toolId)) {
|
|
76001
76121
|
const toolComponent = new ToolExecutionComponent(
|
|
76002
76122
|
toolCall.name,
|
|
@@ -76084,6 +76204,9 @@ ${newToolsSection}`
|
|
|
76084
76204
|
);
|
|
76085
76205
|
}
|
|
76086
76206
|
}
|
|
76207
|
+
if (event.toolName === "manage_todo_list") {
|
|
76208
|
+
this.refreshTodoWidget();
|
|
76209
|
+
}
|
|
76087
76210
|
this.footer.setState({ lifecycleEvent: "tool_execution_end" });
|
|
76088
76211
|
this.emitExtensionEvent({
|
|
76089
76212
|
type: "tool:execution:end",
|
|
@@ -77427,11 +77550,13 @@ ${newToolsSection}`
|
|
|
77427
77550
|
this.updateTerminalTitle();
|
|
77428
77551
|
const messages = this.sessionManager.getMessages();
|
|
77429
77552
|
const conversationMessages = messages.filter(
|
|
77430
|
-
(msg) => msg.role === "user" || msg.role === "assistant"
|
|
77553
|
+
(msg) => msg.role === "user" || msg.role === "assistant" || msg.role === "toolResult"
|
|
77431
77554
|
);
|
|
77432
77555
|
this.agent.resetProcessingState();
|
|
77433
77556
|
this.agent.replaceMessages(conversationMessages);
|
|
77434
77557
|
this.lastSyncedMessageCount = this.agent.state.messages.length;
|
|
77558
|
+
this.options.todoState.restoreFromMessages(messages);
|
|
77559
|
+
this.refreshTodoWidget();
|
|
77435
77560
|
this.chatContainer.clear();
|
|
77436
77561
|
this.lastStatusSpacer = void 0;
|
|
77437
77562
|
this.lastStatusText = void 0;
|
|
@@ -78739,6 +78864,177 @@ var ModelRegistry = class {
|
|
|
78739
78864
|
}
|
|
78740
78865
|
};
|
|
78741
78866
|
|
|
78867
|
+
// src/todo/state-manager.ts
|
|
78868
|
+
var TodoStateManager = class {
|
|
78869
|
+
todos = [];
|
|
78870
|
+
read() {
|
|
78871
|
+
return this.todos.map((todo) => ({ ...todo }));
|
|
78872
|
+
}
|
|
78873
|
+
write(todos) {
|
|
78874
|
+
this.todos = todos.map((todo) => ({ ...todo }));
|
|
78875
|
+
}
|
|
78876
|
+
clear() {
|
|
78877
|
+
this.todos = [];
|
|
78878
|
+
}
|
|
78879
|
+
getStats() {
|
|
78880
|
+
const total = this.todos.length;
|
|
78881
|
+
const completed = this.todos.filter((todo) => todo.status === "completed").length;
|
|
78882
|
+
const inProgress = this.todos.filter((todo) => todo.status === "in-progress").length;
|
|
78883
|
+
return {
|
|
78884
|
+
total,
|
|
78885
|
+
completed,
|
|
78886
|
+
inProgress,
|
|
78887
|
+
notStarted: total - completed - inProgress
|
|
78888
|
+
};
|
|
78889
|
+
}
|
|
78890
|
+
validate(todos) {
|
|
78891
|
+
const errors = [];
|
|
78892
|
+
if (!Array.isArray(todos)) {
|
|
78893
|
+
return { valid: false, errors: ["todoList must be an array"] };
|
|
78894
|
+
}
|
|
78895
|
+
const validStatuses = /* @__PURE__ */ new Set(["not-started", "in-progress", "completed"]);
|
|
78896
|
+
for (let index = 0; index < todos.length; index++) {
|
|
78897
|
+
const item = todos[index];
|
|
78898
|
+
const prefix = `Item ${index + 1}`;
|
|
78899
|
+
if (typeof item?.id !== "number" || !Number.isInteger(item.id) || item.id < 1) {
|
|
78900
|
+
errors.push(`${prefix}: 'id' must be a positive integer`);
|
|
78901
|
+
}
|
|
78902
|
+
if (typeof item?.title !== "string" || item.title.trim().length === 0) {
|
|
78903
|
+
errors.push(`${prefix}: 'title' is required`);
|
|
78904
|
+
}
|
|
78905
|
+
if (typeof item?.description !== "string") {
|
|
78906
|
+
errors.push(`${prefix}: 'description' must be a string`);
|
|
78907
|
+
}
|
|
78908
|
+
if (!validStatuses.has(item?.status)) {
|
|
78909
|
+
errors.push(`${prefix}: 'status' must be one of: not-started, in-progress, completed`);
|
|
78910
|
+
}
|
|
78911
|
+
if (item?.id !== index + 1) {
|
|
78912
|
+
errors.push(`${prefix}: ids must be sequential starting from 1`);
|
|
78913
|
+
}
|
|
78914
|
+
}
|
|
78915
|
+
return { valid: errors.length === 0, errors };
|
|
78916
|
+
}
|
|
78917
|
+
restoreFromMessages(messages) {
|
|
78918
|
+
this.todos = [];
|
|
78919
|
+
for (const message of messages) {
|
|
78920
|
+
if (message.role !== "toolResult" || message.toolName !== "manage_todo_list") {
|
|
78921
|
+
continue;
|
|
78922
|
+
}
|
|
78923
|
+
const details = message.details;
|
|
78924
|
+
if (details?.todos) {
|
|
78925
|
+
this.todos = details.todos.map((todo) => ({ ...todo }));
|
|
78926
|
+
}
|
|
78927
|
+
}
|
|
78928
|
+
}
|
|
78929
|
+
};
|
|
78930
|
+
|
|
78931
|
+
// src/todo/tool.ts
|
|
78932
|
+
init_esm();
|
|
78933
|
+
function stringEnum(values, description) {
|
|
78934
|
+
return Type.Union(values.map((value) => Type.Literal(value)), { description });
|
|
78935
|
+
}
|
|
78936
|
+
var TodoItemSchema = Type.Object({
|
|
78937
|
+
id: Type.Number({ description: "Unique identifier for the todo. Use sequential numbers starting from 1." }),
|
|
78938
|
+
title: Type.String({ description: "Concise action-oriented todo label (3-7 words). Displayed in the UI." }),
|
|
78939
|
+
description: Type.String({
|
|
78940
|
+
description: "Detailed context, requirements, or implementation notes for the todo item."
|
|
78941
|
+
}),
|
|
78942
|
+
status: stringEnum(
|
|
78943
|
+
["not-started", "in-progress", "completed"],
|
|
78944
|
+
"not-started: Not begun | in-progress: Currently working | completed: Fully finished with no blockers"
|
|
78945
|
+
)
|
|
78946
|
+
});
|
|
78947
|
+
var ManageTodoListParams = Type.Object({
|
|
78948
|
+
operation: stringEnum(
|
|
78949
|
+
["write", "read"],
|
|
78950
|
+
"write: Replace entire todo list with new content. read: Retrieve current todo list."
|
|
78951
|
+
),
|
|
78952
|
+
todoList: Type.Optional(
|
|
78953
|
+
Type.Array(TodoItemSchema, {
|
|
78954
|
+
description: "Complete array of all todo items. Required for write. Must include ALL items."
|
|
78955
|
+
})
|
|
78956
|
+
)
|
|
78957
|
+
});
|
|
78958
|
+
var MANAGE_TODO_LIST_DESCRIPTION = `Manage a structured todo list to track progress and plan tasks throughout your coding session.
|
|
78959
|
+
|
|
78960
|
+
Use this tool for complex multi-step work, especially when:
|
|
78961
|
+
- The task needs planning or progress tracking
|
|
78962
|
+
- The user provided multiple requests
|
|
78963
|
+
- You need to break a larger change into actionable steps
|
|
78964
|
+
- You are about to start or finish a tracked step
|
|
78965
|
+
|
|
78966
|
+
Workflow:
|
|
78967
|
+
1. Write the full todo list with clear action items
|
|
78968
|
+
2. Mark the current item as in-progress before starting it
|
|
78969
|
+
3. Complete the work for that item
|
|
78970
|
+
4. Mark it completed immediately
|
|
78971
|
+
5. Continue until all items are done
|
|
78972
|
+
|
|
78973
|
+
Always send the full list on write. Partial updates are not supported.`;
|
|
78974
|
+
function successMessage(todos) {
|
|
78975
|
+
const total = todos.length;
|
|
78976
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
78977
|
+
let message = `Todos have been modified successfully. ${completed}/${total} completed. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable.`;
|
|
78978
|
+
if (total < 3) {
|
|
78979
|
+
message += "\n\nWarning: Small todo list (<3 items). This task might not need a todo list.";
|
|
78980
|
+
}
|
|
78981
|
+
return message;
|
|
78982
|
+
}
|
|
78983
|
+
function createManageTodoListTool(state) {
|
|
78984
|
+
return {
|
|
78985
|
+
name: "manage_todo_list",
|
|
78986
|
+
label: "manage_todo_list",
|
|
78987
|
+
description: MANAGE_TODO_LIST_DESCRIPTION,
|
|
78988
|
+
parameters: ManageTodoListParams,
|
|
78989
|
+
execute: async (_toolCallId, params) => {
|
|
78990
|
+
if (params.operation === "read") {
|
|
78991
|
+
const todos2 = state.read();
|
|
78992
|
+
return {
|
|
78993
|
+
content: [
|
|
78994
|
+
{
|
|
78995
|
+
type: "text",
|
|
78996
|
+
text: todos2.length ? JSON.stringify(todos2, null, 2) : "No todos. Use write operation to create a todo list."
|
|
78997
|
+
}
|
|
78998
|
+
],
|
|
78999
|
+
details: { operation: "read", todos: todos2 }
|
|
79000
|
+
};
|
|
79001
|
+
}
|
|
79002
|
+
if (!params.todoList || !Array.isArray(params.todoList)) {
|
|
79003
|
+
return {
|
|
79004
|
+
content: [{ type: "text", text: "Error: todoList is required for write operation." }],
|
|
79005
|
+
details: { operation: "write", todos: state.read(), error: "todoList required" },
|
|
79006
|
+
isError: true
|
|
79007
|
+
};
|
|
79008
|
+
}
|
|
79009
|
+
const todoList = params.todoList;
|
|
79010
|
+
const validation = state.validate(todoList);
|
|
79011
|
+
if (!validation.valid) {
|
|
79012
|
+
return {
|
|
79013
|
+
content: [
|
|
79014
|
+
{
|
|
79015
|
+
type: "text",
|
|
79016
|
+
text: `Validation failed:
|
|
79017
|
+
${validation.errors.map((error) => ` - ${error}`).join("\n")}`
|
|
79018
|
+
}
|
|
79019
|
+
],
|
|
79020
|
+
details: {
|
|
79021
|
+
operation: "write",
|
|
79022
|
+
todos: state.read(),
|
|
79023
|
+
error: validation.errors.join("; ")
|
|
79024
|
+
},
|
|
79025
|
+
isError: true
|
|
79026
|
+
};
|
|
79027
|
+
}
|
|
79028
|
+
state.write(todoList);
|
|
79029
|
+
const todos = state.read();
|
|
79030
|
+
return {
|
|
79031
|
+
content: [{ type: "text", text: successMessage(todos) }],
|
|
79032
|
+
details: { operation: "write", todos }
|
|
79033
|
+
};
|
|
79034
|
+
}
|
|
79035
|
+
};
|
|
79036
|
+
}
|
|
79037
|
+
|
|
78742
79038
|
// src/cli.ts
|
|
78743
79039
|
enableEarlyLogBuffer();
|
|
78744
79040
|
var logError = (msg, err) => {
|
|
@@ -78830,7 +79126,8 @@ async function startChat(initialPrompt, testCommand) {
|
|
|
78830
79126
|
settingsManager.setDefaultModelAndProvider(provider, modelId);
|
|
78831
79127
|
const cwd = process.cwd();
|
|
78832
79128
|
const fsOptions = { allowlistedDirs: [CONFIG_DIR] };
|
|
78833
|
-
const
|
|
79129
|
+
const todoState = new TodoStateManager();
|
|
79130
|
+
const tools = [...createFileSystemTools(cwd, fsOptions), createBashTool(cwd), createManageTodoListTool(todoState)];
|
|
78834
79131
|
const sessionManager = createSessionManager({ sessionsDir: SESSIONS_DIR });
|
|
78835
79132
|
if (explicitSessionId) {
|
|
78836
79133
|
const loaded = await sessionManager.loadSession(explicitSessionId);
|
|
@@ -78948,6 +79245,7 @@ Current working directory: ${cwd}${skillsSection}`;
|
|
|
78948
79245
|
model,
|
|
78949
79246
|
apiKey,
|
|
78950
79247
|
tools,
|
|
79248
|
+
todoState,
|
|
78951
79249
|
sessionManager,
|
|
78952
79250
|
skills,
|
|
78953
79251
|
cwd,
|