@terminus-ai/cli 0.0.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/LICENSE +21 -0
- package/README.md +1055 -0
- package/bin/agent-discovery.mjs +71 -0
- package/bin/agent-icon.mjs +77 -0
- package/bin/agent-models.mjs +77 -0
- package/bin/agent-type.mjs +51 -0
- package/bin/agentdev.mjs +657 -0
- package/bin/app-route-script.mjs +59 -0
- package/bin/app-runtime-contract.mjs +2 -0
- package/bin/appdev-remote.mjs +346 -0
- package/bin/appdev.mjs +4446 -0
- package/bin/apps.mjs +5512 -0
- package/bin/capability-calls.mjs +437 -0
- package/bin/capsule-data.mjs +260 -0
- package/bin/client.mjs +189 -0
- package/bin/commands.mjs +1194 -0
- package/bin/dev-capsules.mjs +1599 -0
- package/bin/dev-contract.mjs +262 -0
- package/bin/dev-data.mjs +287 -0
- package/bin/dev-members.mjs +18 -0
- package/bin/dev-net.mjs +316 -0
- package/bin/dev-notification-popup.mjs +628 -0
- package/bin/dev-ports.mjs +567 -0
- package/bin/dev-server-binding.mjs +35 -0
- package/bin/dev-server-ops.mjs +1086 -0
- package/bin/dev-ui/IoskeleyMono-400.woff2 +0 -0
- package/bin/dev-ui/IoskeleyMono-600.woff2 +0 -0
- package/bin/dev-ui/OFL.txt +92 -0
- package/bin/dev-ui/agent-robot.webp +0 -0
- package/bin/dev-ui/app.js +5217 -0
- package/bin/dev-ui/highlight.js +195 -0
- package/bin/dev-ui/index.html +34 -0
- package/bin/dev-ui/style.css +3640 -0
- package/bin/devlint.mjs +112 -0
- package/bin/devserver.mjs +2127 -0
- package/bin/devtriggers.mjs +367 -0
- package/bin/endpoints.mjs +156 -0
- package/bin/errors.mjs +61 -0
- package/bin/files.mjs +169 -0
- package/bin/horizontal-capabilities/v1/contract.json +280 -0
- package/bin/http.mjs +500 -0
- package/bin/lint-manifests/justbash-commands.json +88 -0
- package/bin/lint-manifests/python-stdlib.json +295 -0
- package/bin/login-page.mjs +488 -0
- package/bin/schedules.mjs +664 -0
- package/bin/server-sandbox.mjs +204 -0
- package/bin/servicedev.mjs +425 -0
- package/bin/sync.mjs +357 -0
- package/bin/terminus.js +3666 -0
- package/bin/toolchain.mjs +125 -0
- package/bin/vendor/app-runtime-v1/app-host.json +124 -0
- package/bin/vendor/app-runtime-v1/capability-calls.json +412 -0
- package/bin/vendor/app-runtime-v1/doors.json +2867 -0
- package/bin/vendor/appd/node-harness.mjs +209 -0
- package/bin/vendor/appd/python-harness.py +12 -0
- package/bin/vendor/appd/server-protocol.json +84 -0
- package/bin/vendor/where.mjs +541 -0
- package/bin/versioning.mjs +72 -0
- package/bin/write-rules.mjs +398 -0
- package/package.json +41 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free syntax highlighting for the dev editor.
|
|
3
|
+
*
|
|
4
|
+
* A compact port of the studio's approach (components/studio/codeHighlight.tsx)
|
|
5
|
+
* with the same token classes, so the two surfaces speak one vocabulary:
|
|
6
|
+
*
|
|
7
|
+
* cs-k keyword · cs-s string · cs-c comment · cs-n number
|
|
8
|
+
* cs-f call · cs-t type · cs-p property · cs-v variable
|
|
9
|
+
*
|
|
10
|
+
* Two phases. Containers first — comments and strings, which swallow
|
|
11
|
+
* everything inside them — then word classification of what is left between.
|
|
12
|
+
* The output is painted BEHIND a transparent textarea, so a miss can only
|
|
13
|
+
* mis-colour text; it can never corrupt it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Above this, highlighting costs more than it teaches. */
|
|
17
|
+
const MAX_HIGHLIGHT_CHARS = 200_000;
|
|
18
|
+
|
|
19
|
+
const KEYWORDS = {
|
|
20
|
+
js: "await async break case catch class const continue default delete do else export extends finally for from function if import in instanceof let new of return static super switch this throw try typeof var void while yield true false null undefined",
|
|
21
|
+
py: "and as assert async await break class continue def del elif else except finally for from global if import in is lambda none not or pass raise return true false try while with yield self",
|
|
22
|
+
rs: "as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut pub ref return self static struct super trait true type unsafe use where while",
|
|
23
|
+
sh: "case do done elif else esac fi for function if in return then until while export local readonly source echo cd set unset trap exit",
|
|
24
|
+
};
|
|
25
|
+
const TYPEISH = "string number boolean object any void never unknown Array Promise Record Map Set Date JSON Math Object String Number Boolean Error";
|
|
26
|
+
|
|
27
|
+
function esc(text) {
|
|
28
|
+
return String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function languageForPath(filePath) {
|
|
32
|
+
const name = String(filePath ?? "").toLowerCase();
|
|
33
|
+
const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "";
|
|
34
|
+
if (/(^|\/)(agent|readme|prompt|skill)\.md$/.test(name) || ext === "md" || ext === "mdx") return "markdown";
|
|
35
|
+
if (["js", "jsx", "mjs", "cjs", "ts", "tsx"].includes(ext)) return "js";
|
|
36
|
+
if (["json", "jsonc"].includes(ext)) return "json";
|
|
37
|
+
if (["yml", "yaml", "toml", "ini", "env"].includes(ext)) return "yaml";
|
|
38
|
+
if (ext === "py") return "py";
|
|
39
|
+
if (ext === "rs") return "rs";
|
|
40
|
+
if (["sh", "bash", "zsh"].includes(ext)) return "sh";
|
|
41
|
+
if (name.endsWith(".gitignore") || ext === "gitignore") return "conf";
|
|
42
|
+
return "plain";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Markdown is prose first: structure, not tokens. The editor paints this layer
|
|
47
|
+
* under a transparent textarea in a PROPORTIONAL face, so nothing here may
|
|
48
|
+
* change a glyph's advance — no weight, no italic, no second family, no
|
|
49
|
+
* padding. Emphasis is colour, a background, or a stroke (`-webkit-text-stroke`
|
|
50
|
+
* thickens a glyph without moving the caret). The markup characters themselves
|
|
51
|
+
* step back so the words lead.
|
|
52
|
+
*/
|
|
53
|
+
function inlineMarkdown(text) {
|
|
54
|
+
let out = esc(text);
|
|
55
|
+
// Code first: nothing inside backticks is markdown.
|
|
56
|
+
const codes = [];
|
|
57
|
+
out = out.replace(/`([^`]+)`/g, (_, body) => {
|
|
58
|
+
codes.push(`<span class="md-code"><span class="md-mark">\`</span>${body}<span class="md-mark">\`</span></span>`);
|
|
59
|
+
return `\u0000${codes.length - 1}\u0000`;
|
|
60
|
+
});
|
|
61
|
+
out = out.replace(/(\*\*|__)(?=\S)(.+?)(?<=\S)\1/g,
|
|
62
|
+
'<span class="md-mark">$1</span><span class="md-strong">$2</span><span class="md-mark">$1</span>');
|
|
63
|
+
out = out.replace(/(^|[^*\w])(\*|_)(?=\S)([^*_]+?)(?<=\S)\2(?![*\w])/g,
|
|
64
|
+
'$1<span class="md-mark">$2</span><span class="md-em">$3</span><span class="md-mark">$2</span>');
|
|
65
|
+
out = out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
66
|
+
'<span class="md-mark">[</span><span class="md-link">$1</span><span class="md-mark">]($2)</span>');
|
|
67
|
+
return out.replace(/\u0000(\d+)\u0000/g, (_, index) => codes[Number(index)]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function markdownLine(line) {
|
|
71
|
+
const heading = /^(\s{0,3}#{1,6})(\s+)(.*)$/.exec(line);
|
|
72
|
+
if (heading) {
|
|
73
|
+
return `<span class="md-mark">${esc(heading[1])}</span>${heading[2]}<span class="md-h">${inlineMarkdown(heading[3])}</span>`;
|
|
74
|
+
}
|
|
75
|
+
const quote = /^(\s{0,3}>\s?)(.*)$/.exec(line);
|
|
76
|
+
if (quote) {
|
|
77
|
+
return `<span class="md-mark">${esc(quote[1])}</span><span class="md-quote">${inlineMarkdown(quote[2])}</span>`;
|
|
78
|
+
}
|
|
79
|
+
if (/^\s{0,3}([-*_])(\s*\1){2,}\s*$/.test(line)) return `<span class="md-mark">${esc(line)}</span>`;
|
|
80
|
+
const item = /^(\s*)([-*+]|\d+[.)])(\s+)(\[[ xX]\]\s)?(.*)$/.exec(line);
|
|
81
|
+
if (item) {
|
|
82
|
+
const box = item[4] ? `<span class="md-mark">${esc(item[4])}</span>` : "";
|
|
83
|
+
return `${item[1]}<span class="md-bullet">${esc(item[2])}</span>${item[3]}${box}${inlineMarkdown(item[5])}`;
|
|
84
|
+
}
|
|
85
|
+
return inlineMarkdown(line);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** One line of code: containers, then words. */
|
|
89
|
+
function codeLine(line, lang) {
|
|
90
|
+
const commentAt = lang === "js" || lang === "rs"
|
|
91
|
+
? line.indexOf("//")
|
|
92
|
+
: ["py", "sh", "yaml", "conf"].includes(lang)
|
|
93
|
+
? line.indexOf("#")
|
|
94
|
+
: -1;
|
|
95
|
+
|
|
96
|
+
const pieces = [];
|
|
97
|
+
let rest = line;
|
|
98
|
+
let trailing = "";
|
|
99
|
+
// A # inside a string is not a comment; only split when the marker sits
|
|
100
|
+
// outside every quote we have seen so far on the line.
|
|
101
|
+
if (commentAt >= 0) {
|
|
102
|
+
const before = line.slice(0, commentAt);
|
|
103
|
+
const quotes = (before.match(/["'`]/g) ?? []).length;
|
|
104
|
+
if (quotes % 2 === 0) {
|
|
105
|
+
rest = before;
|
|
106
|
+
trailing = `<span class="cs-c">${esc(line.slice(commentAt))}</span>`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Strings are containers: capture them whole, classify only the gaps.
|
|
111
|
+
const stringPattern = /(['"`])(?:\\.|(?!\1)[^\\])*\1?/g;
|
|
112
|
+
let index = 0;
|
|
113
|
+
let match;
|
|
114
|
+
while ((match = stringPattern.exec(rest)) !== null) {
|
|
115
|
+
pieces.push(words(rest.slice(index, match.index), lang));
|
|
116
|
+
// A quoted run followed by a colon is a KEY, not a value. Without this
|
|
117
|
+
// every JSON line is one flat colour, because a key is a string too.
|
|
118
|
+
const isKey = /^\s*:/.test(rest.slice(match.index + match[0].length));
|
|
119
|
+
pieces.push(`<span class="${isKey ? "cs-p" : "cs-s"}">${esc(match[0])}</span>`);
|
|
120
|
+
index = match.index + match[0].length;
|
|
121
|
+
}
|
|
122
|
+
pieces.push(words(rest.slice(index), lang));
|
|
123
|
+
return pieces.join("") + trailing;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function words(chunk, lang) {
|
|
127
|
+
if (!chunk) return "";
|
|
128
|
+
const keywords = new Set((KEYWORDS[lang] ?? "").split(" ").filter(Boolean));
|
|
129
|
+
const types = new Set(TYPEISH.split(" "));
|
|
130
|
+
let out = "";
|
|
131
|
+
const pattern = /([A-Za-z_$][\w$]*)|(\d[\d_.]*)|([^A-Za-z_$\d]+)/g;
|
|
132
|
+
let match;
|
|
133
|
+
while ((match = pattern.exec(chunk)) !== null) {
|
|
134
|
+
const [token, word, number, other] = match;
|
|
135
|
+
if (number) {
|
|
136
|
+
out += `<span class="cs-n">${esc(number)}</span>`;
|
|
137
|
+
} else if (word) {
|
|
138
|
+
const after = chunk.slice(match.index + word.length);
|
|
139
|
+
const isKey = lang === "json" || lang === "yaml" ? /^\s*:/.test(after) : false;
|
|
140
|
+
if (keywords.has(word.toLowerCase()) && keywords.has(word.toLowerCase())) {
|
|
141
|
+
out += `<span class="cs-k">${esc(word)}</span>`;
|
|
142
|
+
} else if (isKey) {
|
|
143
|
+
out += `<span class="cs-p">${esc(word)}</span>`;
|
|
144
|
+
} else if (types.has(word)) {
|
|
145
|
+
out += `<span class="cs-t">${esc(word)}</span>`;
|
|
146
|
+
} else if (/^\s*\(/.test(after)) {
|
|
147
|
+
out += `<span class="cs-f">${esc(word)}</span>`;
|
|
148
|
+
} else {
|
|
149
|
+
out += esc(word);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
out += esc(other ?? token);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Highlight `text` as `lang`, returning ONE `<span class="hl-line">` per line.
|
|
160
|
+
* A line element per line is what lets the gutter and the active-line band
|
|
161
|
+
* agree with the wrapped text without measuring anything.
|
|
162
|
+
*/
|
|
163
|
+
export function highlight(text, lang, { active = -1 } = {}) {
|
|
164
|
+
const source = String(text ?? "");
|
|
165
|
+
const lines = source.split("\n");
|
|
166
|
+
// Each line carries its own number (painted in the margin by CSS) and knows
|
|
167
|
+
// whether it is the caret's, so numbers and the current-line band follow
|
|
168
|
+
// soft-wrapped text without measuring anything.
|
|
169
|
+
const open = (index, extra = "") =>
|
|
170
|
+
`<span class="hl-line${index === active ? " is-active" : ""}${extra}" data-n="${index + 1}">`;
|
|
171
|
+
if (source.length > MAX_HIGHLIGHT_CHARS || lang === "plain" || lang === "conf") {
|
|
172
|
+
return lines.map((line, index) => `${open(index)}${esc(line) || "\u200b"}</span>`).join("");
|
|
173
|
+
}
|
|
174
|
+
if (lang === "markdown") {
|
|
175
|
+
// Fenced blocks span lines, so the fence is tracked across them: the
|
|
176
|
+
// fence lines and everything between wear a quiet band, untokenized.
|
|
177
|
+
let fence = null;
|
|
178
|
+
return lines
|
|
179
|
+
.map((line, index) => {
|
|
180
|
+
const marker = /^\s{0,3}(```+|~~~+)/.exec(line);
|
|
181
|
+
if (fence || marker) {
|
|
182
|
+
const closing = fence && marker && marker[1][0] === fence[0] && marker[1].length >= fence.length;
|
|
183
|
+
if (!fence) fence = marker[1];
|
|
184
|
+
else if (closing) fence = null;
|
|
185
|
+
const cls = marker ? "md-mark" : "md-fenced";
|
|
186
|
+
return `${open(index, " md-fence")}<span class="${cls}">${esc(line) || "\u200b"}</span></span>`;
|
|
187
|
+
}
|
|
188
|
+
return `${open(index)}${markdownLine(line) || "\u200b"}</span>`;
|
|
189
|
+
})
|
|
190
|
+
.join("");
|
|
191
|
+
}
|
|
192
|
+
return lines
|
|
193
|
+
.map((line, index) => `${open(index)}${codeLine(line, lang) || "\u200b"}</span>`)
|
|
194
|
+
.join("");
|
|
195
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>terminus dev</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=EB+Garamond:wght@400;500;600&family=Inter:wght@400;500;600&family=Noto+Sans:wght@400;500;600&display=swap">
|
|
10
|
+
<link rel="stylesheet" href="/assets/style.css">
|
|
11
|
+
<script>
|
|
12
|
+
try {
|
|
13
|
+
document.documentElement.dataset.theme =
|
|
14
|
+
localStorage.getItem("terminus-dev-theme") === "dark" ? "dark" : "light";
|
|
15
|
+
} catch {
|
|
16
|
+
document.documentElement.dataset.theme = "light";
|
|
17
|
+
}
|
|
18
|
+
window.__DEV__ = __DEV_BOOT__;
|
|
19
|
+
</script>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div class="water" aria-hidden="true">
|
|
23
|
+
<div class="water-basin"></div>
|
|
24
|
+
<div class="water-glow"></div>
|
|
25
|
+
<div class="water-current water-current-a"></div>
|
|
26
|
+
<div class="water-current water-current-b"></div>
|
|
27
|
+
<div class="water-ribbon water-ribbon-a"></div>
|
|
28
|
+
<div class="water-ribbon water-ribbon-b"></div>
|
|
29
|
+
<div class="water-grain"></div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="app" id="app"></div>
|
|
32
|
+
<script type="module" src="/assets/app.js"></script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|