aiden-runtime 3.16.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 (159) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +465 -0
  3. package/config/devos.config.json +186 -0
  4. package/config/hardware.json +9 -0
  5. package/config/model-selection.json +7 -0
  6. package/config/setup-complete.json +20 -0
  7. package/dist/api/routes/computerUse.js +112 -0
  8. package/dist/api/server.js +6870 -0
  9. package/dist/bin/npx-init.js +71 -0
  10. package/dist/coordination/commandGate.js +115 -0
  11. package/dist/coordination/livePulse.js +127 -0
  12. package/dist/core/agentLoop.js +2718 -0
  13. package/dist/core/agentShield.js +231 -0
  14. package/dist/core/aidenIdentity.js +215 -0
  15. package/dist/core/aidenPersonality.js +166 -0
  16. package/dist/core/aidenSdk.js +374 -0
  17. package/dist/core/asyncTasks.js +82 -0
  18. package/dist/core/auditTrail.js +61 -0
  19. package/dist/core/auxiliaryClient.js +114 -0
  20. package/dist/core/bgLLM.js +108 -0
  21. package/dist/core/bm25.js +68 -0
  22. package/dist/core/callbackSystem.js +64 -0
  23. package/dist/core/channels/adapter.js +6 -0
  24. package/dist/core/channels/discord.js +173 -0
  25. package/dist/core/channels/email.js +253 -0
  26. package/dist/core/channels/imessage.js +164 -0
  27. package/dist/core/channels/manager.js +96 -0
  28. package/dist/core/channels/signal.js +140 -0
  29. package/dist/core/channels/slack.js +139 -0
  30. package/dist/core/channels/twilio.js +144 -0
  31. package/dist/core/channels/webhook.js +186 -0
  32. package/dist/core/channels/whatsapp.js +185 -0
  33. package/dist/core/clarifyBus.js +75 -0
  34. package/dist/core/codeInterpreter.js +82 -0
  35. package/dist/core/computerControl.js +439 -0
  36. package/dist/core/conversationMemory.js +334 -0
  37. package/dist/core/costTracker.js +221 -0
  38. package/dist/core/cronManager.js +217 -0
  39. package/dist/core/deepKB.js +77 -0
  40. package/dist/core/doctor.js +279 -0
  41. package/dist/core/dreamEngine.js +334 -0
  42. package/dist/core/entityGraph.js +169 -0
  43. package/dist/core/eventBus.js +16 -0
  44. package/dist/core/evolutionAnalyzer.js +153 -0
  45. package/dist/core/executionLoop.js +309 -0
  46. package/dist/core/executor.js +224 -0
  47. package/dist/core/failureAnalyzer.js +166 -0
  48. package/dist/core/fastPathExpansion.js +82 -0
  49. package/dist/core/faultEngine.js +106 -0
  50. package/dist/core/featureGates.js +70 -0
  51. package/dist/core/fileIngestion.js +113 -0
  52. package/dist/core/gateway.js +97 -0
  53. package/dist/core/goalTracker.js +75 -0
  54. package/dist/core/growthEngine.js +168 -0
  55. package/dist/core/hardwareDetector.js +98 -0
  56. package/dist/core/hooks.js +45 -0
  57. package/dist/core/httpKeepalive.js +46 -0
  58. package/dist/core/hybridSearch.js +101 -0
  59. package/dist/core/importers.js +164 -0
  60. package/dist/core/instinctSystem.js +223 -0
  61. package/dist/core/knowledgeBase.js +351 -0
  62. package/dist/core/learningMemory.js +121 -0
  63. package/dist/core/lessonsBrowser.js +125 -0
  64. package/dist/core/licenseManager.js +399 -0
  65. package/dist/core/logBuffer.js +85 -0
  66. package/dist/core/machineId.js +87 -0
  67. package/dist/core/mcpClient.js +442 -0
  68. package/dist/core/memoryDistiller.js +165 -0
  69. package/dist/core/memoryExtractor.js +212 -0
  70. package/dist/core/memoryIds.js +213 -0
  71. package/dist/core/memoryPreamble.js +113 -0
  72. package/dist/core/memoryQuery.js +136 -0
  73. package/dist/core/memoryRecall.js +140 -0
  74. package/dist/core/memoryStrategy.js +201 -0
  75. package/dist/core/messageValidator.js +85 -0
  76. package/dist/core/modelDiscovery.js +108 -0
  77. package/dist/core/modelRouter.js +118 -0
  78. package/dist/core/morningBriefing.js +203 -0
  79. package/dist/core/multiGoalValidator.js +51 -0
  80. package/dist/core/parallelExecutor.js +43 -0
  81. package/dist/core/passiveSkillObserver.js +204 -0
  82. package/dist/core/paths.js +57 -0
  83. package/dist/core/patternDetector.js +83 -0
  84. package/dist/core/planResponseRepair.js +64 -0
  85. package/dist/core/planTool.js +111 -0
  86. package/dist/core/playwrightBridge.js +356 -0
  87. package/dist/core/pluginSystem.js +121 -0
  88. package/dist/core/privateMode.js +85 -0
  89. package/dist/core/reactLoop.js +156 -0
  90. package/dist/core/recipeEngine.js +166 -0
  91. package/dist/core/responseCache.js +128 -0
  92. package/dist/core/runSandbox.js +132 -0
  93. package/dist/core/sandboxRunner.js +200 -0
  94. package/dist/core/scheduler.js +543 -0
  95. package/dist/core/secretScanner.js +49 -0
  96. package/dist/core/semanticMemory.js +223 -0
  97. package/dist/core/sessionMemory.js +259 -0
  98. package/dist/core/sessionRouter.js +91 -0
  99. package/dist/core/sessionSearch.js +163 -0
  100. package/dist/core/setupWizard.js +225 -0
  101. package/dist/core/skillImporter.js +303 -0
  102. package/dist/core/skillLibrary.js +144 -0
  103. package/dist/core/skillLoader.js +471 -0
  104. package/dist/core/skillTeacher.js +352 -0
  105. package/dist/core/skillValidator.js +210 -0
  106. package/dist/core/skillWriter.js +384 -0
  107. package/dist/core/slashAsTool.js +226 -0
  108. package/dist/core/spawnManager.js +197 -0
  109. package/dist/core/statusVerbs.js +43 -0
  110. package/dist/core/swarmManager.js +109 -0
  111. package/dist/core/taskQueue.js +119 -0
  112. package/dist/core/taskRecovery.js +128 -0
  113. package/dist/core/taskState.js +168 -0
  114. package/dist/core/telegramBot.js +152 -0
  115. package/dist/core/todoManager.js +70 -0
  116. package/dist/core/toolNameRepair.js +71 -0
  117. package/dist/core/toolRegistry.js +2730 -0
  118. package/dist/core/tools/calendarTool.js +98 -0
  119. package/dist/core/tools/companyFilingsTool.js +98 -0
  120. package/dist/core/tools/gmailTool.js +87 -0
  121. package/dist/core/tools/marketDataTool.js +135 -0
  122. package/dist/core/tools/socialResearchTool.js +121 -0
  123. package/dist/core/truthCheck.js +57 -0
  124. package/dist/core/updateChecker.js +74 -0
  125. package/dist/core/userCognitionProfile.js +238 -0
  126. package/dist/core/userProfile.js +341 -0
  127. package/dist/core/version.js +5 -0
  128. package/dist/core/visionAnalyze.js +161 -0
  129. package/dist/core/voice/audio.js +187 -0
  130. package/dist/core/voice/stt.js +226 -0
  131. package/dist/core/voice/tts.js +310 -0
  132. package/dist/core/voiceInput.js +118 -0
  133. package/dist/core/voiceOutput.js +130 -0
  134. package/dist/core/webSearch.js +326 -0
  135. package/dist/core/workflowTracker.js +72 -0
  136. package/dist/core/workspaceMemory.js +54 -0
  137. package/dist/core/youtubeTranscript.js +224 -0
  138. package/dist/integrations/computerUse/apiRegistry.js +113 -0
  139. package/dist/integrations/computerUse/screenAgent.js +203 -0
  140. package/dist/integrations/computerUse/visionLoop.js +296 -0
  141. package/dist/memory/memoryLayers.js +143 -0
  142. package/dist/providers/boa.js +93 -0
  143. package/dist/providers/cerebras.js +70 -0
  144. package/dist/providers/custom.js +89 -0
  145. package/dist/providers/gemini.js +82 -0
  146. package/dist/providers/groq.js +92 -0
  147. package/dist/providers/index.js +149 -0
  148. package/dist/providers/nvidia.js +70 -0
  149. package/dist/providers/ollama.js +99 -0
  150. package/dist/providers/openrouter.js +74 -0
  151. package/dist/providers/router.js +497 -0
  152. package/dist/providers/types.js +6 -0
  153. package/dist/security/browserVault.js +129 -0
  154. package/dist/security/dataGuard.js +89 -0
  155. package/dist/tools/eonetTool.js +72 -0
  156. package/dist/types/computerUse.js +2 -0
  157. package/dist/types/executor.js +2 -0
  158. package/dist-bundle/cli.js +357859 -0
  159. package/package.json +256 -0
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // DevOS — Autonomous AI Execution System
4
+ // Copyright (c) 2026 Shiva Deore. All rights reserved.
5
+ // ============================================================
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.rebuildIndex = rebuildIndex;
11
+ exports.getIndex = getIndex;
12
+ exports.searchSessions = searchSessions;
13
+ exports.getIndexSize = getIndexSize;
14
+ // core/sessionSearch.ts — BM25 full-text index over workspace session files.
15
+ //
16
+ // Indexes workspace/sessions/*.md and workspace/memory/*.md on first call,
17
+ // then incrementally re-indexes any files newer than the last rebuild.
18
+ // Exposes searchSessions(query, topK) for synchronous ranked results.
19
+ //
20
+ // BM25 parameters: k1=1.5 b=0.75 (standard defaults)
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const path_1 = __importDefault(require("path"));
23
+ const K1 = 1.5;
24
+ const B = 0.75;
25
+ let _index = null;
26
+ // ── Tokenizer ─────────────────────────────────────────────────
27
+ function tokenize(text) {
28
+ return text
29
+ .toLowerCase()
30
+ .replace(/[^a-z0-9\s]/g, ' ')
31
+ .split(/\s+/)
32
+ .filter(t => t.length > 1);
33
+ }
34
+ // ── Index builder ─────────────────────────────────────────────
35
+ function getSessionDirs() {
36
+ const base = process.cwd();
37
+ return [
38
+ path_1.default.join(base, 'workspace', 'sessions'),
39
+ path_1.default.join(base, 'workspace', 'memory'),
40
+ ];
41
+ }
42
+ function loadDocuments() {
43
+ const docs = [];
44
+ for (const dir of getSessionDirs()) {
45
+ if (!fs_1.default.existsSync(dir))
46
+ continue;
47
+ let entries;
48
+ try {
49
+ entries = fs_1.default.readdirSync(dir);
50
+ }
51
+ catch {
52
+ continue;
53
+ }
54
+ for (const fname of entries) {
55
+ if (!fname.endsWith('.md') && !fname.endsWith('.json'))
56
+ continue;
57
+ const fpath = path_1.default.join(dir, fname);
58
+ let raw = '';
59
+ try {
60
+ raw = fs_1.default.readFileSync(fpath, 'utf-8');
61
+ }
62
+ catch {
63
+ continue;
64
+ }
65
+ let content = raw;
66
+ // For JSON, stringify to searchable text
67
+ if (fname.endsWith('.json')) {
68
+ try {
69
+ content = JSON.stringify(JSON.parse(raw), null, 1);
70
+ }
71
+ catch { /* keep raw */ }
72
+ }
73
+ const stat = fs_1.default.statSync(fpath);
74
+ const firstH1 = raw.match(/^#\s+(.+)$/m)?.[1] ?? fname.replace(/\.\w+$/, '');
75
+ docs.push({
76
+ id: fname.replace(/\.\w+$/, ''),
77
+ path: fpath,
78
+ title: firstH1.trim(),
79
+ content,
80
+ mtime: stat.mtimeMs,
81
+ });
82
+ }
83
+ }
84
+ return docs;
85
+ }
86
+ function buildIndex(docs) {
87
+ const tf = [];
88
+ const df = new Map();
89
+ let totalLen = 0;
90
+ for (const doc of docs) {
91
+ const tokens = tokenize(doc.content);
92
+ totalLen += tokens.length;
93
+ const counts = new Map();
94
+ for (const t of tokens)
95
+ counts.set(t, (counts.get(t) ?? 0) + 1);
96
+ tf.push(counts);
97
+ for (const term of counts.keys())
98
+ df.set(term, (df.get(term) ?? 0) + 1);
99
+ }
100
+ return {
101
+ docs,
102
+ tf,
103
+ df,
104
+ avgdl: docs.length ? totalLen / docs.length : 1,
105
+ built: Date.now(),
106
+ };
107
+ }
108
+ /** Rebuild index from disk. */
109
+ function rebuildIndex() {
110
+ const docs = loadDocuments();
111
+ _index = buildIndex(docs);
112
+ return _index;
113
+ }
114
+ /** Get or lazily build the index. Rebuilds if > 5 minutes old. */
115
+ function getIndex() {
116
+ if (!_index || Date.now() - _index.built > 5 * 60 * 1000) {
117
+ _index = rebuildIndex();
118
+ }
119
+ return _index;
120
+ }
121
+ // ── BM25 scoring ─────────────────────────────────────────────
122
+ function bm25Score(idx, docI, query) {
123
+ const N = idx.docs.length;
124
+ const dl = Array.from(idx.tf[docI].values()).reduce((a, b) => a + b, 0);
125
+ let score = 0;
126
+ for (const term of query) {
127
+ const freq = idx.tf[docI].get(term) ?? 0;
128
+ if (freq === 0)
129
+ continue;
130
+ const df = idx.df.get(term) ?? 0;
131
+ const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
132
+ const tf = (freq * (K1 + 1)) / (freq + K1 * (1 - B + B * (dl / idx.avgdl)));
133
+ score += idf * tf;
134
+ }
135
+ return score;
136
+ }
137
+ // ── Public search API ─────────────────────────────────────────
138
+ /**
139
+ * Full-text BM25 search over indexed session/memory files.
140
+ * @param query Natural-language query string
141
+ * @param topK Maximum results to return (default 5)
142
+ * @returns Hits sorted by descending BM25 score
143
+ */
144
+ function searchSessions(query, topK = 5) {
145
+ const idx = getIndex();
146
+ if (!idx.docs.length)
147
+ return [];
148
+ const tokens = tokenize(query);
149
+ if (!tokens.length)
150
+ return [];
151
+ const scored = idx.docs.map((doc, i) => ({
152
+ doc,
153
+ score: bm25Score(idx, i, tokens),
154
+ }));
155
+ return scored
156
+ .filter(h => h.score > 0)
157
+ .sort((a, b) => b.score - a.score)
158
+ .slice(0, topK);
159
+ }
160
+ /** Returns the number of indexed documents. */
161
+ function getIndexSize() {
162
+ return getIndex().docs.length;
163
+ }
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // DevOS — Autonomous AI Execution System
4
+ // Copyright (c) 2026 Shiva Deore. All rights reserved.
5
+ // ============================================================
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.runSetupWizard = runSetupWizard;
11
+ exports.isSetupComplete = isSetupComplete;
12
+ // core/setupWizard.ts — First-boot setup wizard.
13
+ // Sprint 25: 3-case smart UX:
14
+ // Case 1 — All good models already installed → confirm + use
15
+ // Case 2 — Gaps or user wants upgrades → show plan + pull
16
+ // Case 3 — Skip/offline → save what exists with fallbacks
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const readline_1 = __importDefault(require("readline"));
20
+ const child_process_1 = require("child_process");
21
+ const hardwareDetector_1 = require("./hardwareDetector");
22
+ const modelRouter_1 = require("./modelRouter");
23
+ const livePulse_1 = require("../coordination/livePulse");
24
+ const CONFIG_DIR = path_1.default.join(process.cwd(), 'config');
25
+ const MODEL_CONFIG = path_1.default.join(CONFIG_DIR, 'model-selection.json');
26
+ const SETUP_FLAG = path_1.default.join(CONFIG_DIR, 'setup-complete.json');
27
+ const TASK_TYPES = ['chat', 'code', 'vision', 'reasoning', 'embedding'];
28
+ function ask(q, nonTtyDefault = 'skip') {
29
+ // When running as an Electron child process or in any non-interactive context
30
+ // (no TTY attached to stdin), auto-answer with the default to avoid hanging.
31
+ if (!process.stdin.isTTY) {
32
+ console.log(`${q}[non-interactive: auto-answering "${nonTtyDefault}"]`);
33
+ return Promise.resolve(nonTtyDefault);
34
+ }
35
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
36
+ return new Promise(res => rl.question(q, ans => { rl.close(); res(ans.trim().toLowerCase()); }));
37
+ }
38
+ function pullModel(model) {
39
+ return new Promise((resolve, reject) => {
40
+ livePulse_1.livePulse.act('CEO', `Pulling model: ${model}`);
41
+ const proc = (0, child_process_1.spawn)('ollama', ['pull', model], { stdio: 'inherit' });
42
+ proc.on('close', code => {
43
+ if (code === 0) {
44
+ livePulse_1.livePulse.done('CEO', `Model ready: ${model}`);
45
+ resolve();
46
+ }
47
+ else {
48
+ livePulse_1.livePulse.error('CEO', `Failed: ${model}`);
49
+ reject(new Error(`exit ${code}`));
50
+ }
51
+ });
52
+ });
53
+ }
54
+ function saveSelection(selection) {
55
+ fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
56
+ fs_1.default.writeFileSync(MODEL_CONFIG, JSON.stringify(selection, null, 2));
57
+ }
58
+ function markComplete(hw, selection) {
59
+ fs_1.default.writeFileSync(SETUP_FLAG, JSON.stringify({
60
+ complete: true,
61
+ setupAt: new Date().toISOString(),
62
+ hardware: hw,
63
+ models: selection,
64
+ }, null, 2));
65
+ }
66
+ async function runSetupWizard() {
67
+ if (isSetupComplete())
68
+ return;
69
+ const hw = (0, hardwareDetector_1.detectHardware)();
70
+ // ── Ollama reachability check ─────────────────────────────────
71
+ let ollamaReachable = false;
72
+ try {
73
+ const r = await fetch('http://127.0.0.1:11434/api/tags', {
74
+ signal: AbortSignal.timeout(3000),
75
+ });
76
+ ollamaReachable = r.ok;
77
+ }
78
+ catch {
79
+ // Ollama not running or not installed
80
+ }
81
+ if (!ollamaReachable) {
82
+ markComplete(hw, {});
83
+ console.log(`
84
+ ┌─────────────────────────────────────────┐
85
+ │ DevOS — First Boot │
86
+ └─────────────────────────────────────────┘
87
+
88
+ Ollama is not running on this machine.
89
+ Aiden will start in cloud-only mode — your configured API keys will be used.
90
+
91
+ To enable local models later:
92
+ 1. Install Ollama: https://ollama.com/download
93
+ 2. Run: ollama serve
94
+ 3. Delete config/setup-complete.json and restart with: npm start
95
+
96
+ `);
97
+ return;
98
+ }
99
+ modelRouter_1.modelRouter.syncWithOllama();
100
+ const assessment = modelRouter_1.modelRouter.assessInstalledModels();
101
+ const gpuLine = hw.appleSilicon
102
+ ? `Apple Silicon (${hw.gpu})`
103
+ : `${hw.gpu} · ${hw.vramGB}GB VRAM`;
104
+ console.log(`
105
+ ┌─────────────────────────────────────────┐
106
+ │ DevOS — First Boot │
107
+ └─────────────────────────────────────────┘
108
+
109
+ Hey! I just scanned your machine.
110
+
111
+ GPU → ${gpuLine}
112
+ RAM → ${hw.ramGB}GB
113
+ OS → ${hw.platform}${hw.cudaAvailable ? ' · CUDA ✓' : ''}
114
+ `);
115
+ // ── CASE 1: All good models already installed ────────────────
116
+ if (assessment.allGood) {
117
+ console.log(` Good news — I found models already installed on your machine:\n`);
118
+ const selection = {};
119
+ for (const task of TASK_TYPES) {
120
+ const m = assessment.goodModels[task];
121
+ const upgrade = assessment.upgradesAvailable[task];
122
+ const note = upgrade ? ` (better option: ${upgrade.name})` : '';
123
+ console.log(` ${task.padEnd(10)} → ${m.name.padEnd(25)} ✓ installed${note}`);
124
+ selection[task] = m.name;
125
+ }
126
+ console.log('');
127
+ const answer = await ask(' Use these models? (yes / no) ', 'yes');
128
+ console.log('');
129
+ if (answer === 'yes' || answer === 'y') {
130
+ saveSelection(selection);
131
+ markComplete(hw, selection);
132
+ console.log(' ✓ All set. DevOS is ready.\n Run: npm start\n');
133
+ return;
134
+ }
135
+ // If no, fall through to show upgrade options
136
+ console.log(' Ok — showing better options available for your hardware:\n');
137
+ }
138
+ // ── CASE 2: Some gaps or user wants upgrades ─────────────────
139
+ const selection = {};
140
+ const toPull = [];
141
+ console.log(` Here's what I recommend for your ${hw.gpu}:\n`);
142
+ for (const task of TASK_TYPES) {
143
+ const good = assessment.goodModels[task];
144
+ const upgrade = assessment.upgradesAvailable[task];
145
+ const missing = assessment.missingModels[task];
146
+ if (good && !upgrade) {
147
+ // Already has the best option
148
+ console.log(` ${task.padEnd(10)} → ${good.name.padEnd(25)} ✓ installed, best fit`);
149
+ selection[task] = good.name;
150
+ }
151
+ else if (good && upgrade) {
152
+ // Has something but better available
153
+ console.log(` ${task.padEnd(10)} → ${upgrade.name.padEnd(25)} ↑ upgrade from ${good.name}`);
154
+ selection[task] = upgrade.name;
155
+ toPull.push(upgrade.name);
156
+ }
157
+ else if (missing) {
158
+ // Nothing installed for this task
159
+ console.log(` ${task.padEnd(10)} → ${missing.name.padEnd(25)} ✗ not installed`);
160
+ selection[task] = missing.name;
161
+ toPull.push(missing.name);
162
+ }
163
+ else {
164
+ // Absolute fallback
165
+ console.log(` ${task.padEnd(10)} → ${'phi3:mini'.padEnd(25)} ⚠ fallback`);
166
+ selection[task] = 'phi3:mini';
167
+ }
168
+ }
169
+ console.log('');
170
+ // Nothing needs pulling — just save config and finish
171
+ if (!toPull.length) {
172
+ saveSelection(selection);
173
+ markComplete(hw, selection);
174
+ console.log(' ✓ Configuration saved. Run: npm start\n');
175
+ return;
176
+ }
177
+ const modelWord = toPull.length === 1 ? 'model' : 'models';
178
+ const answer = await ask(` Download ${toPull.length} ${modelWord}? (yes / no / skip) `);
179
+ console.log('');
180
+ // ── CASE 3: Skip downloads ───────────────────────────────────
181
+ if (answer === 'skip' || answer === 's') {
182
+ // Keep installed models; fall back for tasks with nothing
183
+ for (const task of TASK_TYPES) {
184
+ if (!selection[task] || toPull.includes(selection[task])) {
185
+ const installed = assessment.goodModels[task];
186
+ selection[task] = installed ? installed.name : 'phi3:mini';
187
+ }
188
+ }
189
+ saveSelection(selection);
190
+ markComplete(hw, selection);
191
+ console.log(' ✓ Saved with available models. Pull later: ollama pull <model>\n');
192
+ return;
193
+ }
194
+ if (answer !== 'yes' && answer !== 'y') {
195
+ console.log(' No problem. Run: devos config models to configure manually.\n');
196
+ return;
197
+ }
198
+ // Pull missing / upgrade models
199
+ const unique = [...new Set(toPull)];
200
+ console.log(` Pulling ${unique.length} ${unique.length === 1 ? 'model' : 'models'}...\n`);
201
+ for (const model of unique) {
202
+ try {
203
+ await pullModel(model);
204
+ }
205
+ catch {
206
+ console.log(` ✗ Could not pull ${model}. Try manually: ollama pull ${model}`);
207
+ // Fall back to installed equivalent for this task
208
+ for (const task of TASK_TYPES) {
209
+ if (selection[task] === model) {
210
+ const fallback = assessment.goodModels[task];
211
+ selection[task] = fallback ? fallback.name : 'phi3:mini';
212
+ }
213
+ }
214
+ }
215
+ }
216
+ saveSelection(selection);
217
+ markComplete(hw, selection);
218
+ console.log(`
219
+ ✓ DevOS is configured for your machine.
220
+ Run: npm start
221
+ `);
222
+ }
223
+ function isSetupComplete() {
224
+ return fs_1.default.existsSync(SETUP_FLAG);
225
+ }