@jc20231028/local-code-agent 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,32 +1,34 @@
1
- {
2
- "name": "@jc20231028/local-code-agent",
3
- "version": "0.1.0",
4
- "description": "A local coding CLI that uses Ollama or LM Studio models to inspect and modify project files.",
5
- "type": "module",
6
- "bin": {
7
- "local-code": "bin/local-code.js"
8
- },
9
- "files": [
10
- "bin",
11
- "src",
12
- "README.md",
13
- "LICENSE"
14
- ],
15
- "scripts": {
16
- "start": "node ./bin/local-code.js",
17
- "test": "node --test",
18
- "prepublishOnly": "node --test"
19
- },
20
- "engines": {
21
- "node": ">=20.0.0"
22
- },
23
- "keywords": [
24
- "cli",
25
- "ollama",
26
- "lm-studio",
27
- "codegen",
28
- "agent"
29
- ],
30
- "author": "jeff",
31
- "license": "MIT"
32
- }
1
+ {
2
+ "name": "@jc20231028/local-code-agent",
3
+ "version": "0.1.1",
4
+ "description": "A local coding CLI that uses Ollama or LM Studio models to inspect and modify project files.",
5
+ "type": "module",
6
+ "bin": {
7
+ "local-code": "bin/local-code.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "scripts",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "start": "node ./bin/local-code.js",
18
+ "test": "node --test",
19
+ "prepublishOnly": "node --test",
20
+ "postinstall": "node ./scripts/postinstall.js"
21
+ },
22
+ "engines": {
23
+ "node": ">=20.0.0"
24
+ },
25
+ "keywords": [
26
+ "cli",
27
+ "ollama",
28
+ "lm-studio",
29
+ "codegen",
30
+ "agent"
31
+ ],
32
+ "author": "jeff",
33
+ "license": "MIT"
34
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ const isGlobal = process.env.npm_config_global === "true";
3
+
4
+ if (isGlobal) {
5
+ console.log("");
6
+ console.log("local-code-agent installed globally. Run `local-code chat` from any folder to start.");
7
+ console.log("");
8
+ } else {
9
+ console.log("");
10
+ console.log("local-code-agent installed locally (not global), so `local-code` is not on your PATH yet.");
11
+ console.log("Run it with: npx local-code chat");
12
+ console.log("Or install globally instead: npm install -g @jc20231028/local-code-agent");
13
+ console.log("");
14
+ }
package/src/checkpoint.js CHANGED
@@ -1,125 +1,125 @@
1
- import { loadAppState, saveAppState } from "./config.js";
2
-
3
- const DEFAULT_PROMPT_LIMIT = 10;
4
- const DEFAULT_PROMPT_MAX_CHARS = 400;
5
-
6
- const EMPTY_REPLY_NUDGE =
7
- "你的回覆是空的。就算使用者的輸入有錯字、簡短或不完整,也請依照最合理的猜測直接執行或回答,不要保持沉默;如果需要用到工具,回傳一個 <tool_call> 區塊,否則至少用一句話回答。";
8
-
9
- export async function loadCheckpoints(config) {
10
- const state = await loadAppState(config.statePath);
11
- return Array.isArray(state.checkpoints) ? state.checkpoints : [];
12
- }
13
-
14
- export async function saveCheckpoints(config, checkpoints) {
15
- await saveAppState(config.statePath, { checkpoints });
16
- }
17
-
18
- export function findActiveCheckpoint(checkpoints) {
19
- for (let index = checkpoints.length - 1; index >= 0; index -= 1) {
20
- if (!checkpoints[index].completed) {
21
- return checkpoints[index];
22
- }
23
- }
24
-
25
- return null;
26
- }
27
-
28
- export function extractRecentPrompts(history, limit = DEFAULT_PROMPT_LIMIT, maxChars = DEFAULT_PROMPT_MAX_CHARS) {
29
- if (!Array.isArray(history)) {
30
- return [];
31
- }
32
-
33
- const prompts = history
34
- .filter(isRealUserPrompt)
35
- .map((message) => message.content.trim().slice(0, maxChars));
36
-
37
- return prompts.slice(-limit);
38
- }
39
-
40
- function isRealUserPrompt(message) {
41
- if (!message || message.role !== "user" || typeof message.content !== "string") {
42
- return false;
43
- }
44
-
45
- const trimmed = message.content.trim();
46
- if (!trimmed) {
47
- return false;
48
- }
49
-
50
- if (trimmed.startsWith("<tool_result ") || trimmed.startsWith("<tool_call_error>")) {
51
- return false;
52
- }
53
-
54
- if (trimmed === EMPTY_REPLY_NUDGE) {
55
- return false;
56
- }
57
-
58
- return true;
59
- }
60
-
61
- export function createCheckpoint({
62
- goal = "",
63
- status = "",
64
- completedSteps = [],
65
- pendingSteps = [],
66
- contextNotes = [],
67
- blockers = [],
68
- keyFiles = [],
69
- recentPrompts = []
70
- } = {}) {
71
- const now = new Date().toISOString();
72
- return {
73
- id: now.replace(/[:.]/g, "-"),
74
- createdAt: now,
75
- updatedAt: now,
76
- goal,
77
- status,
78
- completedSteps,
79
- pendingSteps,
80
- contextNotes,
81
- blockers,
82
- keyFiles,
83
- recentPrompts,
84
- completed: false,
85
- completedAt: null
86
- };
87
- }
88
-
89
- export function formatCheckpointSummaryLine(checkpoint) {
90
- const tag = checkpoint.completed ? "done " : "active";
91
- return `[${tag}] ${checkpoint.id} ${checkpoint.goal || "(no goal)"} (${checkpoint.createdAt.slice(0, 16)})`;
92
- }
93
-
94
- export function formatCheckpoint(checkpoint) {
95
- const lines = [
96
- `Checkpoint ${checkpoint.id}${checkpoint.completed ? " (completed)" : ""}`,
97
- `Saved: ${checkpoint.createdAt}`
98
- ];
99
-
100
- if (checkpoint.updatedAt && checkpoint.updatedAt !== checkpoint.createdAt) {
101
- lines.push(`Updated: ${checkpoint.updatedAt}`);
102
- }
103
-
104
- lines.push("", `Goal: ${checkpoint.goal || "(none)"}`, `Status: ${checkpoint.status || "(none)"}`);
105
-
106
- pushListSection(lines, "Completed", checkpoint.completedSteps, (step) => ` [x] ${step}`);
107
- pushListSection(lines, "Pending (continue here)", checkpoint.pendingSteps, (step) => ` [ ] ${step}`);
108
- pushListSection(lines, "Context / decisions", checkpoint.contextNotes, (note) => ` - ${note}`);
109
- pushListSection(lines, "Blockers", checkpoint.blockers, (blocker) => ` ! ${blocker}`);
110
- pushListSection(lines, "Key files", checkpoint.keyFiles, (file) => ` - ${file}`);
111
- pushListSection(lines, "Recent prompts (auto-captured)", checkpoint.recentPrompts, (prompt) => ` - ${prompt}`);
112
-
113
- return lines.join("\n");
114
- }
115
-
116
- function pushListSection(lines, title, items, formatItem) {
117
- if (!items || items.length === 0) {
118
- return;
119
- }
120
-
121
- lines.push("", `${title}:`);
122
- for (const item of items) {
123
- lines.push(formatItem(item));
124
- }
125
- }
1
+ import { loadAppState, saveAppState } from "./config.js";
2
+
3
+ const DEFAULT_PROMPT_LIMIT = 10;
4
+ const DEFAULT_PROMPT_MAX_CHARS = 400;
5
+
6
+ const EMPTY_REPLY_NUDGE =
7
+ "你的回覆是空的。就算使用者的輸入有錯字、簡短或不完整,也請依照最合理的猜測直接執行或回答,不要保持沉默;如果需要用到工具,回傳一個 <tool_call> 區塊,否則至少用一句話回答。";
8
+
9
+ export async function loadCheckpoints(config) {
10
+ const state = await loadAppState(config.statePath);
11
+ return Array.isArray(state.checkpoints) ? state.checkpoints : [];
12
+ }
13
+
14
+ export async function saveCheckpoints(config, checkpoints) {
15
+ await saveAppState(config.statePath, { checkpoints });
16
+ }
17
+
18
+ export function findActiveCheckpoint(checkpoints) {
19
+ for (let index = checkpoints.length - 1; index >= 0; index -= 1) {
20
+ if (!checkpoints[index].completed) {
21
+ return checkpoints[index];
22
+ }
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ export function extractRecentPrompts(history, limit = DEFAULT_PROMPT_LIMIT, maxChars = DEFAULT_PROMPT_MAX_CHARS) {
29
+ if (!Array.isArray(history)) {
30
+ return [];
31
+ }
32
+
33
+ const prompts = history
34
+ .filter(isRealUserPrompt)
35
+ .map((message) => message.content.trim().slice(0, maxChars));
36
+
37
+ return prompts.slice(-limit);
38
+ }
39
+
40
+ function isRealUserPrompt(message) {
41
+ if (!message || message.role !== "user" || typeof message.content !== "string") {
42
+ return false;
43
+ }
44
+
45
+ const trimmed = message.content.trim();
46
+ if (!trimmed) {
47
+ return false;
48
+ }
49
+
50
+ if (trimmed.startsWith("<tool_result ") || trimmed.startsWith("<tool_call_error>")) {
51
+ return false;
52
+ }
53
+
54
+ if (trimmed === EMPTY_REPLY_NUDGE) {
55
+ return false;
56
+ }
57
+
58
+ return true;
59
+ }
60
+
61
+ export function createCheckpoint({
62
+ goal = "",
63
+ status = "",
64
+ completedSteps = [],
65
+ pendingSteps = [],
66
+ contextNotes = [],
67
+ blockers = [],
68
+ keyFiles = [],
69
+ recentPrompts = []
70
+ } = {}) {
71
+ const now = new Date().toISOString();
72
+ return {
73
+ id: now.replace(/[:.]/g, "-"),
74
+ createdAt: now,
75
+ updatedAt: now,
76
+ goal,
77
+ status,
78
+ completedSteps,
79
+ pendingSteps,
80
+ contextNotes,
81
+ blockers,
82
+ keyFiles,
83
+ recentPrompts,
84
+ completed: false,
85
+ completedAt: null
86
+ };
87
+ }
88
+
89
+ export function formatCheckpointSummaryLine(checkpoint) {
90
+ const tag = checkpoint.completed ? "done " : "active";
91
+ return `[${tag}] ${checkpoint.id} ${checkpoint.goal || "(no goal)"} (${checkpoint.createdAt.slice(0, 16)})`;
92
+ }
93
+
94
+ export function formatCheckpoint(checkpoint) {
95
+ const lines = [
96
+ `Checkpoint ${checkpoint.id}${checkpoint.completed ? " (completed)" : ""}`,
97
+ `Saved: ${checkpoint.createdAt}`
98
+ ];
99
+
100
+ if (checkpoint.updatedAt && checkpoint.updatedAt !== checkpoint.createdAt) {
101
+ lines.push(`Updated: ${checkpoint.updatedAt}`);
102
+ }
103
+
104
+ lines.push("", `Goal: ${checkpoint.goal || "(none)"}`, `Status: ${checkpoint.status || "(none)"}`);
105
+
106
+ pushListSection(lines, "Completed", checkpoint.completedSteps, (step) => ` [x] ${step}`);
107
+ pushListSection(lines, "Pending (continue here)", checkpoint.pendingSteps, (step) => ` [ ] ${step}`);
108
+ pushListSection(lines, "Context / decisions", checkpoint.contextNotes, (note) => ` - ${note}`);
109
+ pushListSection(lines, "Blockers", checkpoint.blockers, (blocker) => ` ! ${blocker}`);
110
+ pushListSection(lines, "Key files", checkpoint.keyFiles, (file) => ` - ${file}`);
111
+ pushListSection(lines, "Recent prompts (auto-captured)", checkpoint.recentPrompts, (prompt) => ` - ${prompt}`);
112
+
113
+ return lines.join("\n");
114
+ }
115
+
116
+ function pushListSection(lines, title, items, formatItem) {
117
+ if (!items || items.length === 0) {
118
+ return;
119
+ }
120
+
121
+ lines.push("", `${title}:`);
122
+ for (const item of items) {
123
+ lines.push(formatItem(item));
124
+ }
125
+ }