zam-core 0.6.0 → 0.6.2

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
@@ -806,6 +806,8 @@ interface Token {
806
806
  created_at: string;
807
807
  updated_at: string;
808
808
  deprecated_at: string | null;
809
+ provider: string | null;
810
+ topic_id: string | null;
809
811
  }
810
812
  interface CreateTokenInput {
811
813
  slug: string;
@@ -816,6 +818,8 @@ interface CreateTokenInput {
816
818
  symbiosis_mode?: SymbiosisMode | null;
817
819
  source_link?: string | null;
818
820
  question?: string | null;
821
+ provider?: string | null;
822
+ topic_id?: string | null;
819
823
  }
820
824
  interface UpdateTokenInput {
821
825
  concept?: string;
@@ -825,6 +829,8 @@ interface UpdateTokenInput {
825
829
  symbiosis_mode?: SymbiosisMode | null;
826
830
  source_link?: string | null;
827
831
  question?: string | null;
832
+ provider?: string | null;
833
+ topic_id?: string | null;
828
834
  }
829
835
  interface ListTokensOptions {
830
836
  domain?: string;
@@ -922,6 +928,8 @@ interface PersonalCard {
922
928
  elapsedDays: number | null;
923
929
  scheduledDays: number | null;
924
930
  blocked: number | null;
931
+ provider: string | null;
932
+ topicId: string | null;
925
933
  }
926
934
  declare function slugify(text: string): string;
927
935
  declare function generateTokenSlug(db: Database, domain: string, concept: string, question?: string | null): Promise<string>;
@@ -937,6 +945,8 @@ interface CurriculumCardInput {
937
945
  context?: string;
938
946
  bloom_level?: number;
939
947
  symbiosis_mode?: string | null;
948
+ provider?: string | null;
949
+ topic_id?: string | null;
940
950
  }
941
951
  interface ImportCurriculumResult {
942
952
  createdCount: number;
@@ -990,6 +1000,9 @@ interface SourceProposalInput {
990
1000
  symbiosis_mode: string;
991
1001
  excerpt: string;
992
1002
  page_number?: string | null;
1003
+ provider?: string | null;
1004
+ topic_id?: string | null;
1005
+ source_id?: string | null;
993
1006
  }
994
1007
  /**
995
1008
  * Confirm source import transaction.
@@ -1975,6 +1988,12 @@ interface InstallPlan {
1975
1988
  method: string;
1976
1989
  command: string;
1977
1990
  }
1991
+ interface OllamaDetectionOptions {
1992
+ platform?: NodeJS.Platform;
1993
+ homeDir?: string;
1994
+ commandAvailable?: (command: string) => boolean;
1995
+ pathExists?: (path: string) => boolean;
1996
+ }
1978
1997
  /**
1979
1998
  * Check if a command is executable on the system.
1980
1999
  */
@@ -1987,6 +2006,8 @@ declare function installFastFlowLM(): InstallResult;
1987
2006
  * Install Ollama via Homebrew on macOS.
1988
2007
  */
1989
2008
  declare function installOllama(): InstallResult;
2009
+ declare function resolveOllamaCommand(options?: OllamaDetectionOptions): string | undefined;
2010
+ declare function isOllamaInstalled(options?: OllamaDetectionOptions): boolean;
1990
2011
  /**
1991
2012
  * Prepare the recommended model after installing a local LLM runner.
1992
2013
  */
@@ -2053,4 +2074,4 @@ declare function resolveAllBeliefPaths(db: Database): Promise<string[]>;
2053
2074
  */
2054
2075
  declare function resolveAllGoalPaths(db: Database): Promise<string[]>;
2055
2076
 
2056
- export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type ApplySessionSynthesisInput, type ApplySessionSynthesisResult, BUILT_IN_SENSITIVE_MATCHERS, type BloomLevel$1 as BloomLevel, type CaptureDecision, type CaptureDenialReason, type CaptureRequest, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type ConfirmFoundationsResult, type ConnectionOptions, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, type CurriculumCardInput, DEFAULT_OBSERVER_POLICY, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type Database, type DatabaseProvider, type DatabaseTargetInfo, type DatabaseValue, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type FoundationProposalInput, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, HOMEBREW_CASK, type ImportCurriculumResult, type ImportResult, type InstallChannel, type InstallConfig, type InstallMode, type InstallPlan, type InstallResult, type LocalLLMRunner, type LogStepInput, type MachineAiConfig, type MachineProviderRecord, type MachineRoleBinding, type MonitorEvent, type Neighborhood, type NeighborhoodToken, OBSERVER_POLICY_UNSET_HINT, OBSERVER_POLICY_VERSION, type ObservationRating, type ObserverConsent, type ObserverPolicy, type ObserverRetention, type ObserverScope, type ObserverSettingKey, type PersonalCard, type PrepareSessionSynthesisInput, type Prerequisite, type PrerequisiteWithToken, type PromptInput, REVIEW_CONTEXT_CACHE_TTL_MS, type Rating, type RecallPrompt, type RemoteDatabaseOptions, type RepoPaths, type ResolvedCaptureTarget, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type RunResult, SIDECAR_POLICY_FILE, SNAPSHOT_VERSION, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SessionSynthesisCandidate, type SessionSynthesisEvidence, type SessionSynthesisPreview, type SessionSynthesisRecord, type SidecarPrivacyPolicy, type SkillProposal, type SkillSource, type SnapshotManifest, type SourceProposalInput, type SplitProposalInput, type Statement, type SupportedLocale, type SymbiosisMode, type SynthesisConfidence, type SystemProfile, type Token, type TokenDeleteImpact, type TokenPattern, type TranslationKey, type TursoCredentials, UI_OBSERVATION_PROTOCOL_VERSION, type UiActionType, type UiApplicationContext, type UiCandidateToken, type UiEvidenceRef, type UiEvidenceType, type UiObservationKind, type UiObservationReport, type UiObservedAction, type UnblockResult, type UpdateActionKind, type UpdateCardInput, type UpdateDecision, type UpdateStep, type UpdateStepKind, type UpdateTokenInput, type UserSetting, type UserStats, WINGET_PACKAGE_ID, type WorkItem, type WorkspaceConfig, type WorkspaceKind, type WorkspaceSourceControl, addPrerequisite, analyzeObservation, appendUiObservationReport, applySessionSynthesis, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, confirmCardSplit, confirmFoundations, confirmSourceImport, createAgentSkill, createFSRS, createGoal, createToken, decidePostCapture, decidePreCapture, decideUpdate, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSyncProvider, detectSystemLocale, discoverSkills, distributeGlobalSkills, endSession, ensureCard, ensureMonitorDir, ensureUiObserverDir, evaluateRating, executeReviewAction, exportSnapshot, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateTokenSlug, generateZshHooks, generateZshUnhooks, getADOCredentials, getActiveWorkspace, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getGoal, getGoalTree, getInstallChannel, getInstallMode, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isUiObservationReport, listAgentSkills, listGoals, listPersonalCards, listProviderApiKeyRefs, listTokens, loadADOConfig, loadCredentials, loadInstallConfig, logReview, logStep, matchBuiltInSensitive, matchDenylist, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, openRemoteDatabase, pairCommands, parseGoalFile, parseMonitorLog, parseObserverList, parseObserverPolicy, parseSnapshot, parseUiObservationLog, planOpenCodeInstall, planUpdate, prepareLocalModel, prepareSessionSynthesis, readMonitorLog, readUiObservationLog, removeConfiguredWorkspace, resolveAllBeliefPaths, resolveAllGoalPaths, resolveObserverPolicy, resolveReference, resolveRepoPath, resolveReviewContext, saveConfiguredWorkspaces, saveCredentials, saveInstallConfig, saveMachineAiConfig, serializeGoal, setADOCredentials, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unblockReady, updateCard, updateGoalStatus, updateToken, upsertConfiguredWorkspace, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
2077
+ export { type ADOConfig, type ADOCredentials, type AgentSkill, type AnalysisResult, type ApplySessionSynthesisInput, type ApplySessionSynthesisResult, BUILT_IN_SENSITIVE_MATCHERS, type BloomLevel$1 as BloomLevel, type CaptureDecision, type CaptureDenialReason, type CaptureRequest, type Card, type CardDeletionImpact, type CardState$1 as CardState, type CascadeBlockResult, type CommandRecord, type CommandSequence, type ConfirmFoundationsResult, type ConnectionOptions, type CreateAgentSkillInput, type CreateGoalInput, type CreateReviewInput, type CreateSessionInput, type CreateTokenInput, type Credentials, type CurriculumCardInput, DEFAULT_OBSERVER_POLICY, DEFAULT_REVIEW_CONTEXT_MAX_CHARS, type Database, type DatabaseProvider, type DatabaseTargetInfo, type DatabaseValue, type DeleteCardResult, type DeleteTokenResult, type DiscoveryOptions, type DomainCompetence, type EvaluateInput, type EvaluateResult, type ExecuteReviewActionInput, type ExecutionContext, type FSRSParameters, type FoundationProposalInput, type Goal, type GoalFrontmatter, type GoalStatus, type GoalSummary, HOMEBREW_CASK, type ImportCurriculumResult, type ImportResult, type InstallChannel, type InstallConfig, type InstallMode, type InstallPlan, type InstallResult, type LocalLLMRunner, type LogStepInput, type MachineAiConfig, type MachineProviderRecord, type MachineRoleBinding, type MonitorEvent, type Neighborhood, type NeighborhoodToken, OBSERVER_POLICY_UNSET_HINT, OBSERVER_POLICY_VERSION, type ObservationRating, type ObserverConsent, type ObserverPolicy, type ObserverRetention, type ObserverScope, type ObserverSettingKey, type OllamaDetectionOptions, type PersonalCard, type PrepareSessionSynthesisInput, type Prerequisite, type PrerequisiteWithToken, type PromptInput, REVIEW_CONTEXT_CACHE_TTL_MS, type Rating, type RecallPrompt, type RemoteDatabaseOptions, type RepoPaths, type ResolvedCaptureTarget, type ResolvedReference, type ReviewActionResult, type ReviewActionType, type ReviewContext, type ReviewLog, type ReviewQueue, type ReviewQueueItem, type ReviewQueueOptions, type RunResult, SIDECAR_POLICY_FILE, SNAPSHOT_VERSION, type SchedulingCard, type Session, type SessionStep, type SessionSummary, type SessionSynthesisCandidate, type SessionSynthesisEvidence, type SessionSynthesisPreview, type SessionSynthesisRecord, type SidecarPrivacyPolicy, type SkillProposal, type SkillSource, type SnapshotManifest, type SourceProposalInput, type SplitProposalInput, type Statement, type SupportedLocale, type SymbiosisMode, type SynthesisConfidence, type SystemProfile, type Token, type TokenDeleteImpact, type TokenPattern, type TranslationKey, type TursoCredentials, UI_OBSERVATION_PROTOCOL_VERSION, type UiActionType, type UiApplicationContext, type UiCandidateToken, type UiEvidenceRef, type UiEvidenceType, type UiObservationKind, type UiObservationReport, type UiObservedAction, type UnblockResult, type UpdateActionKind, type UpdateCardInput, type UpdateDecision, type UpdateStep, type UpdateStepKind, type UpdateTokenInput, type UserSetting, type UserStats, WINGET_PACKAGE_ID, type WorkItem, type WorkspaceConfig, type WorkspaceKind, type WorkspaceSourceControl, addPrerequisite, analyzeObservation, appendUiObservationReport, applySessionSynthesis, buildReviewQueue, buildUiSynthesisCandidates, cascadeBlock, clearADOCredentials, clearProviderApiKey, clearReviewContextCache, clearTursoCredentials, compareVersions, confirmCardSplit, confirmFoundations, confirmSourceImport, createAgentSkill, createFSRS, createGoal, createToken, decidePostCapture, decidePreCapture, decideUpdate, deleteCardForUser, deleteSetting, deleteToken, deprecateToken, detectSyncProvider, detectSystemLocale, discoverSkills, distributeGlobalSkills, endSession, ensureCard, ensureMonitorDir, ensureUiObserverDir, evaluateRating, executeReviewAction, exportSnapshot, extractTasks, extractTokenRefs, fetchActiveWorkItems, findTokens, generateBashHooks, generateBashUnhooks, generateConceptFreeCue, generatePowerShellHooks, generatePowerShellUnhooks, generatePrompt, generateTokenSlug, generateZshHooks, generateZshUnhooks, getADOCredentials, getActiveWorkspace, getActiveWorkspaceId, getAgentSkill, getAllSettings, getAllSettingsDetailed, getBlockedCards, getCard, getCardById, getCardDeletionImpact, getConfiguredWorkspaces, getDatabaseTargetInfo, getDefaultDbPath, getDependents, getDomainCompetence, getDueCards, getGoal, getGoalTree, getInstallChannel, getInstallMode, getMachineAiConfig, getMonitorDir, getMonitorLogStats, getMonitorPath, getPackageSkillPath, getPrerequisites, getProviderApiKey, getRepoPaths, getReviewsForCard, getReviewsForUser, getSessionSummary, getSessionSynthesisRecords, getSetting, getSystemProfile, getTokenById, getTokenBySlug, getTokenDeleteImpact, getTokenNeighborhood, getTursoCredentials, getUiObservationPath, getUiObserverDir, getUserStats, hasCommand, importCurriculumCards, importSnapshot, injectShellHooks, installFastFlowLM, installOllama, installOpenCode, interleave, isObserverPolicyConfigured, isOllamaInstalled, isUiObservationReport, listAgentSkills, listGoals, listPersonalCards, listProviderApiKeyRefs, listTokens, loadADOConfig, loadCredentials, loadInstallConfig, logReview, logStep, matchBuiltInSensitive, matchDenylist, matchesFilePath, monitorLogExists, normalizeLocale, normalizePath, openDatabase, openDatabaseWithSync, openRemoteDatabase, pairCommands, parseGoalFile, parseMonitorLog, parseObserverList, parseObserverPolicy, parseSnapshot, parseUiObservationLog, planOpenCodeInstall, planUpdate, prepareLocalModel, prepareSessionSynthesis, readMonitorLog, readUiObservationLog, removeConfiguredWorkspace, resolveAllBeliefPaths, resolveAllGoalPaths, resolveObserverPolicy, resolveOllamaCommand, resolveReference, resolveRepoPath, resolveReviewContext, saveConfiguredWorkspaces, saveCredentials, saveInstallConfig, saveMachineAiConfig, serializeGoal, setADOCredentials, setActiveWorkspaceId, setInstallChannel, setInstallMode, setProviderApiKey, setSetting, setTursoCredentials, slugify, startSession, syncObserverSidecarPolicy, t, toSidecarPrivacyPolicy, uiObservationLogExists, uiObservationTimeSpan, unblockReady, updateCard, updateGoalStatus, updateToken, upsertConfiguredWorkspace, verifySnapshot, wouldCreateCycle, writeMonitorEvent };
package/dist/index.js CHANGED
@@ -539,7 +539,9 @@ CREATE TABLE IF NOT EXISTS tokens (
539
539
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
540
540
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
541
541
  deprecated_at TEXT,
542
- question TEXT
542
+ question TEXT,
543
+ provider TEXT,
544
+ topic_id TEXT
543
545
  );
544
546
 
545
547
  -- Prerequisite dependency graph: "to learn A, first know B"
@@ -979,6 +981,14 @@ async function runMigrations(db) {
979
981
  PRIMARY KEY (token_id, source_id)
980
982
  )
981
983
  `);
984
+ if (tokenCols.length > 0) {
985
+ if (!tokenCols.some((c) => c.name === "provider")) {
986
+ await db.exec(`ALTER TABLE tokens ADD COLUMN provider TEXT`);
987
+ }
988
+ if (!tokenCols.some((c) => c.name === "topic_id")) {
989
+ await db.exec(`ALTER TABLE tokens ADD COLUMN topic_id TEXT`);
990
+ }
991
+ }
982
992
  }
983
993
 
984
994
  // src/kernel/db/snapshot.ts
@@ -1481,8 +1491,8 @@ async function createToken(db, input) {
1481
1491
  throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1482
1492
  }
1483
1493
  await db.prepare(`
1484
- INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, created_at, updated_at)
1485
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1494
+ INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
1495
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1486
1496
  `).run(
1487
1497
  id,
1488
1498
  input.slug,
@@ -1493,16 +1503,33 @@ async function createToken(db, input) {
1493
1503
  input.symbiosis_mode ?? null,
1494
1504
  input.source_link ?? null,
1495
1505
  input.question ?? null,
1506
+ input.provider ?? null,
1507
+ input.topic_id ?? null,
1496
1508
  now,
1497
1509
  now
1498
1510
  );
1499
1511
  return await getTokenById(db, id);
1500
1512
  }
1513
+ function parseTokenFallback(token) {
1514
+ if (token && !token.provider && token.source_link) {
1515
+ if (token.source_link.includes("lehrplanplus.bayern.de")) {
1516
+ token.provider = "lehrplanplus-bayern";
1517
+ const match = token.source_link.match(/#(.*)$/);
1518
+ if (match) {
1519
+ token.topic_id = match[1];
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1501
1524
  async function getTokenBySlug(db, slug) {
1502
- return await db.prepare("SELECT * FROM tokens WHERE slug = ?").get(slug);
1525
+ const token = await db.prepare("SELECT * FROM tokens WHERE slug = ?").get(slug);
1526
+ parseTokenFallback(token);
1527
+ return token;
1503
1528
  }
1504
1529
  async function getTokenById(db, id) {
1505
- return await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1530
+ const token = await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1531
+ parseTokenFallback(token);
1532
+ return token;
1506
1533
  }
1507
1534
  async function updateToken(db, slug, updates) {
1508
1535
  const token = await getTokenBySlug(db, slug);
@@ -1548,6 +1575,14 @@ async function updateToken(db, slug, updates) {
1548
1575
  fields.push("question = ?");
1549
1576
  values.push(updates.question);
1550
1577
  }
1578
+ if (updates.provider !== void 0) {
1579
+ fields.push("provider = ?");
1580
+ values.push(updates.provider);
1581
+ }
1582
+ if (updates.topic_id !== void 0) {
1583
+ fields.push("topic_id = ?");
1584
+ values.push(updates.topic_id);
1585
+ }
1551
1586
  if (fields.length === 0) {
1552
1587
  throw new Error("updateToken called with no fields to update");
1553
1588
  }
@@ -1673,14 +1708,20 @@ async function findTokens(db, query) {
1673
1708
  return scored;
1674
1709
  }
1675
1710
  async function listTokens(db, options) {
1711
+ let tokens;
1676
1712
  if (options?.domain) {
1677
- return await db.prepare(
1713
+ tokens = await db.prepare(
1678
1714
  "SELECT * FROM tokens WHERE domain = ? AND deprecated_at IS NULL ORDER BY bloom_level, slug"
1679
1715
  ).all(options.domain);
1716
+ } else {
1717
+ tokens = await db.prepare(
1718
+ "SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
1719
+ ).all();
1680
1720
  }
1681
- return await db.prepare(
1682
- "SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
1683
- ).all();
1721
+ for (const token of tokens) {
1722
+ parseTokenFallback(token);
1723
+ }
1724
+ return tokens;
1684
1725
  }
1685
1726
  function slugify(text) {
1686
1727
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
@@ -1718,10 +1759,22 @@ async function listPersonalCards(db, userId, options) {
1718
1759
  t.bloom_level AS bloomLevel,
1719
1760
  t.context,
1720
1761
  t.symbiosis_mode AS symbiosisMode,
1721
- t.source_link AS sourceLink,
1762
+ COALESCE(
1763
+ t.source_link,
1764
+ (
1765
+ SELECT s.uri
1766
+ FROM token_sources ts
1767
+ INNER JOIN sources s ON s.id = ts.source_id
1768
+ WHERE ts.token_id = t.id
1769
+ ORDER BY s.created_at DESC, s.id DESC
1770
+ LIMIT 1
1771
+ )
1772
+ ) AS sourceLink,
1722
1773
  t.question,
1723
1774
  t.created_at AS createdAt,
1724
1775
  t.updated_at AS updatedAt,
1776
+ t.provider,
1777
+ t.topic_id AS topicId,
1725
1778
  c.id AS cardId,
1726
1779
  c.state,
1727
1780
  c.due_at AS dueAt,
@@ -1751,6 +1804,17 @@ async function listPersonalCards(db, userId, options) {
1751
1804
  }
1752
1805
  sql += " ORDER BY t.created_at DESC";
1753
1806
  const rows = await db.prepare(sql).all(...values);
1807
+ for (const row of rows) {
1808
+ if (!row.provider && row.sourceLink) {
1809
+ if (row.sourceLink.includes("lehrplanplus.bayern.de")) {
1810
+ row.provider = "lehrplanplus-bayern";
1811
+ const match = row.sourceLink.match(/#(.*)$/);
1812
+ if (match) {
1813
+ row.topicId = match[1];
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1754
1818
  return rows;
1755
1819
  }
1756
1820
  async function importCurriculumCards(db, userId, cards) {
@@ -1797,9 +1861,18 @@ async function importCurriculumCards(db, userId, cards) {
1797
1861
  context: card.context || "",
1798
1862
  symbiosis_mode: symbiosisMode,
1799
1863
  source_link: card.source_link || null,
1800
- question: card.question || null
1864
+ question: card.question || null,
1865
+ provider: card.provider || null,
1866
+ topic_id: card.topic_id || null
1801
1867
  });
1802
1868
  createdCount++;
1869
+ } else {
1870
+ if (!token.provider && card.provider) {
1871
+ await updateToken(tx, token.slug, {
1872
+ provider: card.provider,
1873
+ topic_id: card.topic_id || null
1874
+ });
1875
+ }
1803
1876
  }
1804
1877
  const existingCard = await getCard(tx, token.id, userId);
1805
1878
  if (!existingCard) {
@@ -2021,11 +2094,12 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2021
2094
  let createdCount = 0;
2022
2095
  let linkedCount = 0;
2023
2096
  await db.transaction(async (tx) => {
2024
- const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(sourceId);
2025
- if (!source) {
2026
- throw new Error(`Source not found: ${sourceId}`);
2027
- }
2028
2097
  for (const card of proposals) {
2098
+ const cardSourceId = card.source_id || sourceId;
2099
+ const source = await tx.prepare("SELECT id FROM sources WHERE id = ?").get(cardSourceId);
2100
+ if (!source) {
2101
+ throw new Error(`Source not found: ${cardSourceId}`);
2102
+ }
2029
2103
  const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
2030
2104
  const cleanDomain = slugify(card.domain || "");
2031
2105
  const cleanBase = slugify(baseText);
@@ -2056,11 +2130,19 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2056
2130
  bloom_level: bloom,
2057
2131
  context: card.excerpt || "",
2058
2132
  symbiosis_mode: symbiosisMode,
2059
- question: card.question || null
2133
+ question: card.question || null,
2134
+ provider: card.provider || null,
2135
+ topic_id: card.topic_id || null
2060
2136
  });
2061
2137
  createdCount++;
2062
2138
  } else {
2063
2139
  linkedCount++;
2140
+ if (!token.provider && card.provider) {
2141
+ await updateToken(tx, token.slug, {
2142
+ provider: card.provider,
2143
+ topic_id: card.topic_id || null
2144
+ });
2145
+ }
2064
2146
  }
2065
2147
  const existingCard = await getCard(tx, token.id, userId);
2066
2148
  if (!existingCard) {
@@ -2072,7 +2154,12 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
2072
2154
  ON CONFLICT(token_id, source_id) DO UPDATE SET
2073
2155
  excerpt = excluded.excerpt,
2074
2156
  page_number = excluded.page_number`
2075
- ).run(token.id, sourceId, card.excerpt || "", card.page_number || null);
2157
+ ).run(
2158
+ token.id,
2159
+ cardSourceId,
2160
+ card.excerpt || "",
2161
+ card.page_number || null
2162
+ );
2076
2163
  }
2077
2164
  });
2078
2165
  return { createdCount, linkedCount };
@@ -5082,12 +5169,7 @@ function installFastFlowLM() {
5082
5169
  }
5083
5170
  }
5084
5171
  function installOllama() {
5085
- const isMac = process.platform === "darwin";
5086
- const isWin = process.platform === "win32";
5087
- const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
5088
- join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
5089
- );
5090
- if (hasOllama) {
5172
+ if (isOllamaInstalled()) {
5091
5173
  return { success: true, message: "Ollama is already installed." };
5092
5174
  }
5093
5175
  if (process.platform === "darwin") {
@@ -5142,19 +5224,23 @@ function installOllama() {
5142
5224
  }
5143
5225
  }
5144
5226
  }
5145
- function resolveOllamaCommand() {
5146
- if (hasCommand("ollama")) return "ollama";
5147
- const candidates = process.platform === "win32" ? [
5148
- join10(
5149
- homedir7(),
5150
- "AppData",
5151
- "Local",
5152
- "Programs",
5153
- "Ollama",
5154
- "ollama.exe"
5155
- )
5156
- ] : process.platform === "darwin" ? ["/Applications/Ollama.app/Contents/Resources/ollama"] : [];
5157
- return candidates.find((candidate) => existsSync9(candidate));
5227
+ function resolveOllamaCommand(options = {}) {
5228
+ const platform = options.platform ?? process.platform;
5229
+ const homeDir = options.homeDir ?? homedir7();
5230
+ const commandAvailable = options.commandAvailable ?? hasCommand;
5231
+ const pathExists = options.pathExists ?? existsSync9;
5232
+ if (commandAvailable("ollama")) return "ollama";
5233
+ const candidates = platform === "win32" ? [join10(homeDir, "AppData", "Local", "Programs", "Ollama", "ollama.exe")] : platform === "darwin" ? [
5234
+ "/opt/homebrew/bin/ollama",
5235
+ "/usr/local/bin/ollama",
5236
+ "/Applications/Ollama.app/Contents/Resources/ollama"
5237
+ ] : ["/usr/local/bin/ollama", "/usr/bin/ollama"];
5238
+ return candidates.find((candidate) => pathExists(candidate));
5239
+ }
5240
+ function isOllamaInstalled(options = {}) {
5241
+ const platform = options.platform ?? process.platform;
5242
+ const pathExists = options.pathExists ?? existsSync9;
5243
+ return resolveOllamaCommand(options) !== void 0 || platform === "darwin" && pathExists("/Applications/Ollama.app");
5158
5244
  }
5159
5245
  function prepareLocalModel(runner, model) {
5160
5246
  if (runner === "fastflowlm") {
@@ -5628,6 +5714,7 @@ export {
5628
5714
  installOpenCode,
5629
5715
  interleave,
5630
5716
  isObserverPolicyConfigured,
5717
+ isOllamaInstalled,
5631
5718
  isUiObservationReport,
5632
5719
  listAgentSkills,
5633
5720
  listGoals,
@@ -5665,6 +5752,7 @@ export {
5665
5752
  resolveAllBeliefPaths,
5666
5753
  resolveAllGoalPaths,
5667
5754
  resolveObserverPolicy,
5755
+ resolveOllamaCommand,
5668
5756
  resolveReference,
5669
5757
  resolveRepoPath,
5670
5758
  resolveReviewContext,