terry-core 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/dist/orchestrator.js +119 -21
  2. package/package.json +1 -1
@@ -1,12 +1,108 @@
1
+ const KNOWN_COMMANDS = ["git", "npm", "pnpm", "yarn", "node", "npx"];
1
2
  const TOOL_HINTS = [
2
- { tool: "writeFile", match: /\b(write|create|update|edit|save)\b.*\b(file|doc|readme|json|ts|js)\b/i },
3
- { tool: "readFile", match: /\b(read|open|show)\b.*\b(file|md|json|ts|js|readme)\b/i },
3
+ { tool: "runCommand", match: /\b(run|execute)\b|\b(git|npm|pnpm|yarn|node|npx)\b/i },
4
+ { tool: "writeFile", match: /\b(write|create|update|edit|save)\b.*\b(file|readme|json|md|ts|js)\b/i },
5
+ { tool: "readFile", match: /\b(read|open|show)\b.*\b(file|readme|json|md|ts|js)\b/i },
4
6
  { tool: "searchInFiles", match: /\b(search|find|grep|look for)\b/i },
5
7
  { tool: "gitStatus", match: /\bgit status|repo status|what changed\b/i },
6
8
  { tool: "gitDiff", match: /\bgit diff|show diff|changes\b/i },
7
- { tool: "runCommand", match: /\b(run|execute|command|npm|pnpm|yarn|test|build)\b/i },
8
9
  { tool: "listDirectory", match: /\b(list|ls|folders?|directories?)\b/i }
9
10
  ];
11
+ function stripWrappers(value) {
12
+ return value.trim().replace(/^["'`]|["'`]$/g, "");
13
+ }
14
+ function tokenizeCommand(raw) {
15
+ const tokens = [];
16
+ let current = "";
17
+ let quote = "";
18
+ for (let i = 0; i < raw.length; i += 1) {
19
+ const ch = raw[i];
20
+ if (!ch)
21
+ continue;
22
+ if (quote) {
23
+ if (ch === quote) {
24
+ quote = "";
25
+ }
26
+ else {
27
+ current += ch;
28
+ }
29
+ continue;
30
+ }
31
+ if (ch === '"' || ch === "'") {
32
+ quote = ch;
33
+ continue;
34
+ }
35
+ if (/\s/.test(ch)) {
36
+ if (current) {
37
+ tokens.push(current);
38
+ current = "";
39
+ }
40
+ continue;
41
+ }
42
+ current += ch;
43
+ }
44
+ if (current)
45
+ tokens.push(current);
46
+ return tokens;
47
+ }
48
+ function extractFirstPath(content) {
49
+ const quoted = content.match(/["'`]([^"'`]+)["'`]/);
50
+ if (quoted?.[1])
51
+ return quoted[1].trim();
52
+ const direct = content.match(/\b([./\\]?[a-zA-Z0-9_\-/\\.]+\.[a-zA-Z0-9_]+)\b/);
53
+ if (direct?.[1])
54
+ return direct[1].trim();
55
+ return null;
56
+ }
57
+ function parseRunCommand(content) {
58
+ const backtick = content.match(/`([^`]+)`/);
59
+ const candidate = backtick?.[1] ??
60
+ content.match(/\b(?:run|execute)(?:\s+the)?(?:\s+command)?\s+(.+)$/i)?.[1] ??
61
+ content.match(new RegExp(`^\\s*(${KNOWN_COMMANDS.join("|")})\\b(.+)?$`, "i"))?.[0] ??
62
+ "";
63
+ const normalized = candidate.trim().replace(/[.;,\s]+$/g, "");
64
+ if (!normalized)
65
+ return null;
66
+ const tokens = tokenizeCommand(normalized);
67
+ if (tokens.length === 0)
68
+ return null;
69
+ const [cmd, ...args] = tokens;
70
+ if (!cmd || !KNOWN_COMMANDS.includes(cmd.toLowerCase())) {
71
+ return null;
72
+ }
73
+ return { cmd, args };
74
+ }
75
+ function parseReadFileArgs(content) {
76
+ const pathMatch = content.match(/\b(?:read|open|show)(?:\s+the)?(?:\s+file)?\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
77
+ extractFirstPath(content);
78
+ return { pathRelativeToWorkspace: pathMatch ?? "README.md" };
79
+ }
80
+ function parseWriteFileArgs(content) {
81
+ const pathMatch = content.match(/\b(?:write|create|update|edit|save)(?:\s+(?:to|into))?(?:\s+file)?\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
82
+ content.match(/\b(?:write|create|update|edit|save)(?:\s+(?:to|into))?(?:\s+file)?\s+([^\s]+)\b/i)?.[1] ??
83
+ extractFirstPath(content) ??
84
+ "README.md";
85
+ const contentMatch = content.match(/\b(?:with content|with text|saying|that says)\s+([\s\S]+)$/i)?.[1] ??
86
+ content.match(/:\s*([\s\S]+)$/)?.[1] ??
87
+ `# Terry update\n\nGenerated from task:\n${content}\n`;
88
+ return {
89
+ pathRelativeToWorkspace: stripWrappers(pathMatch),
90
+ content: contentMatch.trim()
91
+ };
92
+ }
93
+ function parseSearchArgs(content) {
94
+ const quoted = content.match(/["'`]([^"'`]+)["'`]/)?.[1];
95
+ const phrase = quoted ??
96
+ content.match(/\b(?:search|find|grep|look for)\b\s+([\s\S]+)$/i)?.[1] ??
97
+ content;
98
+ return { query: phrase.replace(/^for\s+/i, "").trim() };
99
+ }
100
+ function parseListDirectoryArgs(content) {
101
+ const target = content.match(/\b(?:in|inside|under)\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
102
+ content.match(/\b(?:in|inside|under)\s+([^\s]+)\b/i)?.[1] ??
103
+ ".";
104
+ return { pathRelativeToWorkspace: stripWrappers(target) || "." };
105
+ }
10
106
  function inferTool(content) {
11
107
  for (const hint of TOOL_HINTS) {
12
108
  if (hint.match.test(content))
@@ -15,27 +111,22 @@ function inferTool(content) {
15
111
  return null;
16
112
  }
17
113
  function inferArgs(tool, content) {
18
- if (tool === "searchInFiles") {
19
- const phrase = content.split("search").pop()?.trim() || content;
20
- return { query: phrase.replace(/^for\s+/i, "") };
21
- }
22
- if (tool === "runCommand") {
23
- return { cmd: "echo", args: [content] };
24
- }
114
+ if (tool === "searchInFiles")
115
+ return parseSearchArgs(content);
116
+ if (tool === "runCommand")
117
+ return parseRunCommand(content);
25
118
  if (tool === "listDirectory")
26
- return { pathRelativeToWorkspace: "." };
119
+ return parseListDirectoryArgs(content);
27
120
  if (tool === "gitStatus")
28
121
  return {};
29
- if (tool === "gitDiff")
30
- return {};
31
- if (tool === "readFile")
32
- return { pathRelativeToWorkspace: "README.md" };
33
- if (tool === "writeFile") {
34
- return {
35
- pathRelativeToWorkspace: "README.md",
36
- content: `# Terry update\n\nGenerated from task:\n${content}\n`
37
- };
122
+ if (tool === "gitDiff") {
123
+ const target = extractFirstPath(content);
124
+ return target ? { path: target } : {};
38
125
  }
126
+ if (tool === "readFile")
127
+ return parseReadFileArgs(content);
128
+ if (tool === "writeFile")
129
+ return parseWriteFileArgs(content);
39
130
  return {};
40
131
  }
41
132
  export const rulesOrchestrator = (messages) => {
@@ -49,10 +140,17 @@ export const rulesOrchestrator = (messages) => {
49
140
  summary: "I can help with workspace files, search, git, and command planning. Ask a concrete repo or file operation."
50
141
  };
51
142
  }
143
+ const args = inferArgs(tool, lastUser.content);
144
+ if (tool === "runCommand" && !args) {
145
+ return {
146
+ kind: "summarize",
147
+ summary: "I can run approved workspace commands. Please provide an explicit command, for example: run `npm test`."
148
+ };
149
+ }
52
150
  return {
53
151
  kind: "ask_permission",
54
152
  tool,
55
- args: inferArgs(tool, lastUser.content),
153
+ args,
56
154
  reason: `Detected request likely needs tool: ${tool}.`
57
155
  };
58
156
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terry-core",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Core policy, tooling, and orchestration modules for Terry Agent.",
5
5
  "license": "MIT",
6
6
  "author": "ELEVAREL",