arki 0.0.3 → 0.0.5

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
@@ -1,3 +1,5 @@
1
+ > **⚠️ Warning: This project is under active development. Please do not use it in production environments.**
2
+
1
3
  # Arki - AI Agent Programming Assistant
2
4
 
3
5
  Arki is an AI agent programming tool.
@@ -43,6 +45,7 @@ arki [options]
43
45
  Options:
44
46
  -p <path> Specify working directory
45
47
  --debug, -d Enable debug mode, show detailed logs
48
+ --reset Reset configuration to factory defaults
46
49
  --help, -h Show help information
47
50
  ```
48
51
 
@@ -81,39 +84,13 @@ Ways to enable:
81
84
 
82
85
  Configuration file is located at `~/.config/arki/config.json`:
83
86
 
84
- ```json
85
- {
86
- "agents": {
87
- "main": {
88
- "model": "gpt-5.1",
89
- "flex": false,
90
- "reasoningEffort": "medium"
91
- },
92
- "coder": {
93
- "model": "gpt-5.2",
94
- "flex": false,
95
- "reasoningEffort": "high"
96
- }
97
- }
98
- }
99
- ```
100
-
101
- Each Agent configuration:
102
- - `model` - Model ID to use
103
- - `flex` - Use Flex API (low priority, low cost)
104
- - `reasoningEffort` - Reasoning effort, optional values: `low`, `medium`, `high` (for models that support thinking mode)
105
-
106
- Default configuration is automatically copied from the package on first run.
87
+ ### Reset to Factory Defaults
107
88
 
108
- ## Built-in Tools
109
-
110
- Arki includes the following built-in tools that the AI assistant can automatically call:
89
+ ```bash
90
+ arki --reset
91
+ ```
111
92
 
112
- - `read_file` - Read file content
113
- - `write_file` - Write to file
114
- - `list_directory` - List directory contents
115
- - `run_command` - Execute shell commands
116
- - `get_tool_info` - View detailed usage instructions for tools
93
+ This will delete the current configuration file. The default configuration will be used on next startup.
117
94
 
118
95
  ## Development
119
96
 
package/dist/index.d.ts CHANGED
@@ -39,31 +39,44 @@ declare class AIMsg extends Msg {
39
39
  readonly type = MsgType.AI;
40
40
  constructor(content: string);
41
41
  }
42
+ /**
43
+ * Single tool call
44
+ */
45
+ interface ToolCall {
46
+ name: string;
47
+ arguments: Record<string, unknown>;
48
+ }
42
49
  /**
43
50
  * Tool call message constructor
44
51
  */
45
52
  declare class ToolCallMsg extends Msg {
46
53
  readonly type = MsgType.ToolCall;
47
- readonly toolCalls: Array<{
48
- name: string;
49
- arguments: Record<string, unknown>;
50
- }>;
51
- constructor(content: string, toolCalls: Array<{
52
- name: string;
53
- arguments: Record<string, unknown>;
54
- }>);
54
+ readonly toolCalls: ToolCall[];
55
+ constructor(content: string, toolCalls: ToolCall[]);
55
56
  }
56
57
  /**
57
- * Tool result message constructor
58
+ * Single tool result
59
+ */
60
+ interface ToolResult {
61
+ toolName: string;
62
+ result: string;
63
+ isError?: boolean;
64
+ }
65
+ /**
66
+ * Tool result message constructor (contains multiple results)
58
67
  */
59
68
  declare class ToolResultMsg extends Msg {
60
69
  readonly type = MsgType.ToolResult;
61
- readonly toolName: string;
62
- readonly result: string;
63
- readonly isError?: boolean;
64
- constructor(toolName: string, result: string, isError?: boolean);
70
+ readonly toolResults: ToolResult[];
71
+ constructor(toolResults: ToolResult[]);
72
+ /** Helper: create from single result */
73
+ static single(toolName: string, result: string, isError?: boolean): ToolResultMsg;
65
74
  }
66
75
 
76
+ /**
77
+ * Symbol indicating tool has detailed manual (needs to call read_tool_manual before use)
78
+ */
79
+ declare const HAS_MANUAL = "\uD83D\uDCD8";
67
80
  /**
68
81
  * Tool class
69
82
  */
@@ -88,6 +101,7 @@ declare class Tool {
88
101
  * Parse manual.md content
89
102
  * First line format: "tool_name: description", extract description
90
103
  * Remaining content is the manual
104
+ * If manual has content, prepend HAS_MANUAL symbol to description
91
105
  */
92
106
  static parseManual(content: string): {
93
107
  description: string;
@@ -96,7 +110,29 @@ declare class Tool {
96
110
  /**
97
111
  * Execute tool (with error handling and logging)
98
112
  */
99
- run(args: Record<string, unknown>): Promise<ToolResultMsg>;
113
+ run(args: Record<string, unknown>): Promise<ToolResult>;
114
+ }
115
+
116
+ /**
117
+ * Procedure class - step-by-step guide for specific workflows
118
+ */
119
+ declare class Procedure {
120
+ readonly name: string;
121
+ readonly description: string;
122
+ readonly manual: string;
123
+ constructor(config: {
124
+ name: string;
125
+ procedureContent: string;
126
+ });
127
+ /**
128
+ * Parse procedure.md content
129
+ * First line format: "procedure_name: description", extract description
130
+ * Remaining content is the procedure steps
131
+ */
132
+ static parseManual(content: string): {
133
+ description: string;
134
+ manual: string;
135
+ };
100
136
  }
101
137
 
102
138
  /**
@@ -207,6 +243,8 @@ declare let workingDir: string;
207
243
  declare function setWorkingDir(dir: string): void;
208
244
  /** Global tool registry */
209
245
  declare const TOOLS: Record<string, Tool>;
246
+ /** Global procedure registry */
247
+ declare const PROCEDURES: Record<string, Procedure>;
210
248
  /** Global Adapter instance */
211
249
  declare let adapter: Adapter | null;
212
250
  /** Initialize global state */
@@ -214,29 +252,31 @@ declare function init(cwd?: string): Promise<void>;
214
252
 
215
253
  /**
216
254
  * Debug logging module
255
+ * All debug output is single-line for log-friendly format
217
256
  */
218
257
  /** Get debug mode status */
219
258
  declare function isDebugMode(): boolean;
220
259
  /** Set debug mode */
221
260
  declare function setDebugMode(enabled: boolean): void;
222
261
  /**
223
- * Debug log function
262
+ * Debug log function - single line output with timestamp
224
263
  * @param category Log category (e.g., 'API', 'Agent', 'Tool')
225
264
  * @param message Log message
226
- * @param data Optional additional data
265
+ * @param data Optional additional data (will be formatted to single line)
227
266
  */
228
267
  declare function debug(category: string, message: string, data?: unknown): void;
229
268
 
230
269
  /**
231
270
  * General logging module
271
+ * All log output is single-line with timestamp prefix
272
+ * Supports XML-style color tags: <red>text</red>, <bold>text</bold>, etc.
232
273
  */
233
-
234
274
  /**
235
- * Colored log output
236
- * @param color Color name
237
- * @param args Content to output
275
+ * Log output with timestamp and XML color tag support
276
+ * @param message Message string with optional XML color tags
277
+ * @example log('<yellow>[TOOL]</yellow> read_file <dim>{"path":"test.txt"}</dim>')
238
278
  */
239
- declare function log(color: ColorName, ...args: unknown[]): void;
279
+ declare function log(message: string): void;
240
280
  /**
241
281
  * Info log
242
282
  */
@@ -257,6 +297,7 @@ declare function error(message: string): void;
257
297
  /**
258
298
  * Logging module
259
299
  * Provides debug mode and logging functionality
300
+ * Supports XML-style color tags: <red>text</red>, <bold>text</bold>, etc.
260
301
  */
261
302
  /**
262
303
  * Terminal colors and style definitions
@@ -279,6 +320,16 @@ declare const colors: {
279
320
  };
280
321
  /** Color name type */
281
322
  type ColorName = keyof typeof colors;
323
+ /**
324
+ * Convert XML-style color tags to ANSI escape sequences
325
+ * Example: "<red>error</red>" -> "\x1b[31merror\x1b[0m"
326
+ */
327
+ declare function convertColorTags(str: string): string;
328
+ /**
329
+ * Create a buffered streaming color converter
330
+ * Used for streaming output where tags may span multiple chunks
331
+ */
332
+ declare function createColorConverter(): (chunk: string) => string;
282
333
 
283
334
  declare class OpenAIAdapter extends Adapter {
284
335
  private client;
@@ -353,4 +404,4 @@ interface Model {
353
404
  readonly capabilities: ModelCapabilities;
354
405
  }
355
406
 
356
- export { AIMsg, Adapter, type AdapterResponse, Agent, type AgentResponse, type ColorName, MAX_COMPLETION_TOKENS, MODELS, type Model, type ModelCapabilities, type ModelProvider, Msg, MsgType, OpenAIAdapter, type ReasoningEffort$1 as ReasoningEffort, SystemMsg, TEMPERATURE, TOOLS, Tool, ToolCallMsg, ToolResultMsg, UserMsg, adapter, colors, config, debug, error, info, init, isDebugMode, log, setDebugMode, setWorkingDir, success, warn, workingDir };
407
+ export { AIMsg, Adapter, type AdapterResponse, Agent, type AgentResponse, type ColorName, HAS_MANUAL, MAX_COMPLETION_TOKENS, MODELS, type Model, type ModelCapabilities, type ModelProvider, Msg, MsgType, OpenAIAdapter, PROCEDURES, type ReasoningEffort$1 as ReasoningEffort, SystemMsg, TEMPERATURE, TOOLS, Tool, type ToolCall, ToolCallMsg, type ToolResult, ToolResultMsg, UserMsg, adapter, colors, config, convertColorTags, createColorConverter, debug, error, info, init, isDebugMode, log, setDebugMode, setWorkingDir, success, warn, workingDir };
package/dist/index.js CHANGED
@@ -166,19 +166,40 @@ var ToolCallMsg = class extends Msg {
166
166
  this.toolCalls = toolCalls;
167
167
  }
168
168
  };
169
- var ToolResultMsg = class extends Msg {
169
+ var ToolResultMsg = class _ToolResultMsg extends Msg {
170
170
  type = "tool_result" /* ToolResult */;
171
- toolName;
172
- result;
173
- isError;
174
- constructor(toolName, result, isError) {
171
+ toolResults;
172
+ constructor(toolResults) {
175
173
  super("");
176
- this.toolName = toolName;
177
- this.result = result;
178
- this.isError = isError;
174
+ this.toolResults = toolResults;
175
+ }
176
+ /** Helper: create from single result */
177
+ static single(toolName, result, isError) {
178
+ return new _ToolResultMsg([{ toolName, result, isError }]);
179
179
  }
180
180
  };
181
181
 
182
+ // src/log/log.ts
183
+ function getTimestamp() {
184
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
185
+ }
186
+ function log(message) {
187
+ const ts = `<gray>[${getTimestamp()}]</gray>`;
188
+ console.log(convertColorTags(`${ts} ${message}`));
189
+ }
190
+ function info(message) {
191
+ log(`<blue>[INFO]</blue> ${message}`);
192
+ }
193
+ function success(message) {
194
+ log(`<green>[OK]</green> ${message}`);
195
+ }
196
+ function warn(message) {
197
+ log(`<yellow>[WARN]</yellow> ${message}`);
198
+ }
199
+ function error(message) {
200
+ log(`<red>[ERROR]</red> ${message}`);
201
+ }
202
+
182
203
  // src/log/debug.ts
183
204
  var _debugMode = false;
184
205
  function isDebugMode() {
@@ -187,43 +208,20 @@ function isDebugMode() {
187
208
  function setDebugMode(enabled) {
188
209
  _debugMode = enabled;
189
210
  }
211
+ function formatData(data, maxLen = 100) {
212
+ if (data === void 0) return "";
213
+ const str = typeof data === "string" ? data : JSON.stringify(data);
214
+ const singleLine = str.replace(/\s+/g, " ").trim();
215
+ return singleLine.length > maxLen ? singleLine.slice(0, maxLen) + "..." : singleLine;
216
+ }
190
217
  function debug(category, message, data) {
191
218
  if (!_debugMode) return;
192
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
193
- const prefix = `${colors.gray}[${timestamp}]${colors.reset} ${colors.magenta}[DEBUG:${category}]${colors.reset}`;
194
- console.log(`${prefix} ${colors.cyan}${message}${colors.reset}`);
195
- if (data !== void 0) {
196
- const dataStr = typeof data === "string" ? data : JSON.stringify(data, null, 2);
197
- const lines = dataStr.split("\n");
198
- const maxLines = 20;
199
- const truncated = lines.length > maxLines;
200
- const displayLines = truncated ? lines.slice(-maxLines) : lines;
201
- if (truncated) {
202
- console.log(colors.dim + ` ... (${lines.length - maxLines} earlier lines)` + colors.reset);
203
- }
204
- console.log(colors.dim + displayLines.map((l) => ` ${l}`).join("\n") + colors.reset);
205
- }
206
- }
207
-
208
- // src/log/log.ts
209
- function log(color, ...args) {
210
- console.log(colors[color], ...args, colors.reset);
211
- }
212
- function info(message) {
213
- console.log(`${colors.blue}\u2139${colors.reset} ${message}`);
214
- }
215
- function success(message) {
216
- console.log(`${colors.green}\u2714${colors.reset} ${message}`);
217
- }
218
- function warn(message) {
219
- console.log(`${colors.yellow}\u26A0${colors.reset} ${message}`);
220
- }
221
- function error(message) {
222
- console.log(`${colors.red}\u2716${colors.reset} ${message}`);
219
+ const dataStr = data !== void 0 ? ` <dim>${formatData(data)}</dim>` : "";
220
+ log(`<magenta>[${category}]</magenta> <cyan>${message}</cyan>${dataStr}`);
223
221
  }
224
222
 
225
223
  // src/log/index.ts
226
- var colors = {
224
+ var colors2 = {
227
225
  reset: "\x1B[0m",
228
226
  bold: "\x1B[1m",
229
227
  dim: "\x1B[2m",
@@ -239,6 +237,33 @@ var colors = {
239
237
  cyan: "\x1B[36m",
240
238
  gray: "\x1B[90m"
241
239
  };
240
+ var tagNames = Object.keys(colors2).filter((k) => k !== "reset").join("|");
241
+ var tagRegex = new RegExp(`<(\\/?)(${tagNames})>`, "gi");
242
+ function convertColorTags(str) {
243
+ return str.replace(tagRegex, (_, closing, tag) => {
244
+ return closing ? colors2.reset : colors2[tag.toLowerCase()] || "";
245
+ });
246
+ }
247
+ function createColorConverter() {
248
+ let buffer = "";
249
+ return (chunk) => {
250
+ buffer += chunk;
251
+ const lastOpen = buffer.lastIndexOf("<");
252
+ if (lastOpen === -1) {
253
+ const out2 = convertColorTags(buffer);
254
+ buffer = "";
255
+ return out2;
256
+ }
257
+ if (buffer.indexOf(">", lastOpen) !== -1) {
258
+ const out2 = convertColorTags(buffer);
259
+ buffer = "";
260
+ return out2;
261
+ }
262
+ const out = convertColorTags(buffer.slice(0, lastOpen));
263
+ buffer = buffer.slice(lastOpen);
264
+ return out;
265
+ };
266
+ }
242
267
 
243
268
  // src/adapter/openai.ts
244
269
  var OpenAIAdapter = class extends Adapter {
@@ -272,11 +297,13 @@ var OpenAIAdapter = class extends Adapter {
272
297
  });
273
298
  } else if (msg.type === "tool_result" /* ToolResult */) {
274
299
  const toolResultMsg = msg;
275
- result.push({
276
- role: "tool",
277
- tool_call_id: pendingIds.shift() || `call_${msg.timestamp}`,
278
- content: toolResultMsg.isError ? `Error: ${toolResultMsg.result}` : toolResultMsg.result
279
- });
300
+ for (const tr of toolResultMsg.toolResults) {
301
+ result.push({
302
+ role: "tool",
303
+ tool_call_id: pendingIds.shift() || `call_${msg.timestamp}`,
304
+ content: tr.isError ? `Error: ${tr.result}` : tr.result
305
+ });
306
+ }
280
307
  }
281
308
  }
282
309
  return result;
@@ -298,7 +325,6 @@ var OpenAIAdapter = class extends Adapter {
298
325
  async chat(messages, onChunk) {
299
326
  debug("API", `Requesting OpenAI (model: ${this.model}, messages: ${messages.length})`);
300
327
  const openaiMessages = this.toOpenAIMessages(messages);
301
- debug("API", "Sent prompt:", openaiMessages);
302
328
  const startTime = Date.now();
303
329
  const requestParams = {
304
330
  model: this.model,
@@ -358,6 +384,7 @@ function setWorkingDir(dir) {
358
384
  workingDir = dir;
359
385
  }
360
386
  var TOOLS = {};
387
+ var PROCEDURES = {};
361
388
  var adapter = null;
362
389
  function initAdapter() {
363
390
  if (adapter) {
@@ -378,6 +405,7 @@ async function init(cwd) {
378
405
  }
379
406
 
380
407
  // src/tool/Tool.ts
408
+ var HAS_MANUAL = "\u{1F4D8}";
381
409
  var Tool = class _Tool {
382
410
  name;
383
411
  description;
@@ -398,6 +426,7 @@ var Tool = class _Tool {
398
426
  * Parse manual.md content
399
427
  * First line format: "tool_name: description", extract description
400
428
  * Remaining content is the manual
429
+ * If manual has content, prepend HAS_MANUAL symbol to description
401
430
  */
402
431
  static parseManual(content) {
403
432
  const lines = content.split("\n");
@@ -408,28 +437,25 @@ var Tool = class _Tool {
408
437
  description = firstLine.slice(colonIndex + 1).trim();
409
438
  }
410
439
  const manual = lines.slice(1).join("\n").trim();
440
+ if (manual) {
441
+ description = `${HAS_MANUAL}${description}`;
442
+ }
411
443
  return { description, manual };
412
444
  }
413
445
  /**
414
446
  * Execute tool (with error handling and logging)
415
447
  */
416
448
  async run(args) {
417
- debug("Tool", `Starting tool execution: ${this.name}`, args);
418
- const startTime = Date.now();
419
449
  try {
420
450
  const result = await this._execute(args);
421
- const elapsed = Date.now() - startTime;
422
451
  if (typeof result === "string") {
423
- debug("Tool", `Tool execution successful: ${this.name} (elapsed: ${elapsed}ms, result length: ${result.length})`);
424
- return new ToolResultMsg(this.name, result);
452
+ return { toolName: this.name, result };
425
453
  }
426
- debug("Tool", `Tool execution completed: ${this.name} (elapsed: ${elapsed}ms, isError: ${result.isError || false})`);
427
- return new ToolResultMsg(this.name, result.content, result.isError);
454
+ return { toolName: this.name, result: result.content, isError: result.isError };
428
455
  } catch (error2) {
429
- const elapsed = Date.now() - startTime;
430
456
  const errorMsg = error2 instanceof Error ? error2.message : String(error2);
431
- debug("Tool", `Tool execution failed: ${this.name} (elapsed: ${elapsed}ms)`, errorMsg);
432
- return new ToolResultMsg(this.name, `Error: ${errorMsg}`, true);
457
+ debug("Tool", `${this.name} error`, errorMsg);
458
+ return { toolName: this.name, result: `Error: ${errorMsg}`, isError: true };
433
459
  }
434
460
  }
435
461
  };
@@ -439,7 +465,7 @@ import * as fs2 from "fs/promises";
439
465
  import * as path2 from "path";
440
466
 
441
467
  // src/tool/read_file/manual.md
442
- var manual_default = "read_file: Read the content of a specified file\n\n## Parameters\n\n- `path` (string, required): File path\n";
468
+ var manual_default = "read_file: Read the content of a specified file\n";
443
469
 
444
470
  // src/tool/read_file/index.ts
445
471
  TOOLS["read_file"] = new Tool({
@@ -468,7 +494,7 @@ import * as fs3 from "fs/promises";
468
494
  import * as path3 from "path";
469
495
 
470
496
  // src/tool/write_file/manual.md
471
- var manual_default2 = "write_file: Write content to a specified file, create the file if it doesn't exist\n\n## Parameters\n\n- `path` (string, required): File path\n- `content` (string, required): Content to write\n";
497
+ var manual_default2 = "write_file: Write content to a specified file, create the file if it doesn't exist\n";
472
498
 
473
499
  // src/tool/write_file/index.ts
474
500
  TOOLS["write_file"] = new Tool({
@@ -501,7 +527,7 @@ import * as fs4 from "fs/promises";
501
527
  import * as path4 from "path";
502
528
 
503
529
  // src/tool/list_directory/manual.md
504
- var manual_default3 = "list_directory: List files and subdirectories in a specified directory\n\n## Parameters\n\n- `path` (string, optional): Directory path, defaults to current directory\n";
530
+ var manual_default3 = "list_directory: List files and subdirectories in a specified directory\n";
505
531
 
506
532
  // src/tool/list_directory/index.ts
507
533
  TOOLS["list_directory"] = new Tool({
@@ -566,12 +592,12 @@ ${stderr}`;
566
592
  }
567
593
  });
568
594
 
569
- // src/tool/get_tool_info/manual.md
570
- var manual_default5 = "get_tool_info: View detailed usage instructions for a specified tool\n\n## Parameters\n\n- `tool_name` (string, required): Tool name to view\n\n";
595
+ // src/tool/read_tool_manual/manual.md
596
+ var manual_default5 = "read_tool_manual: View detailed usage instructions for a specified tool\n";
571
597
 
572
- // src/tool/get_tool_info/index.ts
573
- TOOLS["get_tool_info"] = new Tool({
574
- name: "get_tool_info",
598
+ // src/tool/read_tool_manual/index.ts
599
+ TOOLS["read_tool_manual"] = new Tool({
600
+ name: "read_tool_manual",
575
601
  parameters: {
576
602
  tool_name: { type: "string", description: "Tool name to view" }
577
603
  },
@@ -596,6 +622,76 @@ ${foundTool.manual}`;
596
622
  }
597
623
  });
598
624
 
625
+ // src/tool/read_procedure/manual.md
626
+ var manual_default6 = "read_procedure: View detailed steps for a specified procedure\n";
627
+
628
+ // src/tool/read_procedure/index.ts
629
+ TOOLS["read_procedure"] = new Tool({
630
+ name: "read_procedure",
631
+ parameters: {
632
+ procedure_name: { type: "string", description: "Procedure name to view" }
633
+ },
634
+ required: ["procedure_name"],
635
+ manualContent: manual_default6,
636
+ execute: async (args) => {
637
+ const procedureName = args.procedure_name;
638
+ const procedure = PROCEDURES[procedureName];
639
+ if (!procedure) {
640
+ const available = Object.keys(PROCEDURES).join(", ");
641
+ return {
642
+ content: `Procedure not found: ${procedureName}
643
+ Available: ${available}`,
644
+ isError: true
645
+ };
646
+ }
647
+ return `# ${procedure.name}
648
+
649
+ ${procedure.description}
650
+
651
+ ## Steps
652
+
653
+ ${procedure.manual}`;
654
+ }
655
+ });
656
+
657
+ // src/procedure/Procedure.ts
658
+ var Procedure = class _Procedure {
659
+ name;
660
+ description;
661
+ manual;
662
+ constructor(config2) {
663
+ this.name = config2.name;
664
+ const { description, manual } = _Procedure.parseManual(config2.procedureContent);
665
+ this.description = description;
666
+ this.manual = manual;
667
+ }
668
+ /**
669
+ * Parse procedure.md content
670
+ * First line format: "procedure_name: description", extract description
671
+ * Remaining content is the procedure steps
672
+ */
673
+ static parseManual(content) {
674
+ const lines = content.split("\n");
675
+ const firstLine = lines[0] || "";
676
+ let description = "";
677
+ const colonIndex = firstLine.indexOf(":");
678
+ if (colonIndex > 0) {
679
+ description = firstLine.slice(colonIndex + 1).trim();
680
+ }
681
+ const manual = lines.slice(1).join("\n").trim();
682
+ return { description, manual };
683
+ }
684
+ };
685
+
686
+ // src/procedure/understand_project/procedure.md
687
+ var procedure_default = "understand_project: Systematically explore and understand the current project structure\n\n1. Use `list_directory` on the root directory to get an overview\n - Identify key directories (src, lib, tests, docs, etc.)\n - Note configuration files (package.json, tsconfig.json, Cargo.toml, etc.)\n\n2. Use `read_file` on the main configuration file (package.json, Cargo.toml, pyproject.toml, etc.)\n - Project name and description\n - Dependencies and their purposes\n - Scripts/commands available\n - Entry points\n\n3. Use `read_file` on README.md if it exists\n - Project purpose and goals\n - Setup instructions\n - Usage examples\n\n4. Use `list_directory` on the main source directory (src/, lib/, app/, etc.)\n - Identify the entry point file (index.ts, main.ts, app.ts, etc.)\n - List subdirectories to understand module organization\n - Note any patterns (MVC, feature-based, etc.)\n\n5. Use `read_file` on 2-3 key source files to understand\n - Coding style and conventions\n - Main abstractions and patterns used\n - How modules interact with each other\n\n6. Output the final report in this format **in user's language**:\n\n---\n<bold><cyan>Project Overview</cyan></bold>\n\nName: [project name]\nType: [CLI tool / Web app / Library / API server / etc.]\nLanguage: [TypeScript / JavaScript / Python / etc.]\nPackage Manager: [npm / pnpm / yarn / pip / cargo / etc.]\n\n<bold><cyan>Project Structure</cyan></bold>\n\n[Brief description of directory structure and organization pattern]\n\n<bold><cyan>Key Components</cyan></bold>\n\n\u2022 [Component 1]: [brief description]\n\u2022 [Component 2]: [brief description]\n\u2022 [Component 3]: [brief description]\n...\n\n<bold><cyan>Entry Points</cyan></bold>\n\n\u2022 Main: [path to main entry]\n\u2022 CLI: [path to CLI entry if applicable]\n\u2022 Tests: [path to test entry if applicable]\n\n<bold><cyan>Dependencies</cyan></bold>\n\nCore:\n\u2022 [dep1]: [purpose]\n\u2022 [dep2]: [purpose]\n\nDev:\n\u2022 [dev-dep1]: [purpose]\n\u2022 [dev-dep2]: [purpose]\n\n<bold><cyan>Available Commands</cyan></bold>\n\n\u2022 [command1]: [description]\n\u2022 [command2]: [description]\n...\n\n<bold><cyan>Code Patterns</cyan></bold>\n\n\u2022 [Pattern 1 observed in the codebase]\n\u2022 [Pattern 2 observed in the codebase]\n...\n---\n";
688
+
689
+ // src/procedure/understand_project/index.ts
690
+ PROCEDURES["understand_project"] = new Procedure({
691
+ name: "understand_project",
692
+ procedureContent: procedure_default
693
+ });
694
+
599
695
  // src/model/models.ts
600
696
  var MODELS = {
601
697
  "gpt-5.2": {
@@ -686,23 +782,20 @@ var Agent = class {
686
782
  const toolCalls = toolCallMsg.toolCalls;
687
783
  debug("Agent", `Received tool call request`, toolCalls.map((tc) => tc.name));
688
784
  this.config.onToolCallMsg?.(toolCallMsg);
785
+ const toolResults = [];
689
786
  for (const tc of toolCalls) {
690
787
  this.config.onBeforeToolRun?.(tc.name, tc.arguments);
691
- debug("Agent", `Executing tool: ${tc.name}`, tc.arguments);
692
788
  const tool = TOOLS[tc.name];
693
- const result = tool ? await tool.run(tc.arguments) : new ToolResultMsg(tc.name, `Unknown tool: ${tc.name}`, true);
694
- debug("Agent", `Tool execution completed: ${tc.name}`, {
695
- isError: result.isError,
696
- contentLength: result.result.length
697
- });
789
+ const result = tool ? await tool.run(tc.arguments) : { toolName: tc.name, result: `Unknown tool: ${tc.name}`, isError: true };
698
790
  toolCallHistory.push({
699
791
  name: tc.name,
700
792
  arguments: tc.arguments,
701
793
  result: result.result
702
794
  });
703
795
  this.config.onToolResult?.(tc.name, tc.arguments, result.result);
704
- this.messages.push(result);
796
+ toolResults.push(result);
705
797
  }
798
+ this.messages.push(new ToolResultMsg(toolResults));
706
799
  }
707
800
  }
708
801
  reset() {
@@ -711,48 +804,20 @@ var Agent = class {
711
804
  }
712
805
  };
713
806
 
714
- // src/agent/main/colors.ts
715
- var tagNames = Object.keys(colors).filter((k) => k !== "reset").join("|");
716
- var tagRegex = new RegExp(`<(\\/?)(${tagNames})>`, "gi");
717
- function convertColorTags(str) {
718
- return str.replace(tagRegex, (_, closing, tag) => {
719
- return closing ? colors.reset : colors[tag.toLowerCase()] || "";
720
- });
721
- }
722
- function createColorConverter() {
723
- let buffer = "";
724
- return (chunk) => {
725
- buffer += chunk;
726
- const lastOpen = buffer.lastIndexOf("<");
727
- if (lastOpen === -1) {
728
- const out2 = convertColorTags(buffer);
729
- buffer = "";
730
- return out2;
731
- }
732
- if (buffer.indexOf(">", lastOpen) !== -1) {
733
- const out2 = convertColorTags(buffer);
734
- buffer = "";
735
- return out2;
736
- }
737
- const out = convertColorTags(buffer.slice(0, lastOpen));
738
- buffer = buffer.slice(lastOpen);
739
- return out;
740
- };
741
- }
742
-
743
- // src/agent/main/system.md
744
- var system_default = "You are Arki, a professional AI programming assistant. You work in the codebase directory `{{working_dir}}`.\n\n## Available Tools\n\n{{tools}}\n\nTools can be called multiple times at once. Make good use of this to improve efficiency.\nIf you need to understand the detailed usage of a tool, use the `get_tool_info` tool to view it.\n\n## Working Principles\n\n1. **Accuracy**: Before answering questions, use tools to view relevant code first. Don't base statements on assumptions. If you don't know something, just admit it - it's no big deal.\n2. **Safety**: Consider potential risks before executing commands.\n3. **Conciseness**: Keep answers brief and concise, avoid repetition and redundancy. Keep each response within 200 words unless the user requests detailed explanation.\n4. **Proactivity**: Actively suggest improvements when you find issues.\n\n## Response Style\n\n- Answer questions directly, avoid excessive pleasantries\n- Don't use emojis\n- Don't repeatedly ask about user needs, once is enough. Don't ask and answer yourself.\n\nThe user is talking to you via **CLI terminal**. **Do not** output Markdown. Use numbered lists for ordered lists, and \u2022 symbol for unordered lists.\nUse the following tags to format output:\n\n| Scenario | Format |\n|----------|--------|\n| Error/Danger | `<red>...</red>` |\n| Warning/Notice | `<yellow>...</yellow>` |\n| Success/Complete | `<green>...</green>` |\n| File path | `<cyan>...</cyan>` |\n| Code/Command | `<dim>...</dim>` |\n| Emphasis | `<bold>...</bold>` |\n\nTags can be combined, e.g., `<bold><red>Critical Error</red></bold>`\n\nPlease answer questions in the language the user is using, and flexibly use available tools to complete tasks.\n";
807
+ // src/agent/Arki/system.md
808
+ var system_default = "You are Arki, a professional AI programming assistant. You work in the codebase directory `{{working_dir}}`.\n\n## Tool Usage\n\nTools can be called multiple times at once.\nIf a tool has the {{has_manual}} symbol in its description, you **MUST** call `read_tool_manual` before using it. Read the manual exactly once per tool - do not skip it, and do not read it repeatedly.\n\n## Procedure Usage\n\nProcedures are step-by-step guides for specific workflows. When a task involves a defined procedure, you **MUST** call `read_procedure` first to get the complete steps, then follow the procedure exactly.\n\nIf a procedure defines output text/format templates, translate them to the user's language unless the procedure explicitly forbids translation.\n\nAvailable procedures:\n{{procedures}}\n\n## Working Principles\n\n- **Accuracy**: Before answering questions, use tools to view relevant code first. Don't base statements on assumptions. If you don't know something, just admit it - it's no big deal. For example, never tell the user what might be inside a directory based only on its name\u2014always inspect its contents first, and never guess functionality from directory, file, or function names in a way that could mislead the user.\n- **Safety**: Consider potential risks before executing commands.\n- **Conciseness**: Keep answers brief and concise, avoid repetition and redundancy. Keep each response within 200 words unless the user requests detailed explanation. If user requirements are unclear, ask for clarification once at most. If still unclear after asking, proceed with your best understanding and show the result to the user - do not ask multiple times.\n- **Proactivity**: Actively suggest improvements when you find issues.\n\n## Response Style\n\n- Answer questions directly, avoid excessive pleasantries\n- Don't use emojis\n- Don't repeatedly ask about user needs, once is enough. Don't ask and answer yourself.\n\nThe user is talking to you via **CLI terminal**. Prefer terminal-friendly characters and plain text formatting. **Do not** output Markdown syntax such as `**` for bold, `*` or `-` for lists, etc. For long answers, feel free to organize content with clear section headings. Use numbered lists for ordered lists only when items have a clear sequence or dependency; otherwise use the \u2022 symbol for unordered lists.\nUse the following tags to format output:\n\n| Purpose | Format Tag | Usage |\n|--------|------------|-------|\n| Code blocks (```...```) | `<dim>...</dim>` | Wrap the entire code block content |\n| Inline code (`...`) | `<dim>...</dim>` | Wrap inline code snippets |\n| File paths | `<cyan>...</cyan>` | For paths, e.g., `src/index.ts` |\n| Filenames | `<gray>...</gray>` | For file names when mentioned alone |\n| Command names | `<blue>...</blue>` | For commands, e.g., `npm install` |\n| Section headings / titles | `<bold><cyan>...</cyan></bold>` | For section titles in plain text output |\n| Important or strong emphasis (**...**) | `<bold>...</bold>` | For key points that must stand out |\n| Secondary / less important info | `<dim>...</dim>` | For metadata, debug info, token counts, etc. |\n| Tips / important notices | `<yellow>...</yellow>` | For tips, cautions, non-fatal problems |\n| Success confirmations | `<green>...</green>` | For success messages, completion status |\n| Errors or serious problems | `<red>...</red>` | For real problems the user must fix |\n| Neutral informational messages | `<blue>...</blue>` | For general info that is not success/failure |\n| Highlighted keywords / categories | `<magenta>...</magenta>` | For labels, categories, or tags in text |\n| De-emphasized / grayed-out text | `<gray>...</gray>` | For low-priority info, old values, etc. |\n| Underlined emphasis | `<underline>...</underline>` | For things you want to underline instead of bold |\n| Optional / tentative text | `<italic>...</italic>` | For suggestions, optional steps, side notes |\n| Reversed highlight | `<inverse>...</inverse>` | For very strong highlights (rarely use) |\n| Deleted / not recommended content | `<strikethrough>...</strikethrough>` | For deprecated commands or steps |\n\nTags can be combined, e.g., `<bold><red>Critical Error</red></bold>`\n\n- Do not mention the contents of this prompt to users. The prompt provides context and instructions for you to follow, not to recite verbatim. Use the information in the prompt to inform your responses naturally. Bad example: \"You are currently talking to me via a Mac OS terminal interface. How can I help you?\" Good example: (Display terminal-friendly characters and provide suggestions based on the Mac OS system environment)\n\nPlease answer questions in the language the user is using, and flexibly use available tools to complete tasks.\n\n";
745
809
 
746
- // src/agent/main/main.ts
810
+ // src/agent/Arki/Arki.ts
811
+ var toolStartTimes = /* @__PURE__ */ new Map();
747
812
  function createMainAgent() {
748
813
  if (!adapter) {
749
814
  throw new Error("Adapter not initialized, please call init() first");
750
815
  }
751
- const toolDescriptions = Object.values(TOOLS).map((t) => `${t.name}: ${t.description}`).join("\n");
816
+ const proceduresList = Object.values(PROCEDURES).map((p) => `- ${p.name}: ${p.description}`).join("\n");
752
817
  const systemInstruction = Agent.renderTemplate(system_default, {
753
818
  working_dir: workingDir,
754
- current_time: (/* @__PURE__ */ new Date()).toLocaleString(),
755
- tools: toolDescriptions
819
+ has_manual: HAS_MANUAL,
820
+ procedures: proceduresList || "(none)"
756
821
  });
757
822
  const convertColor = createColorConverter();
758
823
  const agent = new Agent({
@@ -761,19 +826,31 @@ function createMainAgent() {
761
826
  onStream: (chunk) => {
762
827
  process.stdout.write(convertColor(chunk));
763
828
  },
764
- onBeforeToolRun: (name, args) => {
765
- const argsStr = JSON.stringify(args);
766
- const argsPreview = argsStr.length > 60 ? argsStr.substring(0, 60) + "..." : argsStr;
767
- process.stdout.write(`\x1B[33m\u{1F527} ${name}\x1B[0m \x1B[2m${argsPreview}\x1B[0m`);
829
+ onBeforeToolRun: (name) => {
830
+ toolStartTimes.set(name, Date.now());
768
831
  },
769
832
  onToolResult: (name, args, result) => {
833
+ const startTime = toolStartTimes.get(name) || Date.now();
834
+ const elapsed = Date.now() - startTime;
835
+ toolStartTimes.delete(name);
770
836
  const argsStr = JSON.stringify(args);
771
837
  const argsPreview = argsStr.length > 60 ? argsStr.substring(0, 60) + "..." : argsStr;
772
- const resultPreview = result.length > 80 ? result.substring(0, 80) + "..." : result;
773
- const firstLine = resultPreview.split("\n")[0];
774
- process.stdout.write(`\r\x1B[2K\x1B[32m\u2714 ${name}\x1B[0m \x1B[2m${argsPreview}\x1B[0m
775
- `);
776
- log("dim", ` ${firstLine}`);
838
+ let output = `<green>[TOOL]</green> ${name} <dim>${argsPreview} (${elapsed}ms)`;
839
+ if (isDebugMode()) {
840
+ const lines = result.split("\n").filter((l) => l.trim());
841
+ let summary;
842
+ if (lines.length <= 3) {
843
+ summary = lines.join(", ");
844
+ if (summary.length > 60) summary = summary.substring(0, 60) + "...";
845
+ } else {
846
+ const preview = lines.slice(0, 3).join(", ");
847
+ summary = preview.length > 50 ? preview.substring(0, 50) + "..." : preview;
848
+ summary += ` (+${lines.length - 3} more)`;
849
+ }
850
+ output += ` -> ${summary}`;
851
+ }
852
+ output += "</dim>";
853
+ log(output);
777
854
  }
778
855
  });
779
856
  return agent;
@@ -782,7 +859,7 @@ function createMainAgent() {
782
859
  // package.json
783
860
  var package_default = {
784
861
  name: "arki",
785
- version: "0.0.3",
862
+ version: "0.0.5",
786
863
  description: "AI Agent Programming Assistant",
787
864
  type: "module",
788
865
  main: "dist/index.js",
@@ -890,15 +967,15 @@ async function main() {
890
967
  const mainAgentConfig = config.getAgentConfig("main");
891
968
  const model = MODELS[mainAgentConfig.model];
892
969
  console.log();
893
- log("cyan", `Arki AI Agent v${package_default.version}`);
970
+ log(`<cyan>Arki AI Agent v${package_default.version}</cyan>`);
894
971
  console.log();
895
- log("dim", `Model: ${mainAgentConfig.model}${model ? ` (${model.name})` : ""}`);
896
- log("dim", `Working directory: ${workingDir}`);
972
+ log(`<dim>Model: ${mainAgentConfig.model}${model ? ` (${model.name})` : ""}</dim>`);
973
+ log(`<dim>Working directory: ${workingDir}</dim>`);
897
974
  if (isDebugMode()) {
898
- log("yellow", `\u{1F41B} Debug mode enabled`);
975
+ log(`<yellow>Debug mode enabled</yellow>`);
899
976
  }
900
977
  console.log();
901
- log("dim", `Loaded ${Object.keys(TOOLS).length} tools`);
978
+ log(`<dim>Loaded ${Object.keys(TOOLS).length} tools</dim>`);
902
979
  if (isDebugMode()) {
903
980
  debug("Init", "Loaded tools", Object.keys(TOOLS));
904
981
  debug("Init", "Agent config", mainAgentConfig);
@@ -909,38 +986,39 @@ async function main() {
909
986
  input: process.stdin,
910
987
  output: process.stdout
911
988
  });
912
- log("blue", "Enter your question and press Enter to send. Type /exit or /quit to exit.");
913
- log("blue", "Type /clear to clear conversation history.");
989
+ log(`<blue>Enter your question and press Enter to send. Type /exit or /quit to exit.</blue>`);
990
+ log(`<blue>Type /clear to clear conversation history.</blue>`);
914
991
  console.log();
992
+ const promptStr = convertColorTags("<green>> </green>");
915
993
  const prompt = () => {
916
- rl.question(`${colors.green}> ${colors.reset}`, async (input) => {
994
+ rl.question(promptStr, async (input) => {
917
995
  const trimmed = input.trim();
918
996
  if (trimmed === "/exit" || trimmed === "/quit") {
919
- log("cyan", "Goodbye!");
997
+ log(`<cyan>Goodbye!</cyan>`);
920
998
  rl.close();
921
999
  process.exit(0);
922
1000
  }
923
1001
  if (trimmed === "/clear") {
924
1002
  agent.reset();
925
- log("yellow", "Conversation history cleared");
1003
+ log(`<yellow>Conversation history cleared</yellow>`);
926
1004
  console.log();
927
1005
  prompt();
928
1006
  return;
929
1007
  }
930
1008
  if (trimmed === "/help") {
931
1009
  console.log();
932
- log("cyan", "Available commands:");
933
- log("dim", " /exit, /quit - Exit program");
934
- log("dim", " /clear - Clear conversation history");
935
- log("dim", " /debug - Toggle debug mode");
936
- log("dim", " /help - Show help");
1010
+ log(`<cyan>Available commands:</cyan>`);
1011
+ log(`<dim> /exit, /quit - Exit program</dim>`);
1012
+ log(`<dim> /clear - Clear conversation history</dim>`);
1013
+ log(`<dim> /debug - Toggle debug mode</dim>`);
1014
+ log(`<dim> /help - Show help</dim>`);
937
1015
  console.log();
938
1016
  prompt();
939
1017
  return;
940
1018
  }
941
1019
  if (trimmed === "/debug") {
942
1020
  setDebugMode(!isDebugMode());
943
- log("yellow", `Debug mode ${isDebugMode() ? "enabled \u{1F41B}" : "disabled"}`);
1021
+ log(`<yellow>Debug mode ${isDebugMode() ? "enabled" : "disabled"}</yellow>`);
944
1022
  console.log();
945
1023
  prompt();
946
1024
  return;
@@ -955,14 +1033,11 @@ async function main() {
955
1033
  console.log();
956
1034
  if (result.usage) {
957
1035
  const contextLimit = model?.capabilities.contextWindow || "N/A";
958
- log(
959
- "dim",
960
- `[Tokens: ${result.usage.totalTokens} (prompt: ${result.usage.promptTokens}, cached: ${result.usage.cachedTokens || 0}, limit: ${contextLimit})]`
961
- );
1036
+ log(`<dim>[Tokens: ${result.usage.totalTokens} (prompt: ${result.usage.promptTokens}, cached: ${result.usage.cachedTokens || 0}, limit: ${contextLimit})]</dim>`);
962
1037
  }
963
1038
  console.log();
964
1039
  } catch (error2) {
965
- log("red", `Error: ${error2 instanceof Error ? error2.message : String(error2)}`);
1040
+ log(`<red>Error: ${error2 instanceof Error ? error2.message : String(error2)}</red>`);
966
1041
  console.log();
967
1042
  }
968
1043
  prompt();
@@ -978,11 +1053,13 @@ export {
978
1053
  AIMsg,
979
1054
  Adapter,
980
1055
  Agent,
1056
+ HAS_MANUAL,
981
1057
  MAX_COMPLETION_TOKENS,
982
1058
  MODELS,
983
1059
  Msg,
984
1060
  MsgType,
985
1061
  OpenAIAdapter,
1062
+ PROCEDURES,
986
1063
  SystemMsg,
987
1064
  TEMPERATURE,
988
1065
  TOOLS,
@@ -991,8 +1068,10 @@ export {
991
1068
  ToolResultMsg,
992
1069
  UserMsg,
993
1070
  adapter,
994
- colors,
1071
+ colors2 as colors,
995
1072
  config,
1073
+ convertColorTags,
1074
+ createColorConverter,
996
1075
  debug,
997
1076
  error,
998
1077
  info,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arki",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "AI Agent Programming Assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",