zam-core 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,26 @@
1
- import { Database } from 'libsql';
1
+ type DatabaseValue = string | number | bigint | null | Uint8Array;
2
+ interface RunResult {
3
+ changes: number;
4
+ lastInsertRowid: number | bigint;
5
+ }
6
+ interface Statement {
7
+ run(...params: unknown[]): RunResult;
8
+ get(...params: unknown[]): unknown;
9
+ all(...params: unknown[]): unknown[];
10
+ }
11
+ /**
12
+ * Synchronous database surface used by the learning kernel.
13
+ *
14
+ * Both better-sqlite3 and the optional libsql embedded-replica driver satisfy
15
+ * this contract.
16
+ */
17
+ interface Database {
18
+ prepare(sql: string): Statement;
19
+ exec(sql: string): void;
20
+ pragma(source: string): unknown;
21
+ close(): void;
22
+ sync?(): void;
23
+ }
2
24
 
3
25
  /**
4
26
  * Learning Analytics
@@ -381,6 +403,7 @@ declare function getBlockedCards(db: Database, userId: string): BlockedCard[];
381
403
  * Prerequisite repository — typed wrappers around the prerequisites table.
382
404
  *
383
405
  * Models the dependency graph: "to learn token A, first know token B."
406
+ * The graph must remain acyclic — cycles are rejected at insert time.
384
407
  */
385
408
 
386
409
  interface Prerequisite {
@@ -394,12 +417,19 @@ interface PrerequisiteWithToken extends Prerequisite {
394
417
  domain: string;
395
418
  bloom_level: number;
396
419
  }
420
+ /**
421
+ * Returns true if adding edge (tokenId → requiresId) would create a cycle.
422
+ * Uses BFS from requiresId: if tokenId is reachable, adding the edge closes
423
+ * a loop.
424
+ */
425
+ declare function wouldCreateCycle(db: Database, tokenId: string, requiresId: string): boolean;
397
426
  /**
398
427
  * Add a prerequisite edge: tokenId requires requiresId.
399
428
  *
400
429
  * Idempotent — silently ignores duplicate edges.
401
430
  * Throws if either token ID does not exist (FK constraint).
402
431
  * Throws if a token is declared as its own prerequisite.
432
+ * Throws if the edge would create a cycle in the prerequisite graph.
403
433
  */
404
434
  declare function addPrerequisite(db: Database, tokenId: string, requiresId: string): void;
405
435
  /**
@@ -670,10 +700,14 @@ declare function deleteToken(db: Database, slug: string): DeleteTokenResult;
670
700
  /**
671
701
  * Fuzzy search for tokens by keyword query.
672
702
  *
673
- * Ported from the PoC's find-token command: splits the query into word
674
- * tokens, scores each database token by word overlap plus a substring
675
- * bonus on the concept field, and returns all matches sorted by score
676
- * descending.
703
+ * Uses SQLite LIKE queries on slug, concept, and domain to avoid loading
704
+ * every non-deprecated token into memory. Each search term runs its own
705
+ * LIKE query; results are aggregated in JS with a word-overlap score plus
706
+ * a substring bonus on the concept field. Results are returned sorted by
707
+ * relevance score descending.
708
+ *
709
+ * For very small search terms (< 3 chars) a light in-memory fallback is
710
+ * used to avoid matching every token.
677
711
  */
678
712
  declare function findTokens(db: Database, query: string): ScoredToken[];
679
713
  /**
@@ -1190,10 +1224,9 @@ declare function distributeGlobalSkills(home?: string): Array<{
1190
1224
  success: boolean;
1191
1225
  }>;
1192
1226
  /**
1193
- * Inject the ZAM shell observation hook into user profile scripts so monitoring
1194
- * is automatically initialized on startup.
1227
+ * Add opt-in helpers for starting a monitored session to user shell profiles.
1195
1228
  */
1196
- declare function injectShellHooks(): Array<{
1229
+ declare function injectShellHooks(home?: string): Array<{
1197
1230
  shell: string;
1198
1231
  file: string;
1199
1232
  success: boolean;
@@ -1220,6 +1253,7 @@ interface InstallResult {
1220
1253
  success: boolean;
1221
1254
  message: string;
1222
1255
  }
1256
+ type LocalLLMRunner = "fastflowlm" | "ollama" | "generic";
1223
1257
  /**
1224
1258
  * Check if a command is executable on the system.
1225
1259
  */
@@ -1232,6 +1266,10 @@ declare function installFastFlowLM(): InstallResult;
1232
1266
  * Install Ollama via Homebrew on macOS.
1233
1267
  */
1234
1268
  declare function installOllama(): InstallResult;
1269
+ /**
1270
+ * Prepare the recommended model after installing a local LLM runner.
1271
+ */
1272
+ declare function prepareLocalModel(runner: LocalLLMRunner, model: string): InstallResult;
1235
1273
 
1236
1274
  interface SystemProfile {
1237
1275
  os: "windows" | "macos" | "linux" | "unknown";
@@ -1271,4 +1309,4 @@ declare function resolveAllBeliefPaths(db: Database): string[];
1271
1309
  */
1272
1310
  declare function resolveAllGoalPaths(db: Database): string[];
1273
1311
 
1274
- export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type BloomLevel$1 as BloomLevel, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, type InstallResult, type LogStepInput, type MonitorEvent, type ObservationRating, type Prerequisite, type PrerequisiteWithToken, type PromptInput, type Rating, type RecallPrompt, type RepoPaths, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SkillProposal, type SkillSource, type SupportedLocale, type SymbiosisMode, type SystemProfile, type Token, type TokenDeleteImpact, type TokenPattern, type TranslationKey, type TursoCredentials, type UnblockResult, type UpdateCardInput, type UpdateTokenInput, type UserSetting, type UserStats, type WorkItem, addPrerequisite, analyzeObservation, buildReviewQueue, cascadeBlock, clearADOCredentials, clearTursoCredentials, createAgentSkill, createFSRS, createGoal, createToken, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSystemLocale, discoverSkills, distributeGlobalSkills, endSession, ensureCard, ensureMonitorDir, evaluateRating, executeReviewAction, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateZshHooks, generateZshUnhooks, getADOCredentials, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getGoal, getGoalTree, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTursoCredentials, getUserStats, hasCommand, injectShellHooks, installFastFlowLM, installOllama, interleave, listAgentSkills, listGoals, listTokens, loadADOConfig, loadCredentials, logReview, logStep, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, pairCommands, parseGoalFile, parseMonitorLog, readMonitorLog, resolveAllBeliefPaths, resolveAllGoalPaths, resolveReference, resolveRepoPath, resolveReviewContext, saveCredentials, serializeGoal, setADOCredentials, setSetting, setTursoCredentials, startSession, t, unblockReady, updateCard, updateGoalStatus, updateToken, writeMonitorEvent };
1312
+ export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type BloomLevel$1 as BloomLevel, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type Database, type DatabaseValue, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, type InstallResult, type LocalLLMRunner, type LogStepInput, type MonitorEvent, type ObservationRating, type Prerequisite, type PrerequisiteWithToken, type PromptInput, type Rating, type RecallPrompt, type RepoPaths, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type RunResult, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SkillProposal, type SkillSource, type Statement, type SupportedLocale, type SymbiosisMode, type SystemProfile, type Token, type TokenDeleteImpact, type TokenPattern, type TranslationKey, type TursoCredentials, type UnblockResult, type UpdateCardInput, type UpdateTokenInput, type UserSetting, type UserStats, type WorkItem, addPrerequisite, analyzeObservation, buildReviewQueue, cascadeBlock, clearADOCredentials, clearTursoCredentials, createAgentSkill, createFSRS, createGoal, createToken, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSystemLocale, discoverSkills, distributeGlobalSkills, endSession, ensureCard, ensureMonitorDir, evaluateRating, executeReviewAction, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateZshHooks, generateZshUnhooks, getADOCredentials, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getGoal, getGoalTree, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTursoCredentials, getUserStats, hasCommand, injectShellHooks, installFastFlowLM, installOllama, interleave, listAgentSkills, listGoals, listTokens, loadADOConfig, loadCredentials, logReview, logStep, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, pairCommands, parseGoalFile, parseMonitorLog, prepareLocalModel, readMonitorLog, resolveAllBeliefPaths, resolveAllGoalPaths, resolveReference, resolveRepoPath, resolveReviewContext, saveCredentials, serializeGoal, setADOCredentials, setSetting, setTursoCredentials, startSession, t, unblockReady, updateCard, updateGoalStatus, updateToken, wouldCreateCycle, writeMonitorEvent };
package/dist/index.js CHANGED
@@ -219,9 +219,10 @@ async function fetchActiveWorkItems(config) {
219
219
 
220
220
  // src/kernel/db/connection.ts
221
221
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
222
+ import { createRequire } from "module";
222
223
  import { homedir as homedir2 } from "os";
223
224
  import { dirname as dirname2, join as join2 } from "path";
224
- import Database from "libsql";
225
+ import BetterSqlite3 from "better-sqlite3";
225
226
 
226
227
  // src/kernel/db/schema.ts
227
228
  var SCHEMA = `
@@ -337,9 +338,24 @@ CREATE INDEX IF NOT EXISTS idx_session_steps_session ON session_steps(session_id
337
338
  // src/kernel/db/connection.ts
338
339
  var DEFAULT_DB_DIR = join2(homedir2(), ".zam");
339
340
  var DEFAULT_DB_PATH = join2(DEFAULT_DB_DIR, "zam.db");
341
+ var require2 = createRequire(import.meta.url);
340
342
  function isRemoteDatabasePath(dbPath) {
341
343
  return /^(libsql|https?):\/\//i.test(dbPath);
342
344
  }
345
+ function openLocalSqlite(dbPath) {
346
+ return new BetterSqlite3(dbPath);
347
+ }
348
+ function loadLibsql() {
349
+ try {
350
+ const module = require2("libsql");
351
+ return "default" in module ? module.default : module;
352
+ } catch (err) {
353
+ const detail = err instanceof Error ? ` ${err.message}` : "";
354
+ throw new Error(
355
+ `Turso sync requires the optional native libsql backend, which is not available for ${process.platform}/${process.arch}.${detail}`
356
+ );
357
+ }
358
+ }
343
359
  function openDatabase(options = {}) {
344
360
  const configuredCloud = options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl ? getTursoCredentials() : null;
345
361
  let requiresTurso = false;
@@ -387,19 +403,24 @@ function openDatabase(options = {}) {
387
403
  dbOpts.authToken = authToken;
388
404
  }
389
405
  let db;
390
- try {
391
- db = new Database(dbPath, dbOpts);
392
- } catch (err) {
393
- const msg = err.message;
394
- if (msg.includes("InvalidLocalState") && options.syncUrl) {
395
- const metaPath = `${dbPath}.meta`;
396
- const infoPath = `${dbPath}-info`;
397
- if (existsSync2(metaPath)) rmSync(metaPath);
398
- if (existsSync2(infoPath)) rmSync(infoPath);
399
- db = new Database(dbPath, dbOpts);
400
- } else {
401
- throw err;
406
+ if (isRemote || isEmbeddedReplica) {
407
+ const LibsqlDatabase = loadLibsql();
408
+ try {
409
+ db = new LibsqlDatabase(dbPath, dbOpts);
410
+ } catch (err) {
411
+ const msg = err.message;
412
+ if (msg.includes("InvalidLocalState") && options.syncUrl) {
413
+ const metaPath = `${dbPath}.meta`;
414
+ const infoPath = `${dbPath}-info`;
415
+ if (existsSync2(metaPath)) rmSync(metaPath);
416
+ if (existsSync2(infoPath)) rmSync(infoPath);
417
+ db = new LibsqlDatabase(dbPath, dbOpts);
418
+ } else {
419
+ throw err;
420
+ }
402
421
  }
422
+ } else {
423
+ db = openLocalSqlite(dbPath);
403
424
  }
404
425
  if (!isRemote && !isEmbeddedReplica) {
405
426
  db.pragma("journal_mode = WAL");
@@ -409,7 +430,7 @@ function openDatabase(options = {}) {
409
430
  db.pragma("busy_timeout = 5000");
410
431
  }
411
432
  if (isEmbeddedReplica) {
412
- db.sync();
433
+ db.sync?.();
413
434
  }
414
435
  if (options.initialize) {
415
436
  db.exec(SCHEMA);
@@ -792,10 +813,47 @@ function getBlockedCards(db, userId) {
792
813
  }
793
814
 
794
815
  // src/kernel/models/prerequisite.ts
816
+ function buildAncestorMap(db) {
817
+ const rows = db.prepare("SELECT token_id, requires_id FROM prerequisites").all();
818
+ const map = /* @__PURE__ */ new Map();
819
+ for (const row of rows) {
820
+ let ancestors = map.get(row.token_id);
821
+ if (!ancestors) {
822
+ ancestors = /* @__PURE__ */ new Set();
823
+ map.set(row.token_id, ancestors);
824
+ }
825
+ ancestors.add(row.requires_id);
826
+ }
827
+ return map;
828
+ }
829
+ function wouldCreateCycle(db, tokenId, requiresId) {
830
+ if (tokenId === requiresId) return true;
831
+ const ancestors = buildAncestorMap(db);
832
+ const visited = /* @__PURE__ */ new Set();
833
+ const queue = [requiresId];
834
+ while (queue.length > 0) {
835
+ const current = queue.shift();
836
+ if (current === tokenId) return true;
837
+ if (visited.has(current)) continue;
838
+ visited.add(current);
839
+ const parents = ancestors.get(current);
840
+ if (parents) {
841
+ for (const parent of parents) {
842
+ if (!visited.has(parent)) queue.push(parent);
843
+ }
844
+ }
845
+ }
846
+ return false;
847
+ }
795
848
  function addPrerequisite(db, tokenId, requiresId) {
796
849
  if (tokenId === requiresId) {
797
850
  throw new Error("A token cannot be a prerequisite of itself");
798
851
  }
852
+ if (wouldCreateCycle(db, tokenId, requiresId)) {
853
+ throw new Error(
854
+ `Cannot add prerequisite: would create a cycle. ${requiresId} already depends on ${tokenId} (directly or transitively).`
855
+ );
856
+ }
799
857
  db.prepare(
800
858
  "INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
801
859
  ).run(tokenId, requiresId);
@@ -1123,23 +1181,51 @@ function deleteToken(db, slug) {
1123
1181
  }
1124
1182
  function findTokens(db, query) {
1125
1183
  const normalised = query.toLowerCase();
1126
- const qTokens = new Set(
1127
- normalised.split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter((t2) => t2.length > 2)
1128
- );
1129
- const tokens = db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
1130
- const scored = [];
1131
- for (const t2 of tokens) {
1132
- const words = `${t2.slug} ${t2.concept} ${t2.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
1133
- let score = 0;
1134
- for (const w of words) {
1135
- if (qTokens.has(w)) score++;
1184
+ const searchTokens = normalised.split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter((t2) => t2.length > 0);
1185
+ if (searchTokens.length === 0) return [];
1186
+ const shortTerms = searchTokens.filter((t2) => t2.length <= 2);
1187
+ const longTerms = searchTokens.filter((t2) => t2.length > 2);
1188
+ const scoreMap = /* @__PURE__ */ new Map();
1189
+ const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
1190
+ for (const term of longTerms) {
1191
+ const pattern = `%${term}%`;
1192
+ const rows = db.prepare(likeSQL).all(pattern, pattern, pattern);
1193
+ for (const row of rows) {
1194
+ const entry = scoreMap.get(row.id);
1195
+ if (entry) {
1196
+ entry.score++;
1197
+ } else {
1198
+ scoreMap.set(row.id, { token: row, score: 1 });
1199
+ }
1136
1200
  }
1137
- if (t2.concept.toLowerCase().includes(normalised.slice(0, 25))) {
1138
- score += 3;
1201
+ }
1202
+ if (shortTerms.length > 0 || longTerms.length === 0) {
1203
+ const allTokens = db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
1204
+ for (const token of allTokens) {
1205
+ const words = `${token.slug} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
1206
+ let matchCount = 0;
1207
+ for (const term of shortTerms.length > 0 ? shortTerms : searchTokens) {
1208
+ for (const w of words) {
1209
+ if (w === term) matchCount++;
1210
+ }
1211
+ }
1212
+ if (matchCount > 0) {
1213
+ const entry = scoreMap.get(token.id);
1214
+ if (entry) {
1215
+ entry.score += matchCount;
1216
+ } else {
1217
+ scoreMap.set(token.id, { token, score: matchCount });
1218
+ }
1219
+ }
1139
1220
  }
1140
- if (score > 0) {
1141
- scored.push({ score, ...t2 });
1221
+ }
1222
+ const scored = [];
1223
+ for (const { token, score } of scoreMap.values()) {
1224
+ let finalScore = score;
1225
+ if (token.concept.toLowerCase().includes(normalised.slice(0, 25))) {
1226
+ finalScore += 3;
1142
1227
  }
1228
+ scored.push({ score: finalScore, ...token });
1143
1229
  }
1144
1230
  scored.sort((a, b) => b.score - a.score);
1145
1231
  return scored;
@@ -2556,7 +2642,8 @@ import {
2556
2642
  copyFileSync,
2557
2643
  existsSync as existsSync6,
2558
2644
  mkdirSync as mkdirSync4,
2559
- readFileSync as readFileSync6
2645
+ readFileSync as readFileSync6,
2646
+ writeFileSync as writeFileSync3
2560
2647
  } from "fs";
2561
2648
  import { homedir as homedir4 } from "os";
2562
2649
  import { join as join6 } from "path";
@@ -2672,102 +2759,82 @@ function distributeGlobalSkills(home = HOME) {
2672
2759
  }
2673
2760
  return results;
2674
2761
  }
2675
- function injectShellHooks() {
2676
- const results = [];
2677
- const hookLine = `
2762
+ var POSIX_OLD_HOOK = `
2678
2763
  # ZAM Shell Observation Hooks
2679
2764
  if (command -v zam >/dev/null 2>&1); then eval "$(zam monitor start --quiet)"; fi
2680
2765
  `;
2681
- const pwshHookLine = `
2766
+ var POWERSHELL_OLD_HOOK = `
2682
2767
  # ZAM Shell Observation Hooks
2683
2768
  if (Get-Command zam -ErrorAction SilentlyContinue) { Invoke-Expression (& zam monitor start --quiet pwsh) }
2684
2769
  `;
2685
- const zshrc = join6(HOME, ".zshrc");
2686
- if (existsSync6(zshrc)) {
2687
- try {
2688
- const content = readFileSync6(zshrc, "utf8");
2689
- if (content.includes("zam monitor start")) {
2690
- results.push({
2691
- shell: "zsh",
2692
- file: zshrc,
2693
- success: true,
2694
- alreadyHooked: true
2695
- });
2696
- } else {
2697
- appendFileSync2(zshrc, hookLine);
2698
- results.push({
2699
- shell: "zsh",
2700
- file: zshrc,
2701
- success: true,
2702
- alreadyHooked: false
2703
- });
2704
- }
2705
- } catch {
2706
- results.push({
2707
- shell: "zsh",
2708
- file: zshrc,
2709
- success: false,
2710
- alreadyHooked: false
2711
- });
2770
+ var HOOK_MARKER = "# ZAM Monitor Session Helper";
2771
+ function posixHook(shell) {
2772
+ return `
2773
+ ${HOOK_MARKER}
2774
+ zam-monitor-session() {
2775
+ local session_id="\${1:-}"
2776
+ if [ -z "$session_id" ]; then
2777
+ printf 'Usage: zam-monitor-session <session-id>
2778
+ ' >&2
2779
+ return 2
2780
+ fi
2781
+ eval "$(command zam monitor start --session "$session_id" --shell ${shell})"
2782
+ }
2783
+ `;
2784
+ }
2785
+ var POWERSHELL_HOOK = `
2786
+ ${HOOK_MARKER}
2787
+ function Start-ZamMonitor {
2788
+ param([Parameter(Mandatory = $true)][string]$Session)
2789
+ Invoke-Expression (& zam monitor start --session $Session --shell pwsh)
2790
+ }
2791
+ `;
2792
+ function installHook(file, hook, oldHook) {
2793
+ try {
2794
+ const content = existsSync6(file) ? readFileSync6(file, "utf8") : "";
2795
+ if (content.includes(HOOK_MARKER)) {
2796
+ return { success: true, alreadyHooked: true };
2712
2797
  }
2798
+ if (content.includes(oldHook.trim())) {
2799
+ writeFileSync3(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
2800
+ } else {
2801
+ appendFileSync2(file, hook);
2802
+ }
2803
+ return { success: true, alreadyHooked: false };
2804
+ } catch {
2805
+ return { success: false, alreadyHooked: false };
2806
+ }
2807
+ }
2808
+ function injectShellHooks(home = HOME) {
2809
+ const results = [];
2810
+ const zshrc = join6(home, ".zshrc");
2811
+ if (existsSync6(zshrc)) {
2812
+ const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
2813
+ results.push({ shell: "zsh", file: zshrc, ...status });
2713
2814
  }
2714
- const bashrc = join6(HOME, ".bashrc");
2815
+ const bashrc = join6(home, ".bashrc");
2715
2816
  if (existsSync6(bashrc)) {
2716
- try {
2717
- const content = readFileSync6(bashrc, "utf8");
2718
- if (content.includes("zam monitor start")) {
2719
- results.push({
2720
- shell: "bash",
2721
- file: bashrc,
2722
- success: true,
2723
- alreadyHooked: true
2724
- });
2725
- } else {
2726
- appendFileSync2(bashrc, hookLine);
2727
- results.push({
2728
- shell: "bash",
2729
- file: bashrc,
2730
- success: true,
2731
- alreadyHooked: false
2732
- });
2733
- }
2734
- } catch {
2735
- results.push({
2736
- shell: "bash",
2737
- file: bashrc,
2738
- success: false,
2739
- alreadyHooked: false
2740
- });
2741
- }
2817
+ const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
2818
+ results.push({ shell: "bash", file: bashrc, ...status });
2742
2819
  }
2743
2820
  const pwshDirs = [
2744
- join6(HOME, "Documents", "PowerShell"),
2745
- join6(HOME, "Documents", "WindowsPowerShell")
2821
+ join6(home, "Documents", "PowerShell"),
2822
+ join6(home, "Documents", "WindowsPowerShell")
2746
2823
  ];
2747
2824
  for (const dir of pwshDirs) {
2748
2825
  const profileFile = join6(dir, "Microsoft.PowerShell_profile.ps1");
2749
2826
  try {
2750
2827
  mkdirSync4(dir, { recursive: true });
2751
- let content = "";
2752
- if (existsSync6(profileFile)) {
2753
- content = readFileSync6(profileFile, "utf8");
2754
- }
2755
- if (content.includes("zam monitor start")) {
2756
- results.push({
2757
- shell: "powershell",
2758
- file: profileFile,
2759
- success: true,
2760
- alreadyHooked: true
2761
- });
2762
- } else {
2763
- appendFileSync2(profileFile, pwshHookLine);
2764
- results.push({
2765
- shell: "powershell",
2766
- file: profileFile,
2767
- success: true,
2768
- alreadyHooked: false
2769
- });
2770
- }
2828
+ const status = installHook(
2829
+ profileFile,
2830
+ POWERSHELL_HOOK,
2831
+ POWERSHELL_OLD_HOOK
2832
+ );
2833
+ results.push({
2834
+ shell: "powershell",
2835
+ file: profileFile,
2836
+ ...status
2837
+ });
2771
2838
  } catch {
2772
2839
  results.push({
2773
2840
  shell: "powershell",
@@ -2989,7 +3056,7 @@ function t(locale, key, params = {}) {
2989
3056
  }
2990
3057
 
2991
3058
  // src/kernel/system/installer.ts
2992
- import { execSync } from "child_process";
3059
+ import { execFileSync, execSync } from "child_process";
2993
3060
  import { existsSync as existsSync7 } from "fs";
2994
3061
  import { homedir as homedir5 } from "os";
2995
3062
  import { join as join7 } from "path";
@@ -3094,6 +3161,51 @@ function installOllama() {
3094
3161
  }
3095
3162
  }
3096
3163
  }
3164
+ function resolveOllamaCommand() {
3165
+ if (hasCommand("ollama")) return "ollama";
3166
+ const candidates = process.platform === "win32" ? [
3167
+ join7(
3168
+ homedir5(),
3169
+ "AppData",
3170
+ "Local",
3171
+ "Programs",
3172
+ "Ollama",
3173
+ "ollama.exe"
3174
+ )
3175
+ ] : process.platform === "darwin" ? ["/Applications/Ollama.app/Contents/Resources/ollama"] : [];
3176
+ return candidates.find((candidate) => existsSync7(candidate));
3177
+ }
3178
+ function prepareLocalModel(runner, model) {
3179
+ if (runner === "fastflowlm") {
3180
+ return {
3181
+ success: true,
3182
+ message: `${model} will be downloaded by FastFlowLM on first use.`
3183
+ };
3184
+ }
3185
+ if (runner !== "ollama") {
3186
+ return {
3187
+ success: false,
3188
+ message: "No supported local LLM runner was selected."
3189
+ };
3190
+ }
3191
+ const ollamaCommand = resolveOllamaCommand();
3192
+ if (!ollamaCommand) {
3193
+ return {
3194
+ success: false,
3195
+ message: `Ollama was installed but its command is not available yet. Restart the terminal, then run ollama pull ${model}.`
3196
+ };
3197
+ }
3198
+ console.log(`Downloading ${model} with Ollama...`);
3199
+ try {
3200
+ execFileSync(ollamaCommand, ["pull", model], { stdio: "inherit" });
3201
+ return { success: true, message: `${model} is ready in Ollama.` };
3202
+ } catch (err) {
3203
+ return {
3204
+ success: false,
3205
+ message: `Could not prepare ${model}: ${err.message}. Start Ollama and run: ollama pull ${model}`
3206
+ };
3207
+ }
3208
+ }
3097
3209
 
3098
3210
  // src/kernel/system/locale.ts
3099
3211
  import { execSync as execSync2 } from "child_process";
@@ -3329,6 +3441,7 @@ export {
3329
3441
  pairCommands,
3330
3442
  parseGoalFile,
3331
3443
  parseMonitorLog,
3444
+ prepareLocalModel,
3332
3445
  readMonitorLog,
3333
3446
  resolveAllBeliefPaths,
3334
3447
  resolveAllGoalPaths,
@@ -3346,6 +3459,7 @@ export {
3346
3459
  updateCard,
3347
3460
  updateGoalStatus,
3348
3461
  updateToken,
3462
+ wouldCreateCycle,
3349
3463
  writeMonitorEvent
3350
3464
  };
3351
3465
  //# sourceMappingURL=index.js.map