@vascend/talos 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.
Files changed (66) hide show
  1. package/README.md +7 -3
  2. package/dist/backend/asset-events.js +9 -9
  3. package/dist/core/agent.js +3 -3
  4. package/dist/core/cli-args.js +50 -50
  5. package/dist/core/cli-installation.d.ts +4 -0
  6. package/dist/core/cli-installation.js +55 -0
  7. package/dist/core/compaction.js +26 -26
  8. package/dist/core/config.js +101 -101
  9. package/dist/core/execution-guidance.js +21 -21
  10. package/dist/core/package-update.d.ts +3 -1
  11. package/dist/core/package-update.js +19 -6
  12. package/dist/core/self-update.d.ts +1 -1
  13. package/dist/core/signed-release.d.ts +33 -0
  14. package/dist/core/signed-release.js +121 -0
  15. package/dist/core/update-check.d.ts +1 -1
  16. package/dist/core/update-check.js +15 -14
  17. package/dist/core/vibe-modes.js +13 -13
  18. package/dist/distribution/install.d.ts +1 -0
  19. package/dist/distribution/install.js +31 -0
  20. package/dist/install.mjs +271 -0
  21. package/dist/node-runtime.json +13 -0
  22. package/dist/providers/oauth-callback-server.js +32 -32
  23. package/dist/tools/capabilities.js +30 -30
  24. package/dist/tools/computer-capture.js +50 -50
  25. package/dist/tools/program.js +82 -82
  26. package/dist/tui/i18n-catalog.js +16 -0
  27. package/dist/tui/quick-help.js +31 -31
  28. package/dist/tui/repl.js +5 -5
  29. package/dist/tui/update-prompt.js +1 -0
  30. package/dist/vascend/code-events.js +9 -9
  31. package/dist/vascend/completeness.js +31 -31
  32. package/dist/vascend/context-fabric.js +16 -16
  33. package/dist/vascend/contract-memory.js +30 -30
  34. package/dist/vascend/council.js +82 -82
  35. package/dist/vascend/decision-promotion.js +23 -23
  36. package/dist/vascend/granular-kingdom.js +17 -17
  37. package/dist/vascend/granular-launcher.js +2 -2
  38. package/dist/vascend/kingdom-kpi.js +19 -19
  39. package/dist/vascend/kingdom-run.js +6 -6
  40. package/dist/vascend/kingdom.js +56 -56
  41. package/dist/vascend/mandate.js +21 -21
  42. package/dist/vascend/objective-revision.js +34 -34
  43. package/dist/vascend/patch-plan.js +22 -22
  44. package/dist/vascend/requirements.js +29 -29
  45. package/dist/vascend/reviewer.js +26 -26
  46. package/dist/vascend/room-memory.js +25 -25
  47. package/dist/vascend/runtime.js +44 -44
  48. package/dist/vascend/self-improve.js +39 -39
  49. package/dist/vascend/test-failure-memory.js +9 -9
  50. package/dist/vascend/vision.js +18 -18
  51. package/dist/vendor/computer-use/README.md +7 -7
  52. package/dist/vendor/computer-use/computer-desktop.js +211 -211
  53. package/dist/vendor/computer-use/computer-observe.js +315 -315
  54. package/dist/vendor/computer-use/computer-scripts.js +1230 -1230
  55. package/dist/vendor/computer-use/computer-tools.js +2136 -2136
  56. package/dist/vendor/computer-use/package.json +1 -1
  57. package/dist/vendor/computer-use/rac/atomic-input.js +91 -91
  58. package/dist/vendor/computer-use/rac/grammatica.js +101 -101
  59. package/dist/vendor/computer-use/rac/parser.js +185 -185
  60. package/dist/vendor/computer-use/rac/protocol.js +136 -136
  61. package/install.ps1 +374 -30
  62. package/install.sh +356 -43
  63. package/package.json +2 -2
  64. package/dist/core/kingdoms-mode-state.d.ts +0 -24
  65. package/dist/core/kingdoms-mode-state.js +0 -139
  66. package/dist/text-sanitize.js +0 -28
package/install.sh CHANGED
@@ -1,50 +1,363 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
-
4
- # Public entry point: https://cdn.jsdelivr.net/npm/@vascend/talos@latest/install.sh
5
- main() {
6
- if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
7
- printf '%s\n' 'Talos requires Node.js 20 or newer (including npm). Install it from https://nodejs.org and run this command again.' >&2
8
- return 1
9
- fi
10
- if ! node -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 20 ? 0 : 1)'; then
11
- printf '%s\n' 'Please update Node.js to version 20 or newer before installing Talos.' >&2
12
- return 1
3
+ # Generated by scripts/build-installers.mjs. Public entry: https://vascend.it/install
4
+ # The version and hashes below are generated from the reviewed node-runtime.json.
5
+ talos_prepare_node() {
6
+ talos_runtime_bin=""
7
+ if [[ "${TALOS_FORCE_BUNDLED_NODE:-0}" != 1 ]] && command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then
8
+ if node -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 22 ? 0 : 1)' >/dev/null 2>&1 && npm --version >/dev/null 2>&1; then return; fi
13
9
  fi
14
- local version="${TALOS_VERSION:-latest}"
15
- if [[ "$version" != latest && ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
16
- printf '%s\n' 'TALOS_VERSION must be latest or a stable version such as 0.1.0.' >&2
17
- return 1
10
+ local platform arch digest runtime_base runtime_dir
11
+ case "$(uname -s)" in Darwin) platform=darwin ;; Linux) platform=linux ;; *) echo 'Use the PowerShell installer on Windows.' >&2; return 1 ;; esac
12
+ case "$(uname -m)" in x86_64|amd64) arch=x64 ;; arm64|aarch64) arch=arm64 ;; *) echo 'This CPU is not supported by the automatic Node installer.' >&2; return 1 ;; esac
13
+ if [[ "$platform" == linux ]] && ldd --version 2>&1 | grep -qi musl; then
14
+ if [[ "$arch" != x64 ]]; then echo 'Automatic Node installation on musl currently supports x64 only.' >&2; return 1; fi
15
+ arch=x64-musl
18
16
  fi
19
- local install_dir="${TALOS_INSTALL_DIR:-$HOME/.local}"
20
- local bin_dir="$install_dir/bin"
21
- printf 'Installing Talos %s in %s\n' "$version" "$install_dir"
22
- npm install --global --prefix "$install_dir" --registry=https://registry.npmjs.org "@vascend/talos@$version"
23
- "$bin_dir/talos" --version
24
- case ":$PATH:" in
25
- *":$bin_dir:"*) ;;
26
- *)
27
- local profile=""
28
- case "${SHELL:-}" in
29
- */zsh) profile="$HOME/.zshrc" ;;
30
- */bash)
31
- if [[ "$(uname -s)" == Darwin ]]; then profile="$HOME/.bash_profile"; else profile="$HOME/.bashrc"; fi
32
- ;;
33
- esac
34
- if [[ -n "$profile" && "${TALOS_NO_MODIFY_PATH:-0}" != 1 ]]; then
35
- local path_line
36
- printf -v path_line 'export PATH=%q:"$PATH"' "$bin_dir"
37
- if ! grep -Fqx "$path_line" "$profile" 2>/dev/null; then
38
- printf '\n# Vascend Talos\n%s\n' "$path_line" >> "$profile"
39
- fi
40
- printf 'Open a new terminal, then run: talos\n'
41
- else
42
- printf 'Add %s to your PATH, then run: talos\n' "$bin_dir"
43
- fi
44
- printf 'To start immediately: %q\n' "$bin_dir/talos"
45
- ;;
17
+ case "$platform-$arch" in
18
+ darwin-arm64) digest=bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057 ;;
19
+ darwin-x64) digest=1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097 ;;
20
+ linux-arm64) digest=724282c3b43aec998aa9527380465b45d229e021b58035f5f4f63095eabfe5d5 ;;
21
+ linux-x64) digest=6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff ;;
22
+ linux-x64-musl) digest=3d63405fc65a0d2d2976c1f0bc2fd27bb0bd07212469e705aac3f03ae5ab4c9c ;;
23
+ *) echo 'Unsupported Node platform.' >&2; return 1 ;;
46
24
  esac
47
- printf '%s\n' 'Talos is ready. Run talos to sign in.'
25
+ runtime_base="${TALOS_RUNTIME_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/vascend/runtime}"
26
+ if [[ "$runtime_base" == *:* || "$runtime_base" == *$'\n'* || "$runtime_base" == *$'\r'* ]]; then echo 'Invalid Node runtime directory.' >&2; return 1; fi
27
+ mkdir -p "$runtime_base"
28
+ runtime_base="$(cd -- "$runtime_base" && pwd -P)"
29
+ runtime_dir="$runtime_base/node-v24.21.0-$platform-$arch"
30
+ if [[ ! -d "$runtime_dir" ]]; then
31
+ command -v curl >/dev/null 2>&1 && command -v tar >/dev/null 2>&1 || { echo 'curl and tar are required.' >&2; return 1; }
32
+ mkdir -p "$runtime_base"
33
+ (
34
+ set -e
35
+ umask 077
36
+ stage=$(mktemp -d "$runtime_base/.node-download.XXXXXX")
37
+ trap 'rm -rf -- "$stage"' EXIT
38
+ archive="node-v24.21.0-$platform-$arch.tar.gz"
39
+ printf 'Installing Node.js 24.21.0 with npm for Talos...\n'
40
+ curl --proto '=https' --tlsv1.2 -fS --connect-timeout 15 --max-time 300 "https://nodejs.org/dist/v24.21.0/$archive" -o "$stage/node.tar.gz"
41
+ if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$stage/node.tar.gz");
42
+ elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$stage/node.tar.gz");
43
+ else echo 'A SHA-256 verifier (sha256sum or shasum) is required.' >&2; exit 1; fi
44
+ [[ "${actual%% *}" == "$digest" ]] || { echo 'Node checksum mismatch. Installation refused.' >&2; exit 1; }
45
+ tar -xzf "$stage/node.tar.gz" -C "$stage"
46
+ # No replacement of an existing runtime, even when two installs overlap.
47
+ [[ ! -e "$runtime_dir" ]] || { echo 'The Node runtime already exists. Run the installer again.' >&2; exit 1; }
48
+ mv "$stage/node-v24.21.0-$platform-$arch" "$runtime_dir"
49
+ ) || return 1
50
+ fi
51
+ talos_runtime_bin="$runtime_dir/bin"
52
+ export PATH="$talos_runtime_bin:$PATH"
53
+ [[ "$(node --version)" == v24.21.0 ]] && npm --version >/dev/null || { echo 'The managed Node runtime is incomplete.' >&2; return 1; }
54
+ }
55
+
56
+ talos_configure_path() {
57
+ local bin_dir="$TALOS_INSTALL_DIR/bin" install_path profile="" path_line
58
+ install_path="$bin_dir"
59
+ if [[ -n "$talos_runtime_bin" ]]; then install_path="$bin_dir:$talos_runtime_bin"; fi
60
+ export PATH="$install_path:$PATH"
61
+ if [[ "$(PATH="$talos_original_path" command -v talos || true)" == "$bin_dir/talos" ]] && { [[ -z "$talos_runtime_bin" ]] || [[ "$(PATH="$talos_original_path" command -v node || true)" == "$talos_runtime_bin/node" ]]; }; then
62
+ echo 'Talos is ready. Run: talos'
63
+ return
64
+ fi
65
+ if [[ "${TALOS_NO_MODIFY_PATH:-0}" != 1 ]]; then
66
+ case "${SHELL:-}" in
67
+ */zsh) profile="${ZDOTDIR:-$HOME}/.zshrc" ;;
68
+ */bash) if [[ "$(uname -s)" == Darwin ]]; then profile="$HOME/.bash_profile"; else profile="$HOME/.bashrc"; fi ;;
69
+ */fish)
70
+ "$SHELL" --no-config -c 'fish_add_path --universal --prepend $argv' "$bin_dir" "${talos_runtime_bin:-$bin_dir}"
71
+ echo 'Open a new terminal, then run: talos'
72
+ return ;;
73
+ esac
74
+ if [[ -n "$profile" ]]; then
75
+ printf -v path_line 'export PATH=%q:"$PATH"' "$install_path"
76
+ if ! grep -Fqx "$path_line" "$profile" 2>/dev/null; then printf '\n# Vascend Talos\n%s\n' "$path_line" >> "$profile"; fi
77
+ # curl | bash runs in a child shell and cannot modify its parent's PATH.
78
+ echo 'Open a new terminal, then run: talos'
79
+ printf 'Or activate it in this terminal: source %q\n' "$profile"
80
+ return
81
+ fi
82
+ fi
83
+ printf 'Add this to your shell PATH, then run talos: export PATH=%q:"$PATH"\n' "$install_path"
84
+ }
85
+
86
+ talos_original_path="$PATH"
87
+ talos_prepare_node
88
+ export TALOS_INSTALL_DIR="$(node -e 'const p=require("node:path"); console.log(p.resolve(process.env.TALOS_INSTALL_DIR || p.join(require("node:os").homedir(), ".local")));')"
89
+ node --input-type=module <<'TALOS_NODE_INSTALLER'
90
+ // src/distribution/install.ts
91
+ import { join as join4 } from "node:path";
92
+ import { homedir } from "node:os";
93
+
94
+ // src/core/signed-release.ts
95
+ import { createHash, createPublicKey, verify } from "node:crypto";
96
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
97
+ import { tmpdir } from "node:os";
98
+ import { join } from "node:path";
99
+ var TALOS_DOWNLOAD_ORIGIN = "https://vascend.it";
100
+ var STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
101
+ function stableVersion(value) {
102
+ return typeof value === "string" && value.length <= 40 && STABLE_VERSION.exec(value)?.[0] === value;
103
+ }
104
+ function verifyRelease(bytes, registryKeys, options = {}) {
105
+ if (bytes.length > 64 * 1024)
106
+ throw new Error("Release metadata is too large.");
107
+ const data = JSON.parse(Buffer.from(bytes).toString("utf8"));
108
+ if (!data || data.name !== "@vascend/talos" || !stableVersion(data.version) || options.version !== undefined && data.version !== options.version)
109
+ throw new Error("Unexpected release version.");
110
+ if (typeof data.dist?.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(data.dist.integrity) || data.dist.integrity.length !== 95 || !Array.isArray(data.dist.signatures) || data.dist.signatures.length > 10)
111
+ throw new Error("Invalid npm release signature.");
112
+ const keys = registryKeys?.keys;
113
+ if (!Array.isArray(keys) || keys.length > 30)
114
+ throw new Error("Invalid npm signing keys.");
115
+ const payload = Buffer.from(`${data.name}@${data.version}:${data.dist.integrity}`);
116
+ for (const signature of data.dist.signatures) {
117
+ if (typeof signature?.sig !== "string" || signature.sig.length > 256 || typeof signature.keyid !== "string")
118
+ continue;
119
+ for (const trusted of keys) {
120
+ if (!trusted || trusted.keyid !== signature.keyid || trusted.keytype !== "ecdsa-sha2-nistp256" || trusted.scheme !== "ecdsa-sha2-nistp256" || typeof trusted.key !== "string" || trusted.key.length > 512 || trusted.expires !== null && !(Date.parse(trusted.expires) > (options.now ?? Date.now())))
121
+ continue;
122
+ try {
123
+ const key = createPublicKey({ key: Buffer.from(trusted.key, "base64"), format: "der", type: "spki" });
124
+ if (key.asymmetricKeyType === "ec" && key.asymmetricKeyDetails?.namedCurve === "prime256v1" && verify("sha256", payload, key, Buffer.from(signature.sig, "base64")))
125
+ return data;
126
+ } catch {}
127
+ }
128
+ }
129
+ throw new Error("The npm registry signature is invalid.");
130
+ }
131
+ function verifyPackage(bytes, release) {
132
+ if (`sha512-${createHash("sha512").update(bytes).digest("base64")}` !== release.dist.integrity) {
133
+ throw new Error("Release checksum does not match. Installation refused.");
134
+ }
135
+ }
136
+ async function fetchReleaseBytes(url, limit, options = {}) {
137
+ const controller = new AbortController;
138
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 20000);
139
+ try {
140
+ const response = await (options.fetch ?? fetch)(url, { redirect: "error", signal: controller.signal, headers: { Accept: "application/octet-stream" } });
141
+ if (!response.ok || !response.body) {
142
+ await response.body?.cancel();
143
+ throw new Error("Release download is unavailable.");
144
+ }
145
+ if (Number(response.headers.get("content-length")) > limit) {
146
+ await response.body.cancel();
147
+ throw new Error("Release download is too large.");
148
+ }
149
+ const reader = response.body.getReader();
150
+ const chunks = [];
151
+ let length = 0;
152
+ try {
153
+ for (;; ) {
154
+ const { done, value } = await reader.read();
155
+ if (done)
156
+ break;
157
+ length += value.length;
158
+ if (length > limit)
159
+ throw new Error("Release download is too large.");
160
+ chunks.push(value);
161
+ }
162
+ } finally {
163
+ await reader.cancel();
164
+ }
165
+ return Buffer.concat(chunks);
166
+ } finally {
167
+ clearTimeout(timer);
168
+ }
169
+ }
170
+ async function fetchSignedRelease(version = "latest", options = {}) {
171
+ if (version !== "latest" && !stableVersion(version))
172
+ throw new Error("Invalid Talos version.");
173
+ const [metadata, keys] = await Promise.all([
174
+ fetchReleaseBytes(`https://registry.npmjs.org/@vascend%2ftalos/${version}`, 64 * 1024, options),
175
+ fetchReleaseBytes("https://registry.npmjs.org/-/npm/v1/keys", 64 * 1024, options)
176
+ ]);
177
+ return verifyRelease(metadata, JSON.parse(keys.toString("utf8")), version === "latest" ? {} : { version });
178
+ }
179
+ async function downloadVerifiedPackage(version, options = {}) {
180
+ const release = await fetchSignedRelease(version, options);
181
+ const filename = `vascend-talos-${release.version}.tgz`;
182
+ let bytes;
183
+ try {
184
+ bytes = await fetchReleaseBytes(`${TALOS_DOWNLOAD_ORIGIN}/talos/releases/${release.version}/${filename}`, 32 * 1024 * 1024, options);
185
+ verifyPackage(bytes, release);
186
+ } catch {
187
+ bytes = await fetchReleaseBytes(`https://registry.npmjs.org/@vascend/talos/-/talos-${release.version}.tgz`, 32 * 1024 * 1024, options);
188
+ verifyPackage(bytes, release);
189
+ }
190
+ const directory = await mkdtemp(join(tmpdir(), "talos-verified-"));
191
+ const dispose = () => rm(directory, { recursive: true, force: true });
192
+ try {
193
+ const path = join(directory, filename);
194
+ await writeFile(path, bytes, { flag: "wx", mode: 384 });
195
+ return { path, dispose };
196
+ } catch (error) {
197
+ await dispose();
198
+ throw error;
199
+ }
200
+ }
201
+
202
+ // src/core/package-update.ts
203
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
204
+ import { delimiter, dirname as dirname2, join as join3 } from "node:path";
205
+
206
+ // src/core/update-process.ts
207
+ import { spawn } from "node:child_process";
208
+ var runUpdateCommand = (command) => new Promise((resolve, reject) => {
209
+ const child = spawn(command.command, [...command.args], {
210
+ cwd: command.cwd,
211
+ env: command.env,
212
+ shell: false,
213
+ windowsHide: true,
214
+ stdio: command.inherit ? "inherit" : ["ignore", "pipe", "pipe"],
215
+ timeout: command.timeoutMs
216
+ });
217
+ let output = "";
218
+ let failed = false;
219
+ const collect = (data) => {
220
+ output += data.toString();
221
+ if (output.length > 2000000) {
222
+ failed = true;
223
+ child.kill();
224
+ reject(new Error("Command output exceeded limit"));
225
+ }
226
+ };
227
+ child.stdout?.on("data", collect);
228
+ child.stderr?.resume();
229
+ child.on("error", reject);
230
+ child.on("close", (code, signal) => {
231
+ if (failed)
232
+ return;
233
+ if (code === 0 && !signal)
234
+ resolve(output.trim());
235
+ else
236
+ reject(new Error(`${command.command} exited with ${signal ?? code}`));
237
+ });
238
+ });
239
+
240
+ // src/core/cli-installation.ts
241
+ import { existsSync, lstatSync, readFileSync, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
242
+ import { dirname, join as join2, resolve } from "node:path";
243
+ function installationPrefix(value) {
244
+ const prefix = resolve(value);
245
+ if (/[\x00-\x1f\x7f]/.test(prefix) || prefix.includes(process.platform === "win32" ? ";" : ":")) {
246
+ throw new Error("The installation directory cannot be represented safely in PATH.");
247
+ }
248
+ return prefix;
249
+ }
250
+ function windowsCliShim() {
251
+ return `@echo off\r
252
+ "%~dp0.talos-runtime\\node.exe" "%~dp0node_modules\\@vascend\\talos\\dist\\index.js" %*\r
253
+ `;
254
+ }
255
+ function finalizeCliInstallation(root, version) {
256
+ const pkg = JSON.parse(readFileSync(join2(root, "package.json"), "utf8"));
257
+ if (pkg.name !== "@vascend/talos" || pkg.version !== version)
258
+ throw new Error("Installed Talos version differs from the requested release.");
259
+ if (process.platform !== "win32")
260
+ return;
261
+ const modules = dirname(dirname(root));
262
+ if (dirname(root).split(/[\\/]/).pop() !== "@vascend" || modules.split(/[\\/]/).pop() !== "node_modules")
263
+ return;
264
+ const prefix = dirname(modules);
265
+ const belongsToTalos = (file) => {
266
+ if (!existsSync(file))
267
+ return false;
268
+ const info = lstatSync(file);
269
+ return info.isFile() && !info.isSymbolicLink() && info.size < 16384 && /node_modules[\\/]@vascend[\\/]talos[\\/]dist[\\/]index\.js/i.test(readFileSync(file, "utf8"));
270
+ };
271
+ for (const alias of ["talos", "horizon"]) {
272
+ const cmd = join2(prefix, `${alias}.cmd`);
273
+ if (!belongsToTalos(cmd))
274
+ continue;
275
+ if (!process.versions.bun) {
276
+ const runtime = join2(prefix, ".talos-runtime");
277
+ if (existsSync(runtime)) {
278
+ if (!lstatSync(runtime).isSymbolicLink())
279
+ throw new Error("The Talos runtime link is occupied by a regular directory.");
280
+ if (realpathSync(runtime).toLowerCase() !== realpathSync(dirname(process.execPath)).toLowerCase())
281
+ unlinkSync(runtime);
282
+ }
283
+ if (!existsSync(runtime))
284
+ symlinkSync(dirname(process.execPath), runtime, "junction");
285
+ writeFileSync(cmd, windowsCliShim());
286
+ }
287
+ const ps1 = join2(prefix, `${alias}.ps1`);
288
+ if (belongsToTalos(ps1))
289
+ unlinkSync(ps1);
290
+ }
291
+ }
292
+
293
+ // src/core/package-update.ts
294
+ var REGISTRY = "https://registry.npmjs.org";
295
+ function managerCommand(manager, env) {
296
+ if (process.platform !== "win32")
297
+ return { command: manager, args: [] };
298
+ const path = Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
299
+ const directories = path.split(delimiter).filter(Boolean).map((directory) => directory.replace(/^"|"$/g, ""));
300
+ const node = process.versions.bun ? directories.map((directory) => join3(directory, "node.exe")).find((file) => existsSync2(file)) : process.execPath;
301
+ for (const directory of directories) {
302
+ const exe = join3(directory, `${manager}.exe`);
303
+ if (existsSync2(exe))
304
+ return { command: exe, args: [] };
305
+ const scripts = {
306
+ npm: ["npm/bin/npm-cli.js"],
307
+ pnpm: ["pnpm/bin/pnpm.cjs", "corepack/dist/pnpm.js"],
308
+ yarn: ["yarn/bin/yarn.js", "corepack/dist/yarn.js"]
309
+ };
310
+ if (manager !== "bun")
311
+ for (const script of scripts[manager]) {
312
+ const file = join3(directory, "node_modules", script);
313
+ if (node && existsSync2(file))
314
+ return { command: node, args: [file] };
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+ function packageUpdateArgs(manager, target, prefix) {
320
+ const security = ["--ignore-scripts", `--registry=${REGISTRY}`];
321
+ if (manager === "npm")
322
+ return ["install", "--global", target, ...security, "--no-audit", "--no-fund", ...prefix ? ["--prefix", prefix] : []];
323
+ if (manager === "yarn")
324
+ return ["global", "add", target, ...security];
325
+ return ["add", "--global", target, ...security];
326
+ }
327
+
328
+ // src/distribution/install.ts
329
+ async function install() {
330
+ if (Number(process.versions.node.split(".")[0]) < 20)
331
+ throw new Error("Talos requires Node.js 20 or newer, including npm: https://nodejs.org");
332
+ const npm = managerCommand("npm", process.env);
333
+ if (!npm)
334
+ throw new Error("npm is missing. Install Node.js with npm from https://nodejs.org");
335
+ const version = process.env.TALOS_VERSION || "latest";
336
+ const release = await fetchSignedRelease(version);
337
+ const prefix = installationPrefix(process.env.TALOS_INSTALL_DIR || (process.platform === "win32" ? join4(process.env.LOCALAPPDATA || join4(homedir(), "AppData", "Local"), "Programs", "Talos") : join4(homedir(), ".local")));
338
+ console.log(`Installing Talos ${release.version} in ${prefix}`);
339
+ const artifact = await downloadVerifiedPackage(release.version);
340
+ try {
341
+ await runUpdateCommand({
342
+ ...npm,
343
+ args: [...npm.args, ...packageUpdateArgs("npm", artifact.path, prefix)],
344
+ cwd: homedir(),
345
+ env: process.env,
346
+ inherit: true,
347
+ timeoutMs: 300000
348
+ });
349
+ const entry = join4(prefix, ...process.platform === "win32" ? [] : ["lib"], "node_modules", "@vascend", "talos", "dist", "index.js");
350
+ finalizeCliInstallation(join4(prefix, ...process.platform === "win32" ? [] : ["lib"], "node_modules", "@vascend", "talos"), release.version);
351
+ await runUpdateCommand({ command: process.execPath, args: [entry, "--version"], cwd: homedir(), env: process.env, inherit: true, timeoutMs: 1e4 });
352
+ console.log("Talos installed. npm registry signature and SHA-512 verified.");
353
+ } finally {
354
+ await artifact.dispose();
355
+ }
48
356
  }
357
+ install().catch((error) => {
358
+ console.error(error instanceof Error ? error.message : "Installation failed.");
359
+ process.exitCode = 1;
360
+ });
49
361
 
50
- main "$@"
362
+ TALOS_NODE_INSTALLER
363
+ talos_configure_path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vascend/talos",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Vascend Talos - extensible coding agent for your terminal",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "install:cli": "bun run build && bun link",
27
27
  "backend": "bun run src/backend/index.ts",
28
28
  "backend:dev": "bun --watch run src/backend/index.ts",
29
- "build": "tsc -p tsconfig.json && node scripts/copy-computer-use.mjs",
29
+ "build": "tsc -p tsconfig.json && node scripts/copy-computer-use.mjs && node scripts/build-installers.mjs",
30
30
  "typecheck": "tsc -p tsconfig.json --noEmit",
31
31
  "test": "node scripts/run-unit-tests.mjs",
32
32
  "test:unit": "node scripts/run-unit-tests.mjs",
@@ -1,24 +0,0 @@
1
- export declare const KINGDOMS_TRIGGER_KEYWORD = "ultracheck";
2
- export type KingdomsModeSource = "default" | "keyword" | "manual" | "stored";
3
- export interface KingdomsModeState {
4
- readonly enabled: boolean;
5
- readonly source: KingdomsModeSource;
6
- readonly updatedAt: string;
7
- }
8
- export declare const DEFAULT_KINGDOMS_MODE_STATE: KingdomsModeState;
9
- export interface KingdomsTriggerOptions {
10
- readonly detect?: (text: string) => boolean;
11
- readonly keyword?: string;
12
- }
13
- export declare function kingdomsModeFile(env: NodeJS.ProcessEnv): string;
14
- export declare function readKingdomsModeState(env: NodeJS.ProcessEnv): KingdomsModeState;
15
- export declare function writeKingdomsModeState(env: NodeJS.ProcessEnv, state: KingdomsModeState): KingdomsModeState;
16
- export declare function isKingdomsModeEnabled(state: KingdomsModeState): boolean;
17
- export declare function enableKingdomsMode(state: KingdomsModeState, source?: KingdomsModeSource, now?: () => string): KingdomsModeState;
18
- export declare function disableKingdomsMode(state: KingdomsModeState, source?: KingdomsModeSource, now?: () => string): KingdomsModeState;
19
- export declare function toggleKingdomsMode(state: KingdomsModeState, now?: () => string): KingdomsModeState;
20
- export declare function promptHasTrigger(text: string, opts?: KingdomsTriggerOptions): boolean;
21
- export declare function applyPromptTrigger(state: KingdomsModeState, prompt: string, opts?: KingdomsTriggerOptions, now?: () => string): KingdomsModeState;
22
- export declare function setKingdomsModeEnabled(env: NodeJS.ProcessEnv, enabled: boolean, source?: KingdomsModeSource): KingdomsModeState;
23
- export declare function toggleKingdomsModePersisted(env: NodeJS.ProcessEnv): KingdomsModeState;
24
- export declare function applyPromptTriggerPersisted(env: NodeJS.ProcessEnv, prompt: string, opts?: KingdomsTriggerOptions): KingdomsModeState;
@@ -1,139 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
4
- // Stato applicativo + toggle "Kingdoms Mode".
5
- //
6
- // La modalita' Kingdoms si ATTIVA quando il prompt dell'utente contiene la
7
- // parola chiave 'ultracheck' (vedi applyPromptTrigger). Questo modulo possiede
8
- // SOLO lo stato e le sue transizioni (abilitazione/disabilitazione/persistenza):
9
- // la RILEVAZIONE della keyword puo' arrivare da un detector esterno (peer
10
- // keyword-detector) passato come predicate opzionale; in sua assenza si usa un
11
- // matcher interno di default.
12
- //
13
- // Lo stato e' persistito come JSON in ~/.talos/agent/kingdoms-mode.json (stesso
14
- // pattern di trust.ts: directory di config overridabile via TALOS_CONFIG_DIR).
15
- // Tutte le transizioni sono IMMUTABILI: si crea un nuovo stato, non si muta.
16
- // Parola chiave che, presente nel prompt, attiva la modalita'.
17
- export const KINGDOMS_TRIGGER_KEYWORD = "ultracheck";
18
- // Stato iniziale: modalita' spenta.
19
- export const DEFAULT_KINGDOMS_MODE_STATE = {
20
- enabled: false,
21
- source: "default",
22
- updatedAt: "",
23
- };
24
- // Path del file di stato persistito. Override della dir via TALOS_CONFIG_DIR
25
- // (parita' con trust.ts), altrimenti ~/.talos/agent.
26
- export function kingdomsModeFile(env) {
27
- const override = env["TALOS_CONFIG_DIR"];
28
- const base = override ? override : join(homedir(), ".talos", "agent");
29
- return join(base, "kingdoms-mode.json");
30
- }
31
- function coerceSource(value) {
32
- return value === "keyword" || value === "manual" || value === "stored" || value === "default"
33
- ? value
34
- : "stored";
35
- }
36
- // Normalizza un oggetto sconosciuto in uno stato valido (difensivo verso JSON
37
- // corrotti o di versioni precedenti).
38
- function coerceState(raw, source) {
39
- if (typeof raw !== "object" || raw === null)
40
- return DEFAULT_KINGDOMS_MODE_STATE;
41
- const obj = raw;
42
- return {
43
- enabled: obj["enabled"] === true,
44
- source: coerceSource(obj["source"]),
45
- updatedAt: typeof obj["updatedAt"] === "string" ? obj["updatedAt"] : "",
46
- };
47
- }
48
- // Legge lo stato persistito. Ritorna lo stato di default se assente/illeggibile.
49
- export function readKingdomsModeState(env) {
50
- const file = kingdomsModeFile(env);
51
- if (!existsSync(file))
52
- return DEFAULT_KINGDOMS_MODE_STATE;
53
- try {
54
- const raw = JSON.parse(readFileSync(file, "utf8"));
55
- return coerceState(raw, "stored");
56
- }
57
- catch {
58
- return DEFAULT_KINGDOMS_MODE_STATE;
59
- }
60
- }
61
- // Persiste lo stato su disco (crea la directory se serve). Ritorna lo stato
62
- // scritto (per concatenare). Operazione idempotente.
63
- export function writeKingdomsModeState(env, state) {
64
- const file = kingdomsModeFile(env);
65
- mkdirSync(dirname(file), { recursive: true });
66
- writeFileSync(file, JSON.stringify(state, null, 2) + "\n", "utf8");
67
- return state;
68
- }
69
- // --- Query -----------------------------------------------------------------
70
- export function isKingdomsModeEnabled(state) {
71
- return state.enabled;
72
- }
73
- // --- Transizioni immutabili (puro stato, niente I/O) -----------------------
74
- function transition(state, enabled, source, now) {
75
- // Se non cambia nulla, ritorna lo stato invariato (idempotenza, nessun
76
- // aggiornamento spurio del timestamp).
77
- if (state.enabled === enabled)
78
- return state;
79
- return { enabled, source, updatedAt: now() };
80
- }
81
- const isoNow = () => new Date().toISOString();
82
- // Abilita la modalita'. `source` distingue keyword vs manuale (default manuale).
83
- export function enableKingdomsMode(state, source = "manual", now = isoNow) {
84
- return transition(state, true, source, now);
85
- }
86
- // Disabilita la modalita'.
87
- export function disableKingdomsMode(state, source = "manual", now = isoNow) {
88
- return transition(state, false, source, now);
89
- }
90
- // Inverte lo stato corrente (toggle esplicito).
91
- export function toggleKingdomsMode(state, now = isoNow) {
92
- return transition(state, !state.enabled, "manual", now);
93
- }
94
- // --- Trigger da keyword nel prompt -----------------------------------------
95
- // True se il testo contiene la keyword di attivazione. Usa il detector esterno
96
- // se fornito, altrimenti un confronto case-insensitive sulla parola.
97
- export function promptHasTrigger(text, opts = {}) {
98
- if (opts.detect)
99
- return opts.detect(text);
100
- const keyword = (opts.keyword ?? KINGDOMS_TRIGGER_KEYWORD).toLowerCase();
101
- if (!keyword)
102
- return false;
103
- return text.toLowerCase().includes(keyword);
104
- }
105
- // Applica il trigger al prompt: se il prompt contiene la keyword, ATTIVA la
106
- // modalita' (source="keyword"); altrimenti lascia lo stato invariato. Il
107
- // trigger non disattiva mai (la presenza/assenza della parola non spegne una
108
- // modalita' gia' accesa): la disattivazione e' un'azione esplicita.
109
- export function applyPromptTrigger(state, prompt, opts = {}, now = isoNow) {
110
- if (!promptHasTrigger(prompt, opts))
111
- return state;
112
- return enableKingdomsMode(state, "keyword", now);
113
- }
114
- // --- Helper con persistenza (I/O) ------------------------------------------
115
- // Comodi per i consumer (TUI/agent): leggono, transizionano e ripersistono.
116
- export function setKingdomsModeEnabled(env, enabled, source = "manual") {
117
- const current = readKingdomsModeState(env);
118
- const next = transition(current, enabled, source, isoNow);
119
- if (next === current)
120
- return current;
121
- return writeKingdomsModeState(env, next);
122
- }
123
- export function toggleKingdomsModePersisted(env) {
124
- const current = readKingdomsModeState(env);
125
- const next = toggleKingdomsMode(current);
126
- if (next === current)
127
- return current;
128
- return writeKingdomsModeState(env, next);
129
- }
130
- // Legge lo stato, applica il trigger sul prompt e — se cambia — lo persiste.
131
- // Ritorna lo stato risultante. Usalo nel flusso del turno per "accendere"
132
- // Kingdoms Mode appena l'utente scrive 'ultracheck'.
133
- export function applyPromptTriggerPersisted(env, prompt, opts = {}) {
134
- const current = readKingdomsModeState(env);
135
- const next = applyPromptTrigger(current, prompt, opts);
136
- if (next === current)
137
- return current;
138
- return writeKingdomsModeState(env, next);
139
- }
@@ -1,28 +0,0 @@
1
- // Sanitizzazione del testo che entra in MEMORIA/CONTEXT (metodo Danilov).
2
- //
3
- // Output di tool (bash, web_fetch, ...), incolla da terminale e contenuti
4
- // esterni possono trascinare sequenze di escape ANSI (es. \x1b[31m), OSC
5
- // (hyperlink), e byte di controllo non-stampabili. Se finiscono nei title/
6
- // terms/body delle stanze o nei summary degli eventi, vengono iniettati TALI
7
- // E QUALI nel memory packet -> system prompt: sono i "caratteri strani" che
8
- // si leggono nel context e che lo sporcano (e gonfiano inutilmente i token).
9
- //
10
- // Questa funzione e' PURA e DETERMINISTICA: strip ANSI/OSC + caratteri di
11
- // controllo, preservando solo \n e \t (la collassatura degli spazi resta agli
12
- // helper oneLine/escapeValue gia' esistenti).
13
- // CSI: ESC [ ... lettera finale. OSC: ESC ] ... (BEL | ESC \).
14
- // Singoli ESC + intro a 1/2 byte (es. ESC ( B, ESC =) inclusi.
15
- const ANSI_RE =
16
- // eslint-disable-next-line no-control-regex
17
- /[\u001b\u009b][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PR-TZcf-ntqry=><~]|[\u001b\u009b][@-Z\\-_]/g;
18
- // Caratteri di controllo C0/C1 non-stampabili, ESCLUSI \t (0x09) e \n (0x0a).
19
- // Anche \r (0x0d) viene rimosso: lo normalizziamo via newline a monte.
20
- // eslint-disable-next-line no-control-regex
21
- const CONTROL_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
22
- // Rimuove escape ANSI/OSC e byte di controllo non-stampabili dal testo.
23
- // Mantiene \n e \t. Non collassa gli spazi (lo fanno oneLine/escapeValue).
24
- export function sanitizeText(value) {
25
- if (!value)
26
- return "";
27
- return value.replace(ANSI_RE, "").replace(CONTROL_RE, "");
28
- }