arkaos 2.10.0 → 2.11.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 +318 -107
  2. package/VERSION +1 -1
  3. package/config/hooks/cwd-changed.ps1 +144 -0
  4. package/config/hooks/post-tool-use.ps1 +347 -0
  5. package/config/hooks/post-tool-use.sh +6 -6
  6. package/config/hooks/pre-compact.ps1 +238 -0
  7. package/config/hooks/pre-compact.sh +10 -6
  8. package/config/hooks/session-start.ps1 +109 -0
  9. package/config/hooks/session-start.sh +1 -1
  10. package/config/hooks/user-prompt-submit.ps1 +287 -0
  11. package/config/hooks/user-prompt-submit.sh +5 -2
  12. package/config/statusline.ps1 +160 -0
  13. package/core/cognition/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/core/cognition/capture/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/core/cognition/capture/__pycache__/collector.cpython-313.pyc +0 -0
  16. package/core/cognition/capture/__pycache__/store.cpython-313.pyc +0 -0
  17. package/core/cognition/insights/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/core/cognition/insights/__pycache__/store.cpython-313.pyc +0 -0
  19. package/core/cognition/memory/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/cognition/memory/__pycache__/obsidian.cpython-313.pyc +0 -0
  21. package/core/cognition/memory/__pycache__/schemas.cpython-313.pyc +0 -0
  22. package/core/cognition/memory/__pycache__/vector.cpython-313.pyc +0 -0
  23. package/core/cognition/memory/__pycache__/writer.cpython-313.pyc +0 -0
  24. package/core/cognition/research/__pycache__/__init__.cpython-313.pyc +0 -0
  25. package/core/cognition/research/__pycache__/profiler.cpython-313.pyc +0 -0
  26. package/core/cognition/scheduler/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/core/cognition/scheduler/__pycache__/cli.cpython-313.pyc +0 -0
  28. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  29. package/core/cognition/scheduler/__pycache__/platform.cpython-313.pyc +0 -0
  30. package/core/cognition/scheduler/daemon.py +77 -21
  31. package/core/cognition/scheduler/platform.py +43 -12
  32. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  33. package/core/knowledge/vector_store.py +50 -25
  34. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  35. package/core/synapse/layers.py +2 -2
  36. package/installer/adapters/claude-code.js +72 -45
  37. package/installer/cli.js +19 -6
  38. package/installer/doctor.js +130 -18
  39. package/installer/index.js +592 -149
  40. package/installer/platform.js +20 -0
  41. package/installer/prompts.js +109 -5
  42. package/installer/python-resolver.js +251 -0
  43. package/installer/update.js +497 -62
  44. package/package.json +1 -1
  45. package/pyproject.toml +2 -2
  46. package/scripts/start-dashboard.ps1 +271 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ArkaOS Platform Helpers — single source of truth for platform branching.
3
+ *
4
+ * Centralises the ~19 scattered `process.platform === "win32"` checks into
5
+ * one importable module. Other installer files should import from here
6
+ * instead of inlining platform ternaries.
7
+ */
8
+
9
+ import { platform } from "node:os";
10
+
11
+ export const IS_WINDOWS = platform() === "win32";
12
+
13
+ /** Hook script extension: `.ps1` on Windows, `.sh` elsewhere. */
14
+ export const HOOK_EXT = IS_WINDOWS ? ".ps1" : ".sh";
15
+
16
+ /** Command-line tool to locate executables. */
17
+ export const CMD_FINDER = IS_WINDOWS ? "where" : "which";
18
+
19
+ /** Python binary name (venv resolution is in python-resolver.js). */
20
+ export const PYTHON_CMD = IS_WINDOWS ? "python" : "python3";
@@ -5,16 +5,26 @@
5
5
  */
6
6
 
7
7
  import { createInterface } from "node:readline";
8
- import { existsSync } from "node:fs";
8
+ import { existsSync, readFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { homedir } from "node:os";
11
11
 
12
- const rl = createInterface({ input: process.stdin, output: process.stdout });
12
+ // Readline interface is lazily created so headless upgrade runs can
13
+ // return without ever opening stdin. Eagerly constructing the interface
14
+ // at module import caused `npx arkaos install --force` to block on a
15
+ // closed-stdin pipe even when the wizard short-circuited below.
16
+ let rl = null;
17
+ function getRl() {
18
+ if (!rl) {
19
+ rl = createInterface({ input: process.stdin, output: process.stdout });
20
+ }
21
+ return rl;
22
+ }
13
23
 
14
24
  function ask(question, defaultValue = "") {
15
25
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
16
26
  return new Promise((resolve) => {
17
- rl.question(` ${question}${suffix}: `, (answer) => {
27
+ getRl().question(` ${question}${suffix}: `, (answer) => {
18
28
  resolve(answer.trim() || defaultValue);
19
29
  });
20
30
  });
@@ -23,7 +33,7 @@ function ask(question, defaultValue = "") {
23
33
  function askYN(question, defaultYes = true) {
24
34
  const suffix = defaultYes ? " [Y/n]" : " [y/N]";
25
35
  return new Promise((resolve) => {
26
- rl.question(` ${question}${suffix}: `, (answer) => {
36
+ getRl().question(` ${question}${suffix}: `, (answer) => {
27
37
  const a = answer.trim().toLowerCase();
28
38
  if (!a) resolve(defaultYes);
29
39
  else resolve(a === "y" || a === "yes");
@@ -37,7 +47,7 @@ function askChoice(question, options) {
37
47
  options.forEach((opt, i) => {
38
48
  console.log(` ${i + 1}) ${opt.label}`);
39
49
  });
40
- rl.question(` Choose [1-${options.length}]: `, (answer) => {
50
+ getRl().question(` Choose [1-${options.length}]: `, (answer) => {
41
51
  const idx = parseInt(answer.trim()) - 1;
42
52
  if (idx >= 0 && idx < options.length) {
43
53
  resolve(options[idx].value);
@@ -48,7 +58,101 @@ function askChoice(question, options) {
48
58
  });
49
59
  }
50
60
 
61
+ // Canonical ArkaOS data directory. Mirrors the fallback used at the
62
+ // top of installer/index.js::install so headless upgrade detection
63
+ // can reach the existing profile.json before the wizard tries to ask
64
+ // the user where their install lives.
65
+ const DEFAULT_INSTALL_DIR = join(homedir(), ".arkaos");
66
+
67
+ // When called on an upgrade and a valid profile.json already exists,
68
+ // honor the rule documented in .claude/rules/node-installer.md:
69
+ //
70
+ // No interactive prompts during headless/CI runs
71
+ //
72
+ // An upgrade by definition means the user already answered these
73
+ // questions on a prior install, so re-asking every field (language,
74
+ // market, role, company, project dir, vault, feature flags, keys)
75
+ // blocks `npx arkaos install --force` even from a redirected-stdin
76
+ // context like `/dev/null` or CI. readline reads directly from
77
+ // process.stdin and does not honor those redirects.
78
+ //
79
+ // The short-circuit reads the existing profile and returns a config
80
+ // compatible with installer/index.js::install's downstream expectations:
81
+ //
82
+ // - User metadata fields (language, market, role, company,
83
+ // projectsDir, vaultPath) come from profile.json.
84
+ // - installDir is the directory we found the profile in.
85
+ // - Feature flags (installDashboard, installKnowledge,
86
+ // installTranscription) default to false on upgrade — an upgrade
87
+ // should NOT reinstall optional features; if the user wants to
88
+ // add one, they re-run a fresh install with the wizard. This
89
+ // preserves the existing install exactly.
90
+ // - API key fields are left empty; keys live in keys.json and the
91
+ // installer already merges them non-destructively.
92
+ //
93
+ // Returns null when the short-circuit conditions are not met, which
94
+ // falls through to the interactive wizard below (fresh install or
95
+ // upgrade with a corrupt/missing profile).
96
+ function loadExistingProfileConfig(installDir) {
97
+ const profilePath = join(installDir, "profile.json");
98
+ if (!existsSync(profilePath)) return null;
99
+ try {
100
+ const profile = JSON.parse(readFileSync(profilePath, "utf-8"));
101
+ // Require the fields the downstream installer flow actually uses.
102
+ // Any missing field forces the wizard so we never write a
103
+ // half-populated profile back to disk on upgrade.
104
+ if (!profile.language || !profile.role) return null;
105
+ return {
106
+ language: profile.language,
107
+ market: profile.market || "",
108
+ role: profile.role,
109
+ company: profile.company || "",
110
+ projectsDir: profile.projectsDir || join(homedir(), "Projects"),
111
+ vaultPath: profile.vaultPath || "",
112
+ installDir,
113
+ // Upgrades preserve the existing install footprint; they do not
114
+ // re-opt-in to optional features. A user who wants to flip a
115
+ // feature flag on upgrade can re-run with the wizard.
116
+ // Exception: knowledge deps are always installed on upgrade because
117
+ // they are required for `npx arkaos index/search` to use vector
118
+ // search instead of degraded keyword fallback.
119
+ installDashboard: false,
120
+ installKnowledge: true,
121
+ installTranscription: false,
122
+ openaiKey: "",
123
+ googleKey: "",
124
+ falKey: "",
125
+ };
126
+ } catch {
127
+ // Corrupt profile.json — fall through to the wizard so the user
128
+ // has a path to fix it.
129
+ return null;
130
+ }
131
+ }
132
+
51
133
  export async function runSetupPrompts(isUpgrade = false) {
134
+ // Headless upgrade short-circuit. See loadExistingProfileConfig.
135
+ if (isUpgrade) {
136
+ const existing = loadExistingProfileConfig(DEFAULT_INSTALL_DIR);
137
+ if (existing) {
138
+ console.log(`
139
+ ╔══════════════════════════════════════════════════════╗
140
+ ║ ArkaOS Upgrade — using existing profile ║
141
+ ╚══════════════════════════════════════════════════════╝
142
+
143
+ Language: ${existing.language}
144
+ Market: ${existing.market || "(not set)"}
145
+ Role: ${existing.role}
146
+ Company: ${existing.company || "(not set)"}
147
+ Install dir: ${existing.installDir}
148
+
149
+ Optional features are NOT re-installed on upgrade.
150
+ Re-run without --force to flip feature flags interactively.
151
+ `);
152
+ return existing;
153
+ }
154
+ }
155
+
52
156
  console.log(`
53
157
  ╔══════════════════════════════════════════════════════╗
54
158
  ║ ArkaOS Setup — Let's configure your environment ║
@@ -0,0 +1,251 @@
1
+ /**
2
+ * ArkaOS Python Resolver — single source of truth for Python interpreter.
3
+ *
4
+ * Strategy: always use ~/.arkaos/venv. This isolates ArkaOS from system
5
+ * Python, avoids PEP 668 issues on macOS/Homebrew and Ubuntu 23.04+,
6
+ * and guarantees the doctor checks the same interpreter the installer uses.
7
+ */
8
+
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { homedir, platform } from "node:os";
12
+ import { execSync } from "node:child_process";
13
+
14
+ const INSTALL_DIR = join(homedir(), ".arkaos");
15
+
16
+ /**
17
+ * Return the path to the venv's Python interpreter.
18
+ * On Windows: ~/.arkaos/venv/Scripts/python.exe
19
+ * On Unix: ~/.arkaos/venv/bin/python
20
+ */
21
+ export function getVenvPython() {
22
+ if (platform() === "win32") {
23
+ return join(INSTALL_DIR, "venv", "Scripts", "python.exe");
24
+ }
25
+ return join(INSTALL_DIR, "venv", "bin", "python");
26
+ }
27
+
28
+ /**
29
+ * Return the path to the venv's pip.
30
+ */
31
+ export function getVenvPip() {
32
+ if (platform() === "win32") {
33
+ return join(INSTALL_DIR, "venv", "Scripts", "pip.exe");
34
+ }
35
+ return join(INSTALL_DIR, "venv", "bin", "pip");
36
+ }
37
+
38
+ /**
39
+ * Return the ArkaOS Python interpreter — venv if it exists, fallback to system.
40
+ * This is the ONLY function other modules should use to get a Python path.
41
+ */
42
+ export function getArkaosPython() {
43
+ const venvPy = getVenvPython();
44
+ if (existsSync(venvPy)) {
45
+ return venvPy;
46
+ }
47
+ // Fallback to system Python (for pre-venv installations)
48
+ return findSystemPython();
49
+ }
50
+
51
+ /**
52
+ * Return the ArkaOS pip — venv if it exists, fallback to system.
53
+ */
54
+ export function getArkaosPip() {
55
+ const venvPip = getVenvPip();
56
+ if (existsSync(venvPip)) {
57
+ return venvPip;
58
+ }
59
+ // Fallback: use python -m pip with PEP 668 handling
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Find system Python 3.11+ (used only during initial venv creation).
65
+ */
66
+ export function findSystemPython() {
67
+ const isWindows = platform() === "win32";
68
+ const candidates = ["python3", "python"];
69
+ const finder = isWindows ? "where" : "which";
70
+
71
+ for (const cmd of candidates) {
72
+ try {
73
+ // stderr redirected via stdio, not shell operators, so it works the
74
+ // same on bash, zsh, and cmd.exe.
75
+ const version = execSync(`${cmd} --version`, {
76
+ stdio: ["pipe", "pipe", "pipe"],
77
+ }).toString().trim();
78
+ const match = version.match(/(\d+)\.(\d+)/);
79
+ if (match && parseInt(match[1]) >= 3 && parseInt(match[2]) >= 11) {
80
+ // Resolve to an absolute path using the platform-native locator.
81
+ try {
82
+ const resolved = execSync(`${finder} ${cmd}`, {
83
+ stdio: ["pipe", "pipe", "ignore"],
84
+ }).toString().trim().split(/\r?\n/)[0];
85
+ return resolved || cmd;
86
+ } catch {
87
+ return cmd;
88
+ }
89
+ }
90
+ } catch {
91
+ continue;
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Create the ArkaOS venv if it doesn't exist.
99
+ * Returns true on success, false on failure.
100
+ */
101
+ export function ensureVenv(log = console.log) {
102
+ const venvDir = join(INSTALL_DIR, "venv");
103
+ const venvPy = getVenvPython();
104
+
105
+ if (existsSync(venvPy)) {
106
+ return true; // Already exists
107
+ }
108
+
109
+ const systemPython = findSystemPython();
110
+ if (!systemPython) {
111
+ log(" \u26a0 No Python 3.11+ found — cannot create venv");
112
+ return false;
113
+ }
114
+
115
+ try {
116
+ execSync(`"${systemPython}" -m venv "${venvDir}"`, { stdio: "pipe", timeout: 60000 });
117
+ log(` \u2713 Virtual environment created at ${venvDir}`);
118
+
119
+ // Upgrade pip inside the venv
120
+ try {
121
+ execSync(`"${venvPy}" -m pip install --upgrade pip --quiet`, { stdio: "pipe", timeout: 60000 });
122
+ } catch { /* pip upgrade is non-critical */ }
123
+
124
+ return true;
125
+ } catch (err) {
126
+ log(` \u26a0 Failed to create venv: ${err.message.slice(0, 100)}`);
127
+ return false;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Extract a meaningful error message from a failed execSync error.
133
+ * Prefers stderr (the actual pip output), falls back to err.message.
134
+ * Collapses multi-line output into a single line and caps the length
135
+ * so a traceback does not dominate the installer log, while keeping
136
+ * the failing package name and the key "No matching distribution" /
137
+ * "error: subprocess-exited-with-error" markers visible.
138
+ */
139
+ function formatPipError(err) {
140
+ let text = "";
141
+ if (err.stderr) {
142
+ text = err.stderr.toString();
143
+ } else if (err.stdout) {
144
+ text = err.stdout.toString();
145
+ } else if (err.message) {
146
+ text = err.message;
147
+ }
148
+ text = text
149
+ .replace(/\r/g, "")
150
+ .split("\n")
151
+ .map((l) => l.trimEnd())
152
+ .filter((l) => l.trim().length > 0)
153
+ .join(" | ");
154
+ if (text.length > 400) text = text.slice(0, 400) + "...";
155
+ return text;
156
+ }
157
+
158
+ /**
159
+ * Install Python packages using the ArkaOS interpreter.
160
+ * Uses venv pip (no PEP 668 issues) or falls back with --break-system-packages.
161
+ */
162
+ export function pipInstall(packages, opts = {}) {
163
+ const { quiet = true, upgrade = false, editable = null, timeout = 120000, log = console.log } = opts;
164
+
165
+ const venvPip = getArkaosPip();
166
+ const arkaosPy = getArkaosPython();
167
+
168
+ const flags = [];
169
+ if (quiet) flags.push("--quiet");
170
+ if (upgrade) flags.push("--upgrade");
171
+
172
+ const pkgArg = editable ? `-e "${editable}"` : packages;
173
+
174
+ // Strategy 1: Use venv pip directly (preferred — no PEP 668)
175
+ if (venvPip && existsSync(venvPip)) {
176
+ try {
177
+ execSync(`"${venvPip}" install ${flags.join(" ")} ${pkgArg}`, { stdio: "pipe", timeout });
178
+ return true;
179
+ } catch (err) {
180
+ log(` \u26a0 Venv pip install failed for ${packages}: ${formatPipError(err)}`);
181
+ return false;
182
+ }
183
+ }
184
+
185
+ // Strategy 2: Use python -m pip (system Python — handle PEP 668)
186
+ if (arkaosPy) {
187
+ // Try without --break-system-packages first
188
+ try {
189
+ execSync(`"${arkaosPy}" -m pip install ${flags.join(" ")} ${pkgArg}`, { stdio: "pipe", timeout });
190
+ return true;
191
+ } catch (err) {
192
+ const stderr = err.stderr ? err.stderr.toString() : err.message;
193
+ if (stderr.includes("externally-managed-environment")) {
194
+ // PEP 668 — retry with --break-system-packages
195
+ log(" \u26a0 PEP 668 detected — retrying with --break-system-packages");
196
+ try {
197
+ execSync(`"${arkaosPy}" -m pip install --break-system-packages ${flags.join(" ")} ${pkgArg}`, {
198
+ stdio: "pipe", timeout,
199
+ });
200
+ return true;
201
+ } catch (err2) {
202
+ log(` \u26a0 pip install with --break-system-packages also failed for ${packages}: ${formatPipError(err2)}`);
203
+ return false;
204
+ }
205
+ }
206
+ log(` \u26a0 pip install failed for ${packages}: ${formatPipError(err)}`);
207
+ return false;
208
+ }
209
+ }
210
+
211
+ log(" \u26a0 No Python interpreter available for pip install");
212
+ return false;
213
+ }
214
+
215
+ /**
216
+ * Check if the ArkaOS Python can import the core engine.
217
+ */
218
+ export function canImportCore() {
219
+ const py = getArkaosPython();
220
+ if (!py || !existsSync(py)) return false;
221
+ try {
222
+ execSync(`"${py}" -c "from core.synapse.engine import SynapseEngine"`, {
223
+ stdio: "pipe",
224
+ timeout: 10000,
225
+ cwd: getRepoRoot(),
226
+ });
227
+ return true;
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get the ArkaOS repo root from .repo-path or manifest.
235
+ */
236
+ export function getRepoRoot() {
237
+ const repoPathFile = join(INSTALL_DIR, ".repo-path");
238
+ if (existsSync(repoPathFile)) {
239
+ const p = readFileSync(repoPathFile, "utf-8").trim();
240
+ if (existsSync(p)) return p;
241
+ }
242
+ // Fallback: try manifest
243
+ const manifestPath = join(INSTALL_DIR, "install-manifest.json");
244
+ if (existsSync(manifestPath)) {
245
+ try {
246
+ const m = JSON.parse(readFileSync(manifestPath, "utf-8"));
247
+ if (m.repoRoot && existsSync(m.repoRoot)) return m.repoRoot;
248
+ } catch {}
249
+ }
250
+ return null;
251
+ }