lampson 0.1.0 → 0.1.2
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 +394 -382
- package/bin/lampson.js +8 -2
- package/chat.syn +1029 -799
- package/lampson.cmd +4 -4
- package/lampson.ps1 +88 -88
- package/lampson.sh +42 -42
- package/lib/agents.syn +471 -471
- package/lib/diff.syn +142 -0
- package/lib/lamps.syn +386 -386
- package/lib/line.syn +365 -0
- package/lib/lsp.syn +503 -503
- package/lib/mcp.syn +403 -403
- package/lib/md.syn +171 -0
- package/lib/permission.syn +189 -154
- package/lib/prompt.syn +75 -75
- package/lib/session.syn +111 -111
- package/lib/skills.syn +179 -179
- package/lib/tools/bash.syn +105 -105
- package/lib/tools/edit.syn +35 -32
- package/lib/tools/memo.syn +148 -148
- package/lib/tools/process.syn +46 -46
- package/lib/tools/write.syn +4 -0
- package/lib/tools.syn +198 -198
- package/lib/tree.syn +59 -59
- package/package.json +2 -2
- package/public/index.html +1268 -1268
- package/skills/lampson/SKILL.md +117 -117
- package/skills/synsema/SKILL.md +15 -2
- package/web.syn +468 -468
package/lib/tools/edit.syn
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
-- lib/tools/edit.syn — reemplazo exacto y único (sin fuzzy replacers: exactitud antes que magia)
|
|
2
|
-
use "./common.syn" as c
|
|
3
|
-
|
|
4
|
-
export task tool(path, old_string, new_string, replace_all)
|
|
5
|
-
require file("workspace")
|
|
6
|
-
require file("workspace/*")
|
|
7
|
-
let real be c.ws(path)
|
|
8
|
-
let shown be c.unws(real)
|
|
9
|
-
c.check_observed(real, "edit")
|
|
10
|
-
let content be read_file(real)
|
|
11
|
-
let parts be split(content, old_string)
|
|
12
|
-
let n be length(parts) - 1
|
|
13
|
-
when n == 0
|
|
14
|
-
raise("old_string not found in " + shown + " — read the file and copy the exact text (whitespace included)")
|
|
15
|
-
let all be when replace_all == nothing then false otherwise replace_all
|
|
16
|
-
when n > 1
|
|
17
|
-
when not all
|
|
18
|
-
raise(`old_string appears {text(n)} times in {shown}; include more surrounding context to make it unique, or pass replace_all=true`)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
-- lib/tools/edit.syn — reemplazo exacto y único (sin fuzzy replacers: exactitud antes que magia)
|
|
2
|
+
use "./common.syn" as c
|
|
3
|
+
|
|
4
|
+
export task tool(path, old_string, new_string, replace_all)
|
|
5
|
+
require file("workspace")
|
|
6
|
+
require file("workspace/*")
|
|
7
|
+
let real be c.ws(path)
|
|
8
|
+
let shown be c.unws(real)
|
|
9
|
+
c.check_observed(real, "edit")
|
|
10
|
+
let content be read_file(real)
|
|
11
|
+
let parts be split(content, old_string)
|
|
12
|
+
let n be length(parts) - 1
|
|
13
|
+
when n == 0
|
|
14
|
+
raise("old_string not found in " + shown + " — read the file and copy the exact text (whitespace included)")
|
|
15
|
+
let all be when replace_all == nothing then false otherwise replace_all
|
|
16
|
+
when n > 1
|
|
17
|
+
when not all
|
|
18
|
+
raise(`old_string appears {text(n)} times in {shown}; include more surrounding context to make it unique, or pass replace_all=true`)
|
|
19
|
+
let after be join(parts, new_string)
|
|
20
|
+
write_file(real, after)
|
|
21
|
+
c.mark_observed(real)
|
|
22
|
+
-- antes/después para que la UI muestre el diff (chat.syn / web); el modelo solo recibe la línea de abajo
|
|
23
|
+
share {"path": shown, "old": content, "new": after} as "lampson:ui:diff"
|
|
24
|
+
give `edited {shown}: {text(n)} replacement(s)`
|
|
25
|
+
|
|
26
|
+
export let SPEC be {
|
|
27
|
+
"name": "edit",
|
|
28
|
+
"description": "Replace an exact string in a file. old_string must match EXACTLY (including whitespace and indentation) and be unique in the file, otherwise the call fails — include more surrounding lines to disambiguate, or set replace_all=true to replace every occurrence. The file MUST have been read in this session (and not changed since), or the call is rejected.",
|
|
29
|
+
"parameters": {"type": "object", "properties": {
|
|
30
|
+
"path": {"type": "string"},
|
|
31
|
+
"old_string": {"type": "string"},
|
|
32
|
+
"new_string": {"type": "string"},
|
|
33
|
+
"replace_all": {"type": "boolean"}
|
|
34
|
+
}, "required": ["path", "old_string", "new_string"]}
|
|
35
|
+
}
|
package/lib/tools/memo.syn
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
-- lib/tools/memo.syn — memoria por proyecto: notas Markdown que el agente escribe y relee
|
|
2
|
-
--
|
|
3
|
-
-- Dónde: lampson/memory/<slug>/<nombre>.md — <slug> sale de la ruta REAL del workspace
|
|
4
|
-
-- (LAMPSON_WORKSPACE, que fija el launcher): "<carpeta>-<hash8>". Así cada proyecto tiene su carpeta
|
|
5
|
-
-- aunque dos se llamen igual, y las notas viven en lampson (no ensucian el repo del usuario).
|
|
6
|
-
--
|
|
7
|
-
-- El system prompt lleva SOLO el índice (nombre + primera línea de cada nota); el contenido entra al
|
|
8
|
-
-- contexto cuando el agente llama `memory(read)`. Son archivos: el humano los lee/edita a mano y la
|
|
9
|
-
-- UI web los muestra.
|
|
10
|
-
use "./common.syn" as c
|
|
11
|
-
|
|
12
|
-
export let ROOT be "memory"
|
|
13
|
-
|
|
14
|
-
task valid_name(name)
|
|
15
|
-
when name == nothing or name == ""
|
|
16
|
-
give false
|
|
17
|
-
give matches(name, "[a-zA-Z0-9_-]{1,60}")
|
|
18
|
-
|
|
19
|
-
-- slug del proyecto montado
|
|
20
|
-
export task slug()
|
|
21
|
-
require env("LAMPSON_*")
|
|
22
|
-
let ws be env("LAMPSON_WORKSPACE", "workspace")
|
|
23
|
-
let norm be replace_text(lower(ws), "\\", "/")
|
|
24
|
-
let parts be where(split(norm, "/"), (x) => x != "")
|
|
25
|
-
let base be when length(parts) > 0 then parts[length(parts) - 1] otherwise "workspace"
|
|
26
|
-
let runs be find_all(base, "[a-z0-9]+")
|
|
27
|
-
let clean be when length(runs) == 0 then "project" otherwise join(runs, "-")
|
|
28
|
-
let h be slice(decode(sha256(norm), "hex"), 0, 8)
|
|
29
|
-
give clean + "-" + h
|
|
30
|
-
|
|
31
|
-
export task dir()
|
|
32
|
-
require env("LAMPSON_*")
|
|
33
|
-
give ROOT + "/" + slug()
|
|
34
|
-
|
|
35
|
-
task path(name)
|
|
36
|
-
give dir() + "/" + name + ".md"
|
|
37
|
-
|
|
38
|
-
-- [{name, title}] — title = primera línea no vacía (sin '#')
|
|
39
|
-
export task list()
|
|
40
|
-
require env("LAMPSON_*")
|
|
41
|
-
require file.read("memory")
|
|
42
|
-
require file.read("memory/*")
|
|
43
|
-
let out be []
|
|
44
|
-
try
|
|
45
|
-
each e in list_dir(dir())
|
|
46
|
-
when not e["is_dir"] and c.ends_with(e["name"], ".md")
|
|
47
|
-
let name be slice(e["name"], 0, length(e["name"]) - 3)
|
|
48
|
-
let title be ""
|
|
49
|
-
each line in split(read_file(path(name)), "\n")
|
|
50
|
-
when title == "" and trim(line) != ""
|
|
51
|
-
set title to trim(replace_text(line, "#", ""))
|
|
52
|
-
set out to append(out, {"name": name, "title": when length(title) > 120 then slice(title, 0, 120) otherwise title})
|
|
53
|
-
recover err
|
|
54
|
-
-- el runtime lanza "Not a directory: …" (mayúscula) cuando memory/<slug> aún no existe
|
|
55
|
-
let e be lower(text(err))
|
|
56
|
-
when not contains(e, "not a directory") and not contains(e, "not found") and not contains(e, "no such")
|
|
57
|
-
raise(err)
|
|
58
|
-
give []
|
|
59
|
-
give sort_by(out, (x) => x["name"])
|
|
60
|
-
|
|
61
|
-
export task note_read(name)
|
|
62
|
-
require env("LAMPSON_*")
|
|
63
|
-
require file.read("memory")
|
|
64
|
-
require file.read("memory/*")
|
|
65
|
-
when not valid_name(name)
|
|
66
|
-
raise("invalid note name")
|
|
67
|
-
try
|
|
68
|
-
give read_file(path(name))
|
|
69
|
-
recover err
|
|
70
|
-
raise("no such note '" + name + "'. Existing: " + join(apply((x) => x["name"], list()), ", "))
|
|
71
|
-
|
|
72
|
-
export task note_write(name, content)
|
|
73
|
-
require env("LAMPSON_*")
|
|
74
|
-
require file("memory")
|
|
75
|
-
require file("memory/*")
|
|
76
|
-
when not valid_name(name)
|
|
77
|
-
raise("invalid note name '" + text(name) + "' (letters, digits, - or _)")
|
|
78
|
-
write_file(path(name), content)
|
|
79
|
-
give "saved memory/" + slug() + "/" + name + ".md (" + text(length(content)) + " chars)"
|
|
80
|
-
|
|
81
|
-
export task note_append(name, content)
|
|
82
|
-
require env("LAMPSON_*")
|
|
83
|
-
require file("memory")
|
|
84
|
-
require file("memory/*")
|
|
85
|
-
when not valid_name(name)
|
|
86
|
-
raise("invalid note name")
|
|
87
|
-
let prev be ""
|
|
88
|
-
try
|
|
89
|
-
set prev to read_file(path(name))
|
|
90
|
-
recover err
|
|
91
|
-
set prev to ""
|
|
92
|
-
let joined be when prev == "" then content otherwise prev + "\n\n" + content
|
|
93
|
-
write_file(path(name), joined)
|
|
94
|
-
give "appended to " + name + ".md (" + text(length(joined)) + " chars total)"
|
|
95
|
-
|
|
96
|
-
export task note_clear(name)
|
|
97
|
-
require env("LAMPSON_*")
|
|
98
|
-
require file("memory")
|
|
99
|
-
require file("memory/*")
|
|
100
|
-
when not valid_name(name)
|
|
101
|
-
raise("invalid note name")
|
|
102
|
-
write_file(path(name), "")
|
|
103
|
-
give "cleared " + name + ".md (the file stays empty; the user can delete it)"
|
|
104
|
-
|
|
105
|
-
-- sección del system prompt
|
|
106
|
-
export task prompt_section()
|
|
107
|
-
require env("LAMPSON_*")
|
|
108
|
-
require file.read("memory")
|
|
109
|
-
require file.read("memory/*")
|
|
110
|
-
let items be list()
|
|
111
|
-
let head be "\n\n# Project memory (your own notes about THIS project, in memory/" + slug() + "/ — read with memory(read), keep them current with memory(write|append))"
|
|
112
|
-
when length(items) == 0
|
|
113
|
-
give head + "\n(empty — when you discover something non-obvious about this project: how to run/test it, gotchas, decisions, where things live — save it with memory(write). Keep notes short and factual.)"
|
|
114
|
-
let lines be [head]
|
|
115
|
-
each it in items
|
|
116
|
-
set lines to append(lines, "- " + it["name"] + ": " + it["title"])
|
|
117
|
-
give join(lines, "\n")
|
|
118
|
-
|
|
119
|
-
-- ---------- tool ----------
|
|
120
|
-
|
|
121
|
-
export task tool(action, name, content)
|
|
122
|
-
require env("LAMPSON_*")
|
|
123
|
-
require file("memory")
|
|
124
|
-
require file("memory/*")
|
|
125
|
-
when action == "list"
|
|
126
|
-
let items be list()
|
|
127
|
-
when length(items) == 0
|
|
128
|
-
give "no notes yet for this project"
|
|
129
|
-
give join(apply((x) => x["name"] + ": " + x["title"], items), "\n")
|
|
130
|
-
when action == "read"
|
|
131
|
-
give note_read(name)
|
|
132
|
-
when action == "write"
|
|
133
|
-
give note_write(name, content)
|
|
134
|
-
when action == "append"
|
|
135
|
-
give note_append(name, content)
|
|
136
|
-
when action == "delete"
|
|
137
|
-
give note_clear(name)
|
|
138
|
-
raise("unknown action '" + text(action) + "' (list | read | write | append | delete)")
|
|
139
|
-
|
|
140
|
-
export let SPEC be {
|
|
141
|
-
"name": "memory",
|
|
142
|
-
"description": "Your persistent notes about THIS project, kept across sessions (Markdown files in Lampson's memory folder, never inside the repo). Use `write` to save non-obvious facts you discovered and will need again: how to run/build/test, environment quirks, architecture decisions, where things live, bugs and their causes, what the user prefers. `append` adds to an existing note, `read` loads one, `list` shows them, `delete` clears one. The system prompt shows the index of notes — read the relevant ones before repeating an investigation. Keep notes short, factual and current: update a note instead of writing a contradictory one.",
|
|
143
|
-
"parameters": {"type": "object", "properties": {
|
|
144
|
-
"action": {"type": "string", "enum": ["list", "read", "write", "append", "delete"]},
|
|
145
|
-
"name": {"type": "string", "description": "Note id: letters, digits, - or _ (e.g. how-to-run, db-schema, gotchas)"},
|
|
146
|
-
"content": {"type": "string", "description": "write/append: Markdown content (first line = title)"}
|
|
147
|
-
}, "required": ["action"]}
|
|
148
|
-
}
|
|
1
|
+
-- lib/tools/memo.syn — memoria por proyecto: notas Markdown que el agente escribe y relee
|
|
2
|
+
--
|
|
3
|
+
-- Dónde: lampson/memory/<slug>/<nombre>.md — <slug> sale de la ruta REAL del workspace
|
|
4
|
+
-- (LAMPSON_WORKSPACE, que fija el launcher): "<carpeta>-<hash8>". Así cada proyecto tiene su carpeta
|
|
5
|
+
-- aunque dos se llamen igual, y las notas viven en lampson (no ensucian el repo del usuario).
|
|
6
|
+
--
|
|
7
|
+
-- El system prompt lleva SOLO el índice (nombre + primera línea de cada nota); el contenido entra al
|
|
8
|
+
-- contexto cuando el agente llama `memory(read)`. Son archivos: el humano los lee/edita a mano y la
|
|
9
|
+
-- UI web los muestra.
|
|
10
|
+
use "./common.syn" as c
|
|
11
|
+
|
|
12
|
+
export let ROOT be "memory"
|
|
13
|
+
|
|
14
|
+
task valid_name(name)
|
|
15
|
+
when name == nothing or name == ""
|
|
16
|
+
give false
|
|
17
|
+
give matches(name, "[a-zA-Z0-9_-]{1,60}")
|
|
18
|
+
|
|
19
|
+
-- slug del proyecto montado
|
|
20
|
+
export task slug()
|
|
21
|
+
require env("LAMPSON_*")
|
|
22
|
+
let ws be env("LAMPSON_WORKSPACE", "workspace")
|
|
23
|
+
let norm be replace_text(lower(ws), "\\", "/")
|
|
24
|
+
let parts be where(split(norm, "/"), (x) => x != "")
|
|
25
|
+
let base be when length(parts) > 0 then parts[length(parts) - 1] otherwise "workspace"
|
|
26
|
+
let runs be find_all(base, "[a-z0-9]+")
|
|
27
|
+
let clean be when length(runs) == 0 then "project" otherwise join(runs, "-")
|
|
28
|
+
let h be slice(decode(sha256(norm), "hex"), 0, 8)
|
|
29
|
+
give clean + "-" + h
|
|
30
|
+
|
|
31
|
+
export task dir()
|
|
32
|
+
require env("LAMPSON_*")
|
|
33
|
+
give ROOT + "/" + slug()
|
|
34
|
+
|
|
35
|
+
task path(name)
|
|
36
|
+
give dir() + "/" + name + ".md"
|
|
37
|
+
|
|
38
|
+
-- [{name, title}] — title = primera línea no vacía (sin '#')
|
|
39
|
+
export task list()
|
|
40
|
+
require env("LAMPSON_*")
|
|
41
|
+
require file.read("memory")
|
|
42
|
+
require file.read("memory/*")
|
|
43
|
+
let out be []
|
|
44
|
+
try
|
|
45
|
+
each e in list_dir(dir())
|
|
46
|
+
when not e["is_dir"] and c.ends_with(e["name"], ".md")
|
|
47
|
+
let name be slice(e["name"], 0, length(e["name"]) - 3)
|
|
48
|
+
let title be ""
|
|
49
|
+
each line in split(read_file(path(name)), "\n")
|
|
50
|
+
when title == "" and trim(line) != ""
|
|
51
|
+
set title to trim(replace_text(line, "#", ""))
|
|
52
|
+
set out to append(out, {"name": name, "title": when length(title) > 120 then slice(title, 0, 120) otherwise title})
|
|
53
|
+
recover err
|
|
54
|
+
-- el runtime lanza "Not a directory: …" (mayúscula) cuando memory/<slug> aún no existe
|
|
55
|
+
let e be lower(text(err))
|
|
56
|
+
when not contains(e, "not a directory") and not contains(e, "not found") and not contains(e, "no such")
|
|
57
|
+
raise(err)
|
|
58
|
+
give []
|
|
59
|
+
give sort_by(out, (x) => x["name"])
|
|
60
|
+
|
|
61
|
+
export task note_read(name)
|
|
62
|
+
require env("LAMPSON_*")
|
|
63
|
+
require file.read("memory")
|
|
64
|
+
require file.read("memory/*")
|
|
65
|
+
when not valid_name(name)
|
|
66
|
+
raise("invalid note name")
|
|
67
|
+
try
|
|
68
|
+
give read_file(path(name))
|
|
69
|
+
recover err
|
|
70
|
+
raise("no such note '" + name + "'. Existing: " + join(apply((x) => x["name"], list()), ", "))
|
|
71
|
+
|
|
72
|
+
export task note_write(name, content)
|
|
73
|
+
require env("LAMPSON_*")
|
|
74
|
+
require file("memory")
|
|
75
|
+
require file("memory/*")
|
|
76
|
+
when not valid_name(name)
|
|
77
|
+
raise("invalid note name '" + text(name) + "' (letters, digits, - or _)")
|
|
78
|
+
write_file(path(name), content)
|
|
79
|
+
give "saved memory/" + slug() + "/" + name + ".md (" + text(length(content)) + " chars)"
|
|
80
|
+
|
|
81
|
+
export task note_append(name, content)
|
|
82
|
+
require env("LAMPSON_*")
|
|
83
|
+
require file("memory")
|
|
84
|
+
require file("memory/*")
|
|
85
|
+
when not valid_name(name)
|
|
86
|
+
raise("invalid note name")
|
|
87
|
+
let prev be ""
|
|
88
|
+
try
|
|
89
|
+
set prev to read_file(path(name))
|
|
90
|
+
recover err
|
|
91
|
+
set prev to ""
|
|
92
|
+
let joined be when prev == "" then content otherwise prev + "\n\n" + content
|
|
93
|
+
write_file(path(name), joined)
|
|
94
|
+
give "appended to " + name + ".md (" + text(length(joined)) + " chars total)"
|
|
95
|
+
|
|
96
|
+
export task note_clear(name)
|
|
97
|
+
require env("LAMPSON_*")
|
|
98
|
+
require file("memory")
|
|
99
|
+
require file("memory/*")
|
|
100
|
+
when not valid_name(name)
|
|
101
|
+
raise("invalid note name")
|
|
102
|
+
write_file(path(name), "")
|
|
103
|
+
give "cleared " + name + ".md (the file stays empty; the user can delete it)"
|
|
104
|
+
|
|
105
|
+
-- sección del system prompt
|
|
106
|
+
export task prompt_section()
|
|
107
|
+
require env("LAMPSON_*")
|
|
108
|
+
require file.read("memory")
|
|
109
|
+
require file.read("memory/*")
|
|
110
|
+
let items be list()
|
|
111
|
+
let head be "\n\n# Project memory (your own notes about THIS project, in memory/" + slug() + "/ — read with memory(read), keep them current with memory(write|append))"
|
|
112
|
+
when length(items) == 0
|
|
113
|
+
give head + "\n(empty — when you discover something non-obvious about this project: how to run/test it, gotchas, decisions, where things live — save it with memory(write). Keep notes short and factual.)"
|
|
114
|
+
let lines be [head]
|
|
115
|
+
each it in items
|
|
116
|
+
set lines to append(lines, "- " + it["name"] + ": " + it["title"])
|
|
117
|
+
give join(lines, "\n")
|
|
118
|
+
|
|
119
|
+
-- ---------- tool ----------
|
|
120
|
+
|
|
121
|
+
export task tool(action, name, content)
|
|
122
|
+
require env("LAMPSON_*")
|
|
123
|
+
require file("memory")
|
|
124
|
+
require file("memory/*")
|
|
125
|
+
when action == "list"
|
|
126
|
+
let items be list()
|
|
127
|
+
when length(items) == 0
|
|
128
|
+
give "no notes yet for this project"
|
|
129
|
+
give join(apply((x) => x["name"] + ": " + x["title"], items), "\n")
|
|
130
|
+
when action == "read"
|
|
131
|
+
give note_read(name)
|
|
132
|
+
when action == "write"
|
|
133
|
+
give note_write(name, content)
|
|
134
|
+
when action == "append"
|
|
135
|
+
give note_append(name, content)
|
|
136
|
+
when action == "delete"
|
|
137
|
+
give note_clear(name)
|
|
138
|
+
raise("unknown action '" + text(action) + "' (list | read | write | append | delete)")
|
|
139
|
+
|
|
140
|
+
export let SPEC be {
|
|
141
|
+
"name": "memory",
|
|
142
|
+
"description": "Your persistent notes about THIS project, kept across sessions (Markdown files in Lampson's memory folder, never inside the repo). Use `write` to save non-obvious facts you discovered and will need again: how to run/build/test, environment quirks, architecture decisions, where things live, bugs and their causes, what the user prefers. `append` adds to an existing note, `read` loads one, `list` shows them, `delete` clears one. The system prompt shows the index of notes — read the relevant ones before repeating an investigation. Keep notes short, factual and current: update a note instead of writing a contradictory one.",
|
|
143
|
+
"parameters": {"type": "object", "properties": {
|
|
144
|
+
"action": {"type": "string", "enum": ["list", "read", "write", "append", "delete"]},
|
|
145
|
+
"name": {"type": "string", "description": "Note id: letters, digits, - or _ (e.g. how-to-run, db-schema, gotchas)"},
|
|
146
|
+
"content": {"type": "string", "description": "write/append: Markdown content (first line = title)"}
|
|
147
|
+
}, "required": ["action"]}
|
|
148
|
+
}
|
package/lib/tools/process.syn
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
-- lib/tools/process.syn — tool `process`: servidores y watchers gestionados (start / logs / stop / list)
|
|
2
|
-
use "./proc.syn" as proc
|
|
3
|
-
|
|
4
|
-
export task tool(action, name, command, tail)
|
|
5
|
-
require exec
|
|
6
|
-
require time
|
|
7
|
-
require env("LAMPSON_*")
|
|
8
|
-
require env("OS")
|
|
9
|
-
require file(".lampson")
|
|
10
|
-
require file(".lampson/*")
|
|
11
|
-
when action == "start"
|
|
12
|
-
when command == nothing or command == ""
|
|
13
|
-
raise("start needs a command")
|
|
14
|
-
give proc.start(name, command)
|
|
15
|
-
when action == "logs"
|
|
16
|
-
give proc.logs(name, tail)
|
|
17
|
-
when action == "stop"
|
|
18
|
-
give proc.halt(name)
|
|
19
|
-
when action == "ports"
|
|
20
|
-
let ls be proc.listeners()
|
|
21
|
-
when length(ls) == 0
|
|
22
|
-
give "no TCP ports listening (>=1024)"
|
|
23
|
-
let lines be []
|
|
24
|
-
each l in ls
|
|
25
|
-
set lines to append(lines, "port " + text(l["port"]) + " pid " + text(l["pid"]) + " " + l["name"])
|
|
26
|
-
give join(lines, "\n")
|
|
27
|
-
when action == "list"
|
|
28
|
-
let items be proc.list()
|
|
29
|
-
when length(items) == 0
|
|
30
|
-
give "no managed processes"
|
|
31
|
-
let lines be []
|
|
32
|
-
each p in items
|
|
33
|
-
set lines to append(lines, p["name"] + " " + (when p["running"] then "running" otherwise "exited") + " $ " + p["command"])
|
|
34
|
-
give join(lines, "\n")
|
|
35
|
-
raise("unknown action '" + text(action) + "' (start | logs | stop | list | ports)")
|
|
36
|
-
|
|
37
|
-
export let SPEC be {
|
|
38
|
-
"name": "process",
|
|
39
|
-
"description": "Manage long-running processes (dev servers, watchers, APIs) so you can SEE their output. start: launches `command` detached with a `name`; its stdout/stderr go to a log you can read any time. After every bash call you automatically receive the NEW log lines of every managed process, so errors in a server you started reach you without asking. logs: last `tail` lines (default 60). A process is EXITED only when its pid is really gone; then its log ends with `[process exited with code N]` — read the reason there instead of retrying the command in bash. stop: kills the process and its whole tree (npm → node included). list: what is running. ports: every TCP port listening on the machine with its pid and program — use it when a port is busy (EADDRINUSE) or to check whether something is already up. Always use this instead of `cmd &` in bash, and stop what you started when the task is done.",
|
|
40
|
-
"parameters": {"type": "object", "properties": {
|
|
41
|
-
"action": {"type": "string", "enum": ["start", "logs", "stop", "list", "ports"]},
|
|
42
|
-
"name": {"type": "string", "description": "Short id: letters, digits, - or _ (e.g. web, api, dev)"},
|
|
43
|
-
"command": {"type": "string", "description": "start only: the shell command (runs with cwd = workspace root)"},
|
|
44
|
-
"tail": {"type": "integer", "description": "logs only: number of lines (default 60)"}
|
|
45
|
-
}, "required": ["action"]}
|
|
46
|
-
}
|
|
1
|
+
-- lib/tools/process.syn — tool `process`: servidores y watchers gestionados (start / logs / stop / list)
|
|
2
|
+
use "./proc.syn" as proc
|
|
3
|
+
|
|
4
|
+
export task tool(action, name, command, tail)
|
|
5
|
+
require exec
|
|
6
|
+
require time
|
|
7
|
+
require env("LAMPSON_*")
|
|
8
|
+
require env("OS")
|
|
9
|
+
require file(".lampson")
|
|
10
|
+
require file(".lampson/*")
|
|
11
|
+
when action == "start"
|
|
12
|
+
when command == nothing or command == ""
|
|
13
|
+
raise("start needs a command")
|
|
14
|
+
give proc.start(name, command)
|
|
15
|
+
when action == "logs"
|
|
16
|
+
give proc.logs(name, tail)
|
|
17
|
+
when action == "stop"
|
|
18
|
+
give proc.halt(name)
|
|
19
|
+
when action == "ports"
|
|
20
|
+
let ls be proc.listeners()
|
|
21
|
+
when length(ls) == 0
|
|
22
|
+
give "no TCP ports listening (>=1024)"
|
|
23
|
+
let lines be []
|
|
24
|
+
each l in ls
|
|
25
|
+
set lines to append(lines, "port " + text(l["port"]) + " pid " + text(l["pid"]) + " " + l["name"])
|
|
26
|
+
give join(lines, "\n")
|
|
27
|
+
when action == "list"
|
|
28
|
+
let items be proc.list()
|
|
29
|
+
when length(items) == 0
|
|
30
|
+
give "no managed processes"
|
|
31
|
+
let lines be []
|
|
32
|
+
each p in items
|
|
33
|
+
set lines to append(lines, p["name"] + " " + (when p["running"] then "running" otherwise "exited") + " $ " + p["command"])
|
|
34
|
+
give join(lines, "\n")
|
|
35
|
+
raise("unknown action '" + text(action) + "' (start | logs | stop | list | ports)")
|
|
36
|
+
|
|
37
|
+
export let SPEC be {
|
|
38
|
+
"name": "process",
|
|
39
|
+
"description": "Manage long-running processes (dev servers, watchers, APIs) so you can SEE their output. start: launches `command` detached with a `name`; its stdout/stderr go to a log you can read any time. After every bash call you automatically receive the NEW log lines of every managed process, so errors in a server you started reach you without asking. logs: last `tail` lines (default 60). A process is EXITED only when its pid is really gone; then its log ends with `[process exited with code N]` — read the reason there instead of retrying the command in bash. stop: kills the process and its whole tree (npm → node included). list: what is running. ports: every TCP port listening on the machine with its pid and program — use it when a port is busy (EADDRINUSE) or to check whether something is already up. Always use this instead of `cmd &` in bash, and stop what you started when the task is done.",
|
|
40
|
+
"parameters": {"type": "object", "properties": {
|
|
41
|
+
"action": {"type": "string", "enum": ["start", "logs", "stop", "list", "ports"]},
|
|
42
|
+
"name": {"type": "string", "description": "Short id: letters, digits, - or _ (e.g. web, api, dev)"},
|
|
43
|
+
"command": {"type": "string", "description": "start only: the shell command (runs with cwd = workspace root)"},
|
|
44
|
+
"tail": {"type": "integer", "description": "logs only: number of lines (default 60)"}
|
|
45
|
+
}, "required": ["action"]}
|
|
46
|
+
}
|
package/lib/tools/write.syn
CHANGED
|
@@ -6,10 +6,14 @@ export task tool(path, content)
|
|
|
6
6
|
require file("workspace/*")
|
|
7
7
|
let real be c.ws(path)
|
|
8
8
|
-- sobrescribir un archivo existente exige haberlo leído (y que no haya cambiado): igual que edit
|
|
9
|
+
let before be ""
|
|
9
10
|
when file_exists(real)
|
|
10
11
|
c.check_observed(real, "write")
|
|
12
|
+
set before to read_file(real)
|
|
11
13
|
write_file(real, content)
|
|
12
14
|
c.mark_observed(real)
|
|
15
|
+
-- antes/después para que la UI muestre el diff (archivo nuevo: before = "")
|
|
16
|
+
share {"path": c.unws(real), "old": before, "new": content} as "lampson:ui:diff"
|
|
13
17
|
give `wrote {text(length(content))} chars to {c.unws(real)}`
|
|
14
18
|
|
|
15
19
|
export let SPEC be {
|