zelari-code 1.23.0 → 1.25.0

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 (46) hide show
  1. package/README.md +3 -0
  2. package/bin/zelari-code.js +44 -12
  3. package/dist/cli/atMentions.js +179 -0
  4. package/dist/cli/atMentions.js.map +1 -0
  5. package/dist/cli/companion/config.js +172 -0
  6. package/dist/cli/companion/config.js.map +1 -0
  7. package/dist/cli/companion/runManager.js +250 -0
  8. package/dist/cli/companion/runManager.js.map +1 -0
  9. package/dist/cli/companion/serve.js +376 -0
  10. package/dist/cli/companion/serve.js.map +1 -0
  11. package/dist/cli/components/InputBar.js +1 -1
  12. package/dist/cli/components/InputBar.js.map +1 -1
  13. package/dist/cli/generateSkillFromUrl.js +236 -0
  14. package/dist/cli/generateSkillFromUrl.js.map +1 -0
  15. package/dist/cli/headless.js +6 -0
  16. package/dist/cli/headless.js.map +1 -1
  17. package/dist/cli/hooks/useSlashDispatch.js +19 -6
  18. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  19. package/dist/cli/main.bundled.js +2617 -769
  20. package/dist/cli/main.bundled.js.map +4 -4
  21. package/dist/cli/main.js +155 -0
  22. package/dist/cli/main.js.map +1 -1
  23. package/dist/cli/missionSlice.js +19 -0
  24. package/dist/cli/missionSlice.js.map +1 -1
  25. package/dist/cli/provider/openai-compatible.js +164 -41
  26. package/dist/cli/provider/openai-compatible.js.map +1 -1
  27. package/dist/cli/runHeadless.js +33 -0
  28. package/dist/cli/runHeadless.js.map +1 -1
  29. package/dist/cli/skillCategories.js +15 -0
  30. package/dist/cli/skillCategories.js.map +1 -0
  31. package/dist/cli/skillConfigIo.js +285 -0
  32. package/dist/cli/skillConfigIo.js.map +1 -0
  33. package/dist/cli/slashCommands.js +14 -5
  34. package/dist/cli/slashCommands.js.map +1 -1
  35. package/dist/cli/slashHandlers/skills.js +30 -0
  36. package/dist/cli/slashHandlers/skills.js.map +1 -1
  37. package/dist/cli/traceStore.js +84 -0
  38. package/dist/cli/traceStore.js.map +1 -0
  39. package/dist/cli/triggerLock.js +79 -0
  40. package/dist/cli/triggerLock.js.map +1 -0
  41. package/dist/cli/zelariMission.js +77 -0
  42. package/dist/cli/zelariMission.js.map +1 -1
  43. package/dist/cli/zelariMission.test.js +44 -0
  44. package/dist/cli/zelariMission.test.js.map +1 -0
  45. package/package.json +4 -3
  46. package/scripts/start-companion-serve.ps1 +43 -0
package/README.md CHANGED
@@ -280,6 +280,9 @@ Disable auto-check: `ANATHEMA_DEV=1 zelari-code`
280
280
  - 🔌 **MCP** — external MCP servers via `.zelari/mcp.json` or `~/.zelari-code/mcp.json`; Desktop **Extensions** store for one-click install
281
281
  - 🔐 **SSH targets** — configure hosts for deploy/monitor (password, agent, or key); tools `ssh_status` / `ssh_run` with allowlist; `ZELARI_SSH=0` kill switch
282
282
  - 🖥️ **Zelari Desktop** — Tauri shell with chat UI, project tree, Settings, dual updates (app vs npm CLI), multi-turn history, optional overlay HUD
283
+ - 📱 **Companion host + Android** — `zelari-code serve` (SSE over Tailscale/LAN) + Settings → Start companion serve; MVP app in `apps/companion-android/`
284
+ - 🧩 **Skills store (Desktop)** — create/remove user & project `SKILL.md`; import skill from URL with the selected model; `/skills` picker in TUI
285
+ - 📎 **@-tag paths** — tag workspace files/folders in Desktop composer and CLI prompts
283
286
  - ◇◆ **Plan / build phase** — `/plan` explores without project writes; `/build` implements with full tools (independent of agent/council/zelari mode)
284
287
  - 🆕 **Self-update** — `/update` slash command + silent registry check on startup
285
288
 
@@ -33,25 +33,57 @@ const compiledMain = path.join(pkgRoot, 'dist', 'cli', 'main.js');
33
33
  const compiledApp = path.join(pkgRoot, 'dist', 'cli', 'app.js');
34
34
  const tsSource = path.join(pkgRoot, 'src', 'cli', 'main.ts');
35
35
 
36
- if (existsSync(compiledBundled)) {
37
- // Production path: esbuild-bundled self-contained ESM (includes .tsx)
38
- await import(toImportURL(compiledBundled));
39
- } else if (existsSync(compiledMain) && existsSync(compiledApp)) {
40
- // Production path (legacy): tsc-only emit (only .ts, requires .tsx to be absent)
41
- await import(toImportURL(compiledMain));
42
- } else if (existsSync(tsSource)) {
43
- // Dev/source install path: use tsx
36
+ /**
37
+ * Prefer TypeScript source only when explicitly requested.
38
+ *
39
+ * Do NOT key off ANATHEMA_DEV: Desktop always sets ANATHEMA_DEV=1 on CLI
40
+ * spawns (to skip background update checks). Forcing tsx there breaks
41
+ * get_app_config / --print-config at startup with "Failed to load provider
42
+ * config" when tsx is unavailable or cwd/node resolution differs.
43
+ *
44
+ * Stale-bundle risk (new flags missing) is handled by `desktop:dev` running
45
+ * `build:cli` first. Override with ZELARI_CLI_SOURCE=1 for pure source runs.
46
+ */
47
+ function preferSourceOverDist() {
48
+ return (
49
+ process.env.ZELARI_CLI_SOURCE === '1' ||
50
+ process.env.ZELARI_CLI_SOURCE === 'true'
51
+ );
52
+ }
53
+
54
+ async function loadFromSource() {
44
55
  try {
45
56
  require('tsx/esm/api');
46
57
  await import(toImportURL(tsSource));
47
- } catch {
58
+ } catch (err) {
59
+ // Fall back to dist if present — better than hard-fail for Desktop.
60
+ if (existsSync(compiledBundled)) {
61
+ console.error(
62
+ '[zelari-code] tsx source load failed; falling back to dist bundle.\n' +
63
+ (err instanceof Error ? err.message : String(err)),
64
+ );
65
+ await import(toImportURL(compiledBundled));
66
+ return;
67
+ }
48
68
  console.error(
49
- '[zelari-code] Neither compiled dist nor tsx runtime found.\n' +
50
- 'Production install: run `npm run build:cli` before installing (requires esbuild + tsx).\n' +
51
- 'Source install: install `tsx` (npm i -g tsx) and run from source.',
69
+ '[zelari-code] Failed to load TypeScript source via tsx.\n' +
70
+ 'Run `npm run build:cli` to use dist, or install tsx.\n' +
71
+ (err instanceof Error ? err.message : String(err)),
52
72
  );
53
73
  process.exit(1);
54
74
  }
75
+ }
76
+
77
+ if (preferSourceOverDist() && existsSync(tsSource)) {
78
+ await loadFromSource();
79
+ } else if (existsSync(compiledBundled)) {
80
+ // Production / Desktop path: esbuild-bundled self-contained ESM
81
+ await import(toImportURL(compiledBundled));
82
+ } else if (existsSync(compiledMain) && existsSync(compiledApp)) {
83
+ // Production path (legacy): tsc-only emit
84
+ await import(toImportURL(compiledMain));
85
+ } else if (existsSync(tsSource)) {
86
+ await loadFromSource();
55
87
  } else {
56
88
  console.error(
57
89
  '[zelari-code] Cannot locate CLI entrypoint.\n' +
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Expand @path tags in a user prompt into attached file/dir context.
3
+ *
4
+ * Tokens look like `@src/cli/main.ts` or `@apps/desktop` (relative to cwd).
5
+ * Skips email-like tokens (`user@host`) and pure `@` noise.
6
+ *
7
+ * Used by CLI free-form prompts and headless tasks so Desktop/CLI share
8
+ * the same @-tag semantics.
9
+ */
10
+ import { existsSync, readFileSync, statSync } from 'node:fs';
11
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
12
+ const ATTACH_TEXT_MAX = 48_000;
13
+ const ATTACH_FILE_MAX_BYTES = 512_000;
14
+ /** Match @path tokens: @foo, @foo/bar, @./x, @../y — not emails (word@word). */
15
+ const AT_PATH_RE = /(^|[\s([{])@((?:\.{1,2}\/)?[A-Za-z0-9_.+-]+(?:[\\/][A-Za-z0-9_.+-]+)*)/g;
16
+ function isProbablyText(name, head) {
17
+ if (/\.(txt|md|markdown|json|jsonc|ts|tsx|js|jsx|mjs|cjs|css|scss|html|htm|xml|yml|yaml|toml|ini|cfg|conf|rs|go|py|java|kt|swift|c|cc|cpp|h|hpp|cs|rb|php|sh|bash|zsh|ps1|sql|graphql|env|gitignore|dockerfile|makefile|cmake|lock|svg)$/i.test(name)) {
18
+ return true;
19
+ }
20
+ return !head.includes('\0') && /[\x09\x0a\x0d\x20-\x7e]/.test(head.slice(0, 200));
21
+ }
22
+ function underRoot(abs, root) {
23
+ const a = resolve(abs);
24
+ const r = resolve(root);
25
+ const prefix = r.endsWith(sep) ? r : r + sep;
26
+ return a === r || a.startsWith(prefix);
27
+ }
28
+ /**
29
+ * Extract unique @path tokens from text (order preserved).
30
+ */
31
+ export function extractAtMentions(text) {
32
+ const out = [];
33
+ const seen = new Set();
34
+ AT_PATH_RE.lastIndex = 0;
35
+ let m;
36
+ while ((m = AT_PATH_RE.exec(text)) !== null) {
37
+ const token = (m[2] ?? '').trim();
38
+ if (!token || token.includes('@'))
39
+ continue;
40
+ // Skip email-like leftovers: if the char before @ was word char, the
41
+ // regex already requires whitespace/start — good.
42
+ const key = token.replace(/\\/g, '/').toLowerCase();
43
+ if (seen.has(key))
44
+ continue;
45
+ seen.add(key);
46
+ out.push(token);
47
+ }
48
+ return out;
49
+ }
50
+ function resolveMention(token, cwd) {
51
+ const abs = isAbsolute(token) ? resolve(token) : resolve(cwd, token);
52
+ if (!underRoot(abs, cwd) && !isAbsolute(token)) {
53
+ // relative path that escapes cwd after resolve
54
+ if (!underRoot(abs, cwd)) {
55
+ return {
56
+ raw: token,
57
+ path: token,
58
+ absolute: abs,
59
+ isDir: false,
60
+ note: 'outside project root — skipped',
61
+ };
62
+ }
63
+ }
64
+ if (isAbsolute(token) && !underRoot(abs, cwd)) {
65
+ return {
66
+ raw: token,
67
+ path: token,
68
+ absolute: abs,
69
+ isDir: false,
70
+ note: 'outside project root — skipped',
71
+ };
72
+ }
73
+ if (!existsSync(abs)) {
74
+ return {
75
+ raw: token,
76
+ path: token,
77
+ absolute: abs,
78
+ isDir: false,
79
+ note: 'not found',
80
+ };
81
+ }
82
+ let st;
83
+ try {
84
+ st = statSync(abs);
85
+ }
86
+ catch {
87
+ return {
88
+ raw: token,
89
+ path: token,
90
+ absolute: abs,
91
+ isDir: false,
92
+ note: 'unreadable',
93
+ };
94
+ }
95
+ const rel = relative(cwd, abs).replace(/\\/g, '/') || '.';
96
+ if (st.isDirectory()) {
97
+ return {
98
+ raw: token,
99
+ path: rel,
100
+ absolute: abs,
101
+ isDir: true,
102
+ note: 'directory — list/read with tools as needed',
103
+ };
104
+ }
105
+ if (st.size > ATTACH_FILE_MAX_BYTES) {
106
+ return {
107
+ raw: token,
108
+ path: rel,
109
+ absolute: abs,
110
+ isDir: false,
111
+ note: `too large (${Math.round(st.size / 1024)} KB) — path only`,
112
+ };
113
+ }
114
+ try {
115
+ const buf = readFileSync(abs);
116
+ const head = buf.subarray(0, 800).toString('utf8');
117
+ if (!isProbablyText(abs, head)) {
118
+ return {
119
+ raw: token,
120
+ path: rel,
121
+ absolute: abs,
122
+ isDir: false,
123
+ note: 'binary — path only',
124
+ };
125
+ }
126
+ let text = buf.toString('utf8');
127
+ if (text.charCodeAt(0) === 0xfeff)
128
+ text = text.slice(1);
129
+ if (text.length > ATTACH_TEXT_MAX) {
130
+ text =
131
+ text.slice(0, ATTACH_TEXT_MAX) +
132
+ `\n\n… [truncated, ${text.length - ATTACH_TEXT_MAX} more chars]`;
133
+ }
134
+ return { raw: token, path: rel, absolute: abs, isDir: false, text };
135
+ }
136
+ catch (err) {
137
+ return {
138
+ raw: token,
139
+ path: rel,
140
+ absolute: abs,
141
+ isDir: false,
142
+ note: err instanceof Error ? err.message : 'read failed',
143
+ };
144
+ }
145
+ }
146
+ /**
147
+ * Expand @path tags: keep user text, append [Tagged paths] blocks with
148
+ * content when available (mirrors Desktop attach format).
149
+ */
150
+ export function expandAtMentions(userText, cwd = process.cwd()) {
151
+ const tokens = extractAtMentions(userText);
152
+ if (tokens.length === 0) {
153
+ return { text: userText, hits: [] };
154
+ }
155
+ const hits = [];
156
+ for (const t of tokens) {
157
+ const hit = resolveMention(t, cwd);
158
+ if (hit)
159
+ hits.push(hit);
160
+ }
161
+ if (hits.length === 0) {
162
+ return { text: userText, hits: [] };
163
+ }
164
+ const blocks = hits.map((h) => {
165
+ const label = h.path || h.raw;
166
+ if (h.text != null && h.text.length > 0) {
167
+ return `--- File: ${label} ---\n${h.text}\n--- End file ---`;
168
+ }
169
+ const extra = h.note ? ` (${h.note})` : '';
170
+ return `--- ${h.isDir ? 'Dir' : 'File'}: ${label}${extra} ---`;
171
+ });
172
+ const text = `${userText.trim()}\n\n[Tagged paths]\n${blocks.join('\n\n')}`;
173
+ return { text, hits };
174
+ }
175
+ /** True if the prompt contains at least one resolvable @path-looking token. */
176
+ export function hasAtMentions(text) {
177
+ return extractAtMentions(text).length > 0;
178
+ }
179
+ //# sourceMappingURL=atMentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atMentions.js","sourceRoot":"","sources":["../../src/cli/atMentions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Companion host config + token (for Android / remote clients over Tailscale).
3
+ *
4
+ * Files under ~/.zelari-code/:
5
+ * companion.json — projects allowlist, bind/port defaults
6
+ * companion.token — bearer token (created on first serve if missing)
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
12
+ export const DEFAULT_COMPANION_PORT = 7421;
13
+ export const DEFAULT_COMPANION_BIND = '127.0.0.1';
14
+ export function getZelariHome() {
15
+ return join(homedir(), '.zelari-code');
16
+ }
17
+ export function getCompanionConfigPath() {
18
+ return join(getZelariHome(), 'companion.json');
19
+ }
20
+ export function getCompanionTokenPath() {
21
+ return join(getZelariHome(), 'companion.token');
22
+ }
23
+ function ensureHome() {
24
+ const home = getZelariHome();
25
+ if (!existsSync(home)) {
26
+ mkdirSync(home, { recursive: true });
27
+ }
28
+ }
29
+ export function loadCompanionConfig() {
30
+ const path = getCompanionConfigPath();
31
+ if (!existsSync(path)) {
32
+ return { projects: [] };
33
+ }
34
+ try {
35
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
36
+ const projects = Array.isArray(raw.projects)
37
+ ? raw.projects
38
+ .filter((p) => p &&
39
+ typeof p.path === 'string' &&
40
+ p.path.trim() &&
41
+ typeof (p.id ?? p.name) === 'string')
42
+ .map((p) => ({
43
+ id: String(p.id || p.name)
44
+ .trim()
45
+ .toLowerCase()
46
+ .replace(/[^a-z0-9-_]+/g, '-')
47
+ .slice(0, 64),
48
+ name: String(p.name || p.id || 'project').trim() || 'project',
49
+ path: String(p.path).trim(),
50
+ }))
51
+ : [];
52
+ return {
53
+ projects,
54
+ bind: typeof raw.bind === 'string' ? raw.bind : undefined,
55
+ port: typeof raw.port === 'number' ? raw.port : undefined,
56
+ };
57
+ }
58
+ catch {
59
+ return { projects: [] };
60
+ }
61
+ }
62
+ export function saveCompanionConfig(cfg) {
63
+ ensureHome();
64
+ writeFileSync(getCompanionConfigPath(), JSON.stringify({
65
+ bind: cfg.bind,
66
+ port: cfg.port,
67
+ projects: cfg.projects,
68
+ }, null, 2) + '\n', 'utf8');
69
+ }
70
+ /** Load token or create a new one (printed once by serve). */
71
+ export function loadOrCreateToken(explicit) {
72
+ if (explicit && explicit.trim()) {
73
+ return { token: explicit.trim(), created: false };
74
+ }
75
+ ensureHome();
76
+ const path = getCompanionTokenPath();
77
+ if (existsSync(path)) {
78
+ const t = readFileSync(path, 'utf8').trim();
79
+ if (t)
80
+ return { token: t, created: false };
81
+ }
82
+ const token = randomBytes(24).toString('base64url');
83
+ writeFileSync(path, token + '\n', 'utf8');
84
+ try {
85
+ // Best-effort restrict (POSIX); ignored on Windows.
86
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
87
+ const fs = require('node:fs');
88
+ fs.chmodSync?.(path, 0o600);
89
+ }
90
+ catch {
91
+ /* ignore */
92
+ }
93
+ return { token, created: true };
94
+ }
95
+ export function tokenMatches(expected, provided) {
96
+ if (!provided)
97
+ return false;
98
+ const a = createHash('sha256').update(expected).digest();
99
+ const b = createHash('sha256').update(provided).digest();
100
+ try {
101
+ return timingSafeEqual(a, b);
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ export function slugFromPath(p) {
108
+ const base = p.replace(/[/\\]+$/, '').split(/[/\\]/).pop() || 'project';
109
+ return base
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9-_]+/g, '-')
112
+ .replace(/^-+|-+$/g, '')
113
+ .slice(0, 48) || 'project';
114
+ }
115
+ /** Merge CLI --project paths into config (in-memory; optionally persist). */
116
+ export function mergeProjects(cfg, extraPaths) {
117
+ const byId = new Map();
118
+ for (const p of cfg.projects) {
119
+ byId.set(p.id, p);
120
+ }
121
+ for (const raw of extraPaths) {
122
+ const path = raw.trim();
123
+ if (!path)
124
+ continue;
125
+ let id = slugFromPath(path);
126
+ let n = 2;
127
+ while (byId.has(id) && byId.get(id).path !== path) {
128
+ id = `${slugFromPath(path)}-${n++}`;
129
+ }
130
+ byId.set(id, {
131
+ id,
132
+ name: slugFromPath(path),
133
+ path,
134
+ });
135
+ }
136
+ return [...byId.values()];
137
+ }
138
+ export function resolveProjectPath(projects, cwdOrId) {
139
+ if (!projects.length) {
140
+ return {
141
+ ok: false,
142
+ error: 'No projects configured. Pass --project <path> or edit ~/.zelari-code/companion.json',
143
+ };
144
+ }
145
+ if (!cwdOrId || !String(cwdOrId).trim()) {
146
+ return { ok: true, project: projects[0] };
147
+ }
148
+ const key = String(cwdOrId).trim();
149
+ const byId = projects.find((p) => p.id === key || p.name === key);
150
+ if (byId)
151
+ return { ok: true, project: byId };
152
+ const norm = key.replace(/\\/g, '/').toLowerCase();
153
+ const byPath = projects.find((p) => p.path.replace(/\\/g, '/').toLowerCase() === norm);
154
+ if (byPath)
155
+ return { ok: true, project: byPath };
156
+ // Prefix match under an allowlisted root
157
+ const under = projects.find((p) => {
158
+ const root = p.path.replace(/\\/g, '/').toLowerCase().replace(/\/$/, '');
159
+ return norm === root || norm.startsWith(root + '/');
160
+ });
161
+ if (under) {
162
+ return {
163
+ ok: true,
164
+ project: { ...under, path: key },
165
+ };
166
+ }
167
+ return {
168
+ ok: false,
169
+ error: `cwd/project not in allowlist: ${key}. Allowed: ${projects.map((p) => p.id).join(', ')}`,
170
+ };
171
+ }
172
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/companion/config.ts"],"names":[],"mappings":"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"}