threadnote 1.1.1 → 1.1.3

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.
@@ -3473,6 +3473,7 @@ var {
3473
3473
  var DEFAULT_HOST = "127.0.0.1";
3474
3474
  var DEFAULT_PORT = 1933;
3475
3475
  var DEFAULT_OPENVIKING_VERSION = "0.3.24";
3476
+ var OPENVIKING_TOOL_PYTHON = "3.12";
3476
3477
  var DEFAULT_ACCOUNT = "local";
3477
3478
  var DEFAULT_AGENT_ID = "threadnote";
3478
3479
  var OPENVIKING_PACKAGE_NAME = "openviking[local-embed]";
@@ -4315,7 +4316,13 @@ function recallSnippet(value) {
4315
4316
  const oneLine = value.replace(/\s+/g, " ").trim();
4316
4317
  return oneLine.length > 180 ? `${oneLine.slice(0, 180)}\u2026` : oneLine;
4317
4318
  }
4318
- function parseRecallHits(output2) {
4319
+ function isArchivedMemoryUri(uri) {
4320
+ const documentUri = uri.replace(/#.*$/, "");
4321
+ return /^viking:\/\/user\/[^/]+\/memories\/(?:durable|handoffs|incidents|preferences|smoke)\/archived(?:\/|$)/.test(
4322
+ documentUri
4323
+ );
4324
+ }
4325
+ function parseRecallHits(output2, options = {}) {
4319
4326
  const start = output2.search(/^\{/m);
4320
4327
  if (start < 0) {
4321
4328
  return [];
@@ -4336,6 +4343,9 @@ function parseRecallHits(output2) {
4336
4343
  if (!isJsonObject(item) || typeof item.uri !== "string" || isSummarySidecarUri(item.uri)) {
4337
4344
  continue;
4338
4345
  }
4346
+ if (options.includeArchived !== true && isArchivedMemoryUri(item.uri)) {
4347
+ continue;
4348
+ }
4339
4349
  hits.push({
4340
4350
  contextType: typeof item.context_type === "string" ? item.context_type : "result",
4341
4351
  score: typeof item.score === "number" ? item.score : 0,
@@ -8447,6 +8457,9 @@ var import_node_path6 = require("node:path");
8447
8457
  var TEAMS_FILE_VERSION = 1;
8448
8458
  var SHARED_SEGMENT = "shared";
8449
8459
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
8460
+ var SHAREABLE_ARTIFACT_DIR = "agent-artifacts";
8461
+ var SHAREABLE_TOP_LEVEL_DIRS = [...SHAREABLE_MEMORY_KIND_DIRS, SHAREABLE_ARTIFACT_DIR];
8462
+ var ARTIFACT_INSTALL_METADATA_VERSION = 1;
8450
8463
  var AUTO_SHARE_FETCH_INTERVAL_MS = 5 * 60 * 1e3;
8451
8464
  var DEFAULT_GIT_REMOTE_NAME = "origin";
8452
8465
  var SCRUBBER_PATTERNS = [
@@ -8538,7 +8551,7 @@ async function runShareInit(config, remoteUrl, options) {
8538
8551
  if (!dryRun) {
8539
8552
  await ensureSharedGitignore(worktree, git, options.push !== false);
8540
8553
  const ingested = await ingestWorktreeFiles(config, newConfig, "create");
8541
- console.log(`Ingested ${ingested} shared memory file(s) into OpenViking.`);
8554
+ console.log(`Ingested ${ingested} shared file(s) into OpenViking.`);
8542
8555
  }
8543
8556
  }
8544
8557
  var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
@@ -8865,7 +8878,7 @@ async function runShareSyncQuiet(config, state, options) {
8865
8878
  return void 0;
8866
8879
  }
8867
8880
  async function stageShareableChanges(dryRun, git, worktree) {
8868
- const pathspecs = [":(top)README.md", ":(top).gitignore", ...SHAREABLE_MEMORY_KIND_DIRS.map((dir) => `:(top)${dir}`)];
8881
+ const pathspecs = [":(top)README.md", ":(top).gitignore", ...SHAREABLE_TOP_LEVEL_DIRS.map((dir) => `:(top)${dir}`)];
8869
8882
  await maybeRun(dryRun, git, ["-C", worktree, "add", "--", ...pathspecs], { allowFailure: true });
8870
8883
  }
8871
8884
  async function publishShareGitChange(worktree, relativePath, commitMessage, options = {}) {
@@ -8987,6 +9000,157 @@ ${err instanceof Error ? err.message : String(err)}`,
8987
9000
  }
8988
9001
  console.log(`Published ${sourceUri} -> ${targetUri}`);
8989
9002
  }
9003
+ async function runSharePublishArtifact(config, sourcePath, options) {
9004
+ const result = await shareAgentArtifact(config, sourcePath, options);
9005
+ printShareArtifactResult(result, options.preview === true);
9006
+ }
9007
+ async function shareAgentArtifact(config, sourcePath, options) {
9008
+ const team = await resolveTeam(config, options.team);
9009
+ const dryRun = options.dryRun === true;
9010
+ const preview = options.preview === true;
9011
+ const resolvedSourcePath = expandPath(sourcePath);
9012
+ if (!await isRegularFileNoSymlink(resolvedSourcePath)) {
9013
+ throw new Error(`Agent artifact source is not a regular file: ${resolvedSourcePath}`);
9014
+ }
9015
+ const artifact = inferShareArtifact(resolvedSourcePath, options);
9016
+ const rawContent = await (0, import_promises5.readFile)(resolvedSourcePath, "utf8");
9017
+ if (!rawContent.trim()) {
9018
+ throw new Error(`Refusing to share empty agent artifact: ${resolvedSourcePath}`);
9019
+ }
9020
+ const scrub = applyScrubber(rawContent, { redact: options.redact === true });
9021
+ const relativePath = sharedArtifactRelativePath(artifact);
9022
+ const targetPath = (0, import_node_path6.join)(team.config.worktree, ...relativePath.split("/"));
9023
+ const targetUri = workfileToVikingUri(config, team.config, targetPath);
9024
+ const messages = [
9025
+ `${preview ? "Previewing" : dryRun ? "Would share" : "Sharing"} ${artifact.kind} ${artifact.agent}/${artifact.name}`,
9026
+ `Source: ${portablePath(resolvedSourcePath)}`,
9027
+ `Destination: ${targetUri}`
9028
+ ];
9029
+ if (preview) {
9030
+ if (scrub.blocker) {
9031
+ messages.push(`PREVIEW BLOCKED: ${scrub.blocker}. Strip the sensitive value or pass --redact.`);
9032
+ return {
9033
+ artifact,
9034
+ gitMessages: [],
9035
+ messages,
9036
+ sourcePath: resolvedSourcePath,
9037
+ targetPath,
9038
+ targetUri
9039
+ };
9040
+ }
9041
+ for (const redaction of scrub.redactions) {
9042
+ messages.push(`PREVIEW redact: ${redaction.count}\xD7 ${redaction.name}`);
9043
+ }
9044
+ return {
9045
+ artifact,
9046
+ gitMessages: [],
9047
+ messages,
9048
+ previewContent: scrub.cleaned,
9049
+ sourcePath: resolvedSourcePath,
9050
+ targetPath,
9051
+ targetUri
9052
+ };
9053
+ }
9054
+ if (scrub.blocker) {
9055
+ throw new Error(
9056
+ `Refusing to share ${resolvedSourcePath}: possible ${scrub.blocker}. Strip the sensitive value or pass --redact for soft-leak patterns.`
9057
+ );
9058
+ }
9059
+ for (const redaction of scrub.redactions) {
9060
+ messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before sharing.`);
9061
+ }
9062
+ const content = scrub.cleaned;
9063
+ const existingContent = await readFileIfExists(targetPath) ?? void 0;
9064
+ if (existingContent !== void 0 && existingContent !== content && options.force !== true) {
9065
+ throw new Error(
9066
+ `Shared artifact already exists with different content: ${portablePath(targetPath)}. Pass --force to replace it.`
9067
+ );
9068
+ }
9069
+ if (dryRun) {
9070
+ messages.push(`Would write shared artifact: ${portablePath(targetPath)}`);
9071
+ }
9072
+ const ov = await openVikingCliForMode(dryRun);
9073
+ const ovHasResource = !dryRun && await vikingResourceExists(ov, config, targetUri);
9074
+ await ensureSharedDirectoryChain(config, ov, targetUri, dryRun, { quiet: true });
9075
+ await writeMemoryFile(config, ov, targetUri, content, ovHasResource ? "replace" : "create", dryRun, { quiet: true });
9076
+ const message = options.message ?? `share: publish ${relativePath}`;
9077
+ const gitMessages = await publishShareGitChange(team.config.worktree, relativePath, message, {
9078
+ dryRun,
9079
+ push: options.push
9080
+ });
9081
+ return { artifact, gitMessages, messages, sourcePath: resolvedSourcePath, targetPath, targetUri };
9082
+ }
9083
+ async function runShareInstallArtifacts(config, options) {
9084
+ const result = await installSharedAgentArtifacts(config, options);
9085
+ if (result.syncedTeams.length > 0) {
9086
+ console.log(`Synced shared teams: ${result.syncedTeams.join(", ")}`);
9087
+ }
9088
+ for (const warning2 of result.warnings) {
9089
+ console.warn(`Warning: ${warning2}`);
9090
+ }
9091
+ for (const message of result.messages) {
9092
+ console.log(message);
9093
+ }
9094
+ }
9095
+ async function installSharedAgentArtifacts(config, options) {
9096
+ const syncResult = await maybeSyncSharedArtifacts(config, options);
9097
+ const team = await resolveTeam(config, options.team);
9098
+ const dryRun = options.dryRun === true || options.apply !== true;
9099
+ const allArtifacts = await collectSharedArtifacts(team.config.worktree, team.name);
9100
+ const artifacts = filterSharedArtifacts(allArtifacts, options);
9101
+ const messages = [];
9102
+ if (artifacts.length === 0) {
9103
+ const filters = sharedArtifactFilterLabel(options);
9104
+ if (filters) {
9105
+ throw new Error(`No shared agent artifacts found for team "${team.name}" matching ${filters}.`);
9106
+ }
9107
+ return {
9108
+ installedCount: 0,
9109
+ messages: [`No shared agent artifacts found for team "${team.name}".`],
9110
+ syncedTeams: syncResult.syncedTeams,
9111
+ team: team.name,
9112
+ warnings: syncResult.warnings
9113
+ };
9114
+ }
9115
+ if (options.name !== void 0 && artifacts.length > 1 && (options.agent === void 0 || options.kind === void 0)) {
9116
+ throw new Error(
9117
+ `Shared artifact "${options.name}" is ambiguous. Specify agent and kind. Matches: ${artifacts.map((artifact) => sharedArtifactLabel(artifact.artifact)).join(", ")}`
9118
+ );
9119
+ }
9120
+ let installedCount = 0;
9121
+ for (const artifact of artifacts) {
9122
+ const label = sharedArtifactLabel(artifact.artifact);
9123
+ const state = await sharedArtifactInstallState(artifact);
9124
+ if (dryRun) {
9125
+ const verb = sharedArtifactDryRunVerb(state.status, options.force === true);
9126
+ const suffix = sharedArtifactDryRunSuffix(state.status, options.force === true);
9127
+ messages.push(`${verb} ${label}: ${portablePath(artifact.installPath)}${suffix}`);
9128
+ continue;
9129
+ }
9130
+ if ((state.status === "local_modified" || state.status === "remote_changed_and_local_modified") && options.force !== true) {
9131
+ throw new Error(`Refusing to overwrite ${portablePath(artifact.installPath)}. Pass force=true or --force.`);
9132
+ }
9133
+ if (state.status === "current") {
9134
+ await writeSharedArtifactMetadata(artifact, state.sourceSha);
9135
+ messages.push(`Already installed ${label}: ${portablePath(artifact.installPath)}`);
9136
+ continue;
9137
+ }
9138
+ await ensureDirectory((0, import_node_path6.dirname)(artifact.installPath), false);
9139
+ await (0, import_promises5.writeFile)(artifact.installPath, state.sourceContent, { encoding: "utf8", mode: 384 });
9140
+ await writeSharedArtifactMetadata(artifact, state.sourceSha);
9141
+ installedCount += 1;
9142
+ messages.push(
9143
+ `${sharedArtifactInstallVerb(state.status, options.force === true)} ${label}: ${portablePath(artifact.installPath)}`
9144
+ );
9145
+ }
9146
+ return {
9147
+ installedCount,
9148
+ messages,
9149
+ syncedTeams: syncResult.syncedTeams,
9150
+ team: team.name,
9151
+ warnings: syncResult.warnings
9152
+ };
9153
+ }
8990
9154
  async function runShareUnpublish(config, sourceUri, options) {
8991
9155
  assertVikingUri(sourceUri);
8992
9156
  const team = await resolveTeam(config, options.team);
@@ -9079,7 +9243,7 @@ async function runShareRename(config, options) {
9079
9243
  console.log(`Would rename worktree: ${portablePath(oldTeam.config.worktree)} -> ${portablePath(newWorktree)}`);
9080
9244
  console.log(`Would rename gitdir: ${portablePath(oldTeam.config.gitdir)} -> ${portablePath(newGitdir)}`);
9081
9245
  console.log(`Would update git core.worktree for team "${newName}".`);
9082
- console.log(`Would reindex shared memories under team "${newName}" and remove old shared URI tree.`);
9246
+ console.log(`Would reindex shared context under team "${newName}" and remove old shared URI tree.`);
9083
9247
  console.log(`Would write teams file: ${teamsFilePath(config)}`);
9084
9248
  return;
9085
9249
  }
@@ -9097,7 +9261,7 @@ async function runShareRename(config, options) {
9097
9261
  false
9098
9262
  );
9099
9263
  console.log(`Renamed shared team "${oldTeam.name}" -> "${newName}".`);
9100
- console.log(`Reindexed ${ingested} shared memory file(s).`);
9264
+ console.log(`Reindexed ${ingested} shared file(s).`);
9101
9265
  }
9102
9266
  async function runShareSetUrl(config, remoteUrl, options) {
9103
9267
  if (!remoteUrl.trim()) {
@@ -9340,7 +9504,7 @@ async function walkMemoryFiles(root) {
9340
9504
  }
9341
9505
  const full = (0, import_node_path6.join)(path, entry.name);
9342
9506
  if (entry.isDirectory()) {
9343
- if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
9507
+ if (depth === 0 && !SHAREABLE_TOP_LEVEL_DIRS.includes(entry.name)) {
9344
9508
  continue;
9345
9509
  }
9346
9510
  await visit(full, depth + 1);
@@ -9422,6 +9586,264 @@ function vikingUriToWorktreeRelative(config, uri, team) {
9422
9586
  }
9423
9587
  return uri.slice(prefix.length);
9424
9588
  }
9589
+ async function isRegularFileNoSymlink(path) {
9590
+ try {
9591
+ const stat5 = await (0, import_promises5.lstat)(path);
9592
+ return stat5.isFile();
9593
+ } catch (_err) {
9594
+ return false;
9595
+ }
9596
+ }
9597
+ function inferShareArtifact(path, options) {
9598
+ const normalizedPath = path.split(import_node_path6.sep).join("/");
9599
+ const fileName = (0, import_node_path6.basename)(path);
9600
+ const lowerFileName = fileName.toLowerCase();
9601
+ const lowerPath = normalizedPath.toLowerCase();
9602
+ const inferredKind = lowerFileName === "skill.md" ? "skill" : lowerPath.includes("/.claude/commands/") && lowerFileName.endsWith(".md") ? "command" : void 0;
9603
+ const inferredAgent = lowerPath.includes("/.codex/skills/") ? "codex" : lowerPath.includes("/.claude/skills/") || lowerPath.includes("/.claude/commands/") ? "claude" : void 0;
9604
+ const extensionIndex = fileName.lastIndexOf(".");
9605
+ const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
9606
+ const inferredName = lowerFileName === "skill.md" ? (0, import_node_path6.basename)((0, import_node_path6.dirname)(path)) : stem;
9607
+ const kind = options.kind ?? inferredKind;
9608
+ const agent = options.agent ?? inferredAgent;
9609
+ const name = options.name ?? inferredName;
9610
+ if (kind !== "skill" && kind !== "command") {
9611
+ throw new Error("Could not infer artifact kind. Pass --kind skill or --kind command.");
9612
+ }
9613
+ if (agent !== "codex" && agent !== "claude") {
9614
+ throw new Error("Could not infer artifact agent. Pass --agent codex or --agent claude.");
9615
+ }
9616
+ if (kind === "skill" && lowerFileName !== "skill.md") {
9617
+ throw new Error("Skill artifacts must point at a SKILL.md file.");
9618
+ }
9619
+ if (kind === "command" && !lowerFileName.endsWith(".md")) {
9620
+ throw new Error("Command artifacts must be Markdown files.");
9621
+ }
9622
+ if (kind === "command" && agent !== "claude") {
9623
+ throw new Error("Only Claude command artifacts are supported.");
9624
+ }
9625
+ if (name.trim().length === 0) {
9626
+ throw new Error("Artifact name cannot be empty.");
9627
+ }
9628
+ return { agent, kind, name: uriSegment(name) };
9629
+ }
9630
+ function sharedArtifactRelativePath(artifact) {
9631
+ if (artifact.kind === "skill") {
9632
+ return `${SHAREABLE_ARTIFACT_DIR}/skills/${artifact.agent}/${artifact.name}/SKILL.md`;
9633
+ }
9634
+ return `${SHAREABLE_ARTIFACT_DIR}/commands/${artifact.agent}/${artifact.name}.md`;
9635
+ }
9636
+ function sharedArtifactFromRelativePath(relativePath) {
9637
+ const parts = relativePath.split("/");
9638
+ if (parts[0] !== SHAREABLE_ARTIFACT_DIR) {
9639
+ return void 0;
9640
+ }
9641
+ if (parts.length === 5 && parts[1] === "skills" && (parts[2] === "codex" || parts[2] === "claude") && parts[4] === "SKILL.md") {
9642
+ return { agent: parts[2], kind: "skill", name: parts[3] };
9643
+ }
9644
+ if (parts.length === 4 && parts[1] === "commands" && parts[2] === "claude" && parts[3].endsWith(".md")) {
9645
+ return { agent: "claude", kind: "command", name: parts[3].slice(0, -".md".length) };
9646
+ }
9647
+ return void 0;
9648
+ }
9649
+ async function collectSharedArtifacts(worktree, team) {
9650
+ const root = (0, import_node_path6.join)(worktree, SHAREABLE_ARTIFACT_DIR);
9651
+ if (!await isDirectory(root)) {
9652
+ return [];
9653
+ }
9654
+ const out = [];
9655
+ async function visit(path) {
9656
+ const entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
9657
+ for (const entry of entries) {
9658
+ const full = (0, import_node_path6.join)(path, entry.name);
9659
+ if (entry.isDirectory()) {
9660
+ await visit(full);
9661
+ continue;
9662
+ }
9663
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
9664
+ continue;
9665
+ }
9666
+ const relativePath = (0, import_node_path6.relative)(worktree, full).split(import_node_path6.sep).join("/");
9667
+ const artifact = sharedArtifactFromRelativePath(relativePath);
9668
+ if (artifact === void 0) {
9669
+ continue;
9670
+ }
9671
+ out.push({
9672
+ artifact,
9673
+ installPath: sharedArtifactInstallPath(team, artifact),
9674
+ sourcePath: full,
9675
+ sourceRelativePath: relativePath,
9676
+ team
9677
+ });
9678
+ }
9679
+ }
9680
+ await visit(root);
9681
+ return out.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
9682
+ }
9683
+ function filterSharedArtifacts(artifacts, options) {
9684
+ const name = options.name === void 0 ? void 0 : uriSegment(options.name);
9685
+ return artifacts.filter((artifact) => {
9686
+ if (options.agent !== void 0 && artifact.artifact.agent !== options.agent) {
9687
+ return false;
9688
+ }
9689
+ if (options.kind !== void 0 && artifact.artifact.kind !== options.kind) {
9690
+ return false;
9691
+ }
9692
+ if (name !== void 0 && artifact.artifact.name !== name) {
9693
+ return false;
9694
+ }
9695
+ return true;
9696
+ });
9697
+ }
9698
+ async function maybeSyncSharedArtifacts(config, options) {
9699
+ if (options.sync === false) {
9700
+ return { syncedTeams: [], warnings: [] };
9701
+ }
9702
+ return syncSharedReposBeforeAgentRead(config);
9703
+ }
9704
+ async function sharedArtifactInstallState(artifact) {
9705
+ const sourceContent = await (0, import_promises5.readFile)(artifact.sourcePath, "utf8");
9706
+ const sourceSha = sha256(sourceContent);
9707
+ const existingContent = await readFileIfExists(artifact.installPath) ?? void 0;
9708
+ if (existingContent === void 0) {
9709
+ return { sourceContent, sourceSha, status: "not_installed" };
9710
+ }
9711
+ const existingSha = sha256(existingContent);
9712
+ const metadata = await readSharedArtifactMetadata(artifact);
9713
+ if (existingSha === sourceSha) {
9714
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "current" };
9715
+ }
9716
+ if (metadata === void 0) {
9717
+ return { existingContent, existingSha, sourceContent, sourceSha, status: "local_modified" };
9718
+ }
9719
+ const remoteChanged = metadata.sourceSha256 !== sourceSha;
9720
+ const localChanged = metadata.installedSha256 !== existingSha;
9721
+ if (remoteChanged && localChanged) {
9722
+ return {
9723
+ existingContent,
9724
+ existingSha,
9725
+ metadata,
9726
+ sourceContent,
9727
+ sourceSha,
9728
+ status: "remote_changed_and_local_modified"
9729
+ };
9730
+ }
9731
+ if (remoteChanged) {
9732
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "update_available" };
9733
+ }
9734
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "local_modified" };
9735
+ }
9736
+ async function readSharedArtifactMetadata(artifact) {
9737
+ const raw = await readFileIfExists(sharedArtifactMetadataPath(artifact));
9738
+ if (raw === void 0) {
9739
+ return void 0;
9740
+ }
9741
+ const parsed = parseJsonConfigObject(raw);
9742
+ if (parsed === void 0 || parsed.version !== ARTIFACT_INSTALL_METADATA_VERSION) {
9743
+ return void 0;
9744
+ }
9745
+ const artifactValue = parsed.artifact;
9746
+ if (typeof parsed.team !== "string" || typeof parsed.source !== "string" || typeof parsed.sourceSha256 !== "string" || typeof parsed.installedSha256 !== "string" || typeof parsed.installedAt !== "string" || typeof artifactValue !== "object" || artifactValue === null || Array.isArray(artifactValue)) {
9747
+ return void 0;
9748
+ }
9749
+ const metadataArtifact = artifactValue;
9750
+ if (metadataArtifact.agent !== artifact.artifact.agent || metadataArtifact.kind !== artifact.artifact.kind || metadataArtifact.name !== artifact.artifact.name) {
9751
+ return void 0;
9752
+ }
9753
+ return {
9754
+ artifact: artifact.artifact,
9755
+ installedAt: parsed.installedAt,
9756
+ installedSha256: parsed.installedSha256,
9757
+ source: parsed.source,
9758
+ sourceSha256: parsed.sourceSha256,
9759
+ team: parsed.team,
9760
+ version: ARTIFACT_INSTALL_METADATA_VERSION
9761
+ };
9762
+ }
9763
+ async function writeSharedArtifactMetadata(artifact, sourceSha) {
9764
+ const metadata = {
9765
+ artifact: artifact.artifact,
9766
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9767
+ installedSha256: sourceSha,
9768
+ source: artifact.sourceRelativePath,
9769
+ sourceSha256: sourceSha,
9770
+ team: artifact.team,
9771
+ version: ARTIFACT_INSTALL_METADATA_VERSION
9772
+ };
9773
+ const metadataPath = sharedArtifactMetadataPath(artifact);
9774
+ await ensureDirectory((0, import_node_path6.dirname)(metadataPath), false);
9775
+ await (0, import_promises5.writeFile)(metadataPath, `${JSON.stringify(metadata, void 0, 2)}
9776
+ `, { encoding: "utf8", mode: 384 });
9777
+ }
9778
+ function sharedArtifactMetadataPath(artifact) {
9779
+ return `${artifact.installPath}.threadnote-install.json`;
9780
+ }
9781
+ function sharedArtifactDryRunVerb(status, force) {
9782
+ switch (status) {
9783
+ case "not_installed":
9784
+ return "Would install";
9785
+ case "current":
9786
+ return "Already installed";
9787
+ case "update_available":
9788
+ return "Would update";
9789
+ case "local_modified":
9790
+ case "remote_changed_and_local_modified":
9791
+ return force ? "Would replace" : "Would skip modified";
9792
+ }
9793
+ }
9794
+ function sharedArtifactDryRunSuffix(status, force) {
9795
+ if ((status === "local_modified" || status === "remote_changed_and_local_modified") && !force) {
9796
+ return " (pass --force to replace local changes)";
9797
+ }
9798
+ return "";
9799
+ }
9800
+ function sharedArtifactInstallVerb(status, force) {
9801
+ if (force && (status === "local_modified" || status === "remote_changed_and_local_modified")) {
9802
+ return "Replaced";
9803
+ }
9804
+ if (status === "update_available") {
9805
+ return "Updated";
9806
+ }
9807
+ return "Installed";
9808
+ }
9809
+ function sharedArtifactFilterLabel(options) {
9810
+ const filters = [];
9811
+ if (options.kind !== void 0) {
9812
+ filters.push(`kind=${options.kind}`);
9813
+ }
9814
+ if (options.agent !== void 0) {
9815
+ filters.push(`agent=${options.agent}`);
9816
+ }
9817
+ if (options.name !== void 0) {
9818
+ filters.push(`name=${uriSegment(options.name)}`);
9819
+ }
9820
+ return filters.join(", ");
9821
+ }
9822
+ function sharedArtifactLabel(artifact) {
9823
+ return `${artifact.kind} ${artifact.agent}/${artifact.name}`;
9824
+ }
9825
+ function sharedArtifactInstallPath(team, artifact) {
9826
+ if (artifact.kind === "skill" && artifact.agent === "codex") {
9827
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".codex", "skills", "threadnote", team, artifact.name, "SKILL.md");
9828
+ }
9829
+ if (artifact.kind === "skill" && artifact.agent === "claude") {
9830
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".claude", "skills", "threadnote", team, artifact.name, "SKILL.md");
9831
+ }
9832
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".claude", "commands", "threadnote", team, `${artifact.name}.md`);
9833
+ }
9834
+ function printShareArtifactResult(result, preview) {
9835
+ for (const message of result.messages) {
9836
+ console.log(message);
9837
+ }
9838
+ for (const gitMessage of result.gitMessages) {
9839
+ console.log(gitMessage);
9840
+ }
9841
+ if (preview && result.previewContent !== void 0) {
9842
+ console.log("-----BEGIN PREVIEW-----");
9843
+ console.log(result.previewContent);
9844
+ console.log("-----END PREVIEW-----");
9845
+ }
9846
+ }
9425
9847
  function stripPersonalProvenance(content) {
9426
9848
  const lines = content.split("\n");
9427
9849
  let headerEnd = lines.length;
@@ -9470,9 +9892,9 @@ async function ensureSharedDirectoryChain(config, ov, memoryUri, dryRun, options
9470
9892
  continue;
9471
9893
  }
9472
9894
  if (options.quiet === true) {
9473
- await runCommand(ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared memories."]));
9895
+ await runCommand(ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared context."]));
9474
9896
  } else {
9475
- await maybeRun(false, ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared memories."]));
9897
+ await maybeRun(false, ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared context."]));
9476
9898
  }
9477
9899
  }
9478
9900
  }
@@ -9701,7 +10123,7 @@ async function applyChangesToOpenViking(config, team, changes, options = {}) {
9701
10123
  continue;
9702
10124
  }
9703
10125
  const firstSegment = change.relativePath.split("/")[0];
9704
- if (!SHAREABLE_MEMORY_KIND_DIRS.includes(firstSegment)) {
10126
+ if (!SHAREABLE_TOP_LEVEL_DIRS.includes(firstSegment)) {
9705
10127
  continue;
9706
10128
  }
9707
10129
  const uri = workfileToVikingUri(config, team, change.path);
@@ -9960,14 +10382,17 @@ async function runRecall(config, options) {
9960
10382
  if (inferredUri) {
9961
10383
  console.log(`Recall scope: ${inferredUri}`);
9962
10384
  }
9963
- const passes = [await recallSearchHits(config, ov, searchArgs(inferredUri), { dryRun })];
10385
+ const includeArchived = options.includeArchived === true;
10386
+ const passes = [
10387
+ await recallSearchHits(config, ov, searchArgs(inferredUri), { dryRun, includeArchived })
10388
+ ];
9964
10389
  if (options.project && project) {
9965
10390
  const projectMemoryUri = `viking://user/${uriSegment(config.user)}/memories/durable/projects/${uriSegment(project.name)}`;
9966
- passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun }));
10391
+ passes.push(await recallSearchHits(config, ov, searchArgs(projectMemoryUri), { dryRun, includeArchived }));
9967
10392
  }
9968
10393
  const seededUri = project ? trimTrailingSlash(project.uri) : void 0;
9969
10394
  if (seededUri?.startsWith("viking://") && seededUri !== inferredUri && !options.uri && options.inferScope !== false) {
9970
- passes.push(await recallSearchHits(config, ov, searchArgs(seededUri), { dryRun }));
10395
+ passes.push(await recallSearchHits(config, ov, searchArgs(seededUri), { dryRun, includeArchived }));
9971
10396
  }
9972
10397
  const recallOutputs = [];
9973
10398
  const semanticSection = formatRecallHits(mergeRecallHits(passes), nodeLimit ?? 12);
@@ -9978,7 +10403,7 @@ ${semanticSection}`);
9978
10403
  }
9979
10404
  const exactOutput = await printExactMemoryMatches(config, ov, query, {
9980
10405
  dryRun,
9981
- includeArchived: options.includeArchived === true,
10406
+ includeArchived,
9982
10407
  project
9983
10408
  });
9984
10409
  if (exactOutput) {
@@ -10002,7 +10427,7 @@ async function recallSearchHits(config, ov, args, options) {
10002
10427
  console.log(`WARN recall search failed: ${result.stderr.trim() || result.stdout.trim() || "ov search error"}`);
10003
10428
  return [];
10004
10429
  }
10005
- return parseRecallHits(result.stdout);
10430
+ return parseRecallHits(result.stdout, { includeArchived: options.includeArchived });
10006
10431
  }
10007
10432
  function stripAdvancedSearchFlags(args) {
10008
10433
  const stripped = [];
@@ -12442,6 +12867,14 @@ async function runInstall(config, options) {
12442
12867
  serverInstallRan = true;
12443
12868
  }
12444
12869
  const resolvedServerPath = serverInstallRan ? await findOpenVikingServer() : serverPath;
12870
+ if (serverInstallRan && !resolvedServerPath && !dryRun) {
12871
+ const message = `OpenViking install ran but ${OPENVIKING_SERVER_COMMAND} was not found on PATH, in the uv tool bin dir, or ~/.local/bin. Re-run \`threadnote install --force\` (it streams the full build), then \`threadnote doctor\`.`;
12872
+ if (options.requireServerBinary === false) {
12873
+ console.warn(`WARN ${message}`);
12874
+ } else {
12875
+ throw new Error(message);
12876
+ }
12877
+ }
12445
12878
  if (resolvedServerPath && !dryRun) {
12446
12879
  await maybePrintOpenVikingPathHint(resolvedServerPath);
12447
12880
  }
@@ -12478,6 +12911,7 @@ async function runRepair(config, options) {
12478
12911
  packageManager: options.packageManager,
12479
12912
  printNextSteps: false,
12480
12913
  repairInvalidConfigs: true,
12914
+ requireServerBinary: false,
12481
12915
  start: false
12482
12916
  });
12483
12917
  await repairManifest(config, dryRun);
@@ -12832,7 +13266,11 @@ async function openVikingServerCheck() {
12832
13266
  const name = OPENVIKING_SERVER_COMMAND;
12833
13267
  const executable = await findOpenVikingServer();
12834
13268
  if (!executable) {
12835
- return { name, status: "fail", detail: "missing; install will fetch it via uv or pipx" };
13269
+ return {
13270
+ name,
13271
+ status: "fail",
13272
+ detail: "missing; run `threadnote install` to fetch it via uv or pipx (local-embed may compile from source on first install)"
13273
+ };
12836
13274
  }
12837
13275
  const result = await runCommand(executable, ["--help"], { allowFailure: true });
12838
13276
  const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
@@ -13066,7 +13504,36 @@ async function runInstallCommands(config, preferred, force, dryRun) {
13066
13504
  }
13067
13505
  const installCommands = await getInstallCommands(config, manager, force);
13068
13506
  for (const installCommand of installCommands) {
13069
- await maybeRun(dryRun, installCommand.executable, installCommand.args);
13507
+ if (dryRun) {
13508
+ await maybeRun(true, installCommand.executable, installCommand.args);
13509
+ continue;
13510
+ }
13511
+ console.log(`Running: ${formatShellCommand(installCommand.executable, installCommand.args)}`);
13512
+ const exitCode = await runInteractive(installCommand.executable, installCommand.args);
13513
+ if (exitCode !== 0) {
13514
+ printOpenVikingInstallFailureHelp(installCommand);
13515
+ throw new Error(`${formatShellCommand(installCommand.executable, installCommand.args)} exited with ${exitCode}.`);
13516
+ }
13517
+ }
13518
+ }
13519
+ function printOpenVikingInstallFailureHelp(failedCommand) {
13520
+ console.error("");
13521
+ console.error("OpenViking install did not complete.");
13522
+ console.error(
13523
+ "openviking[local-embed] includes llama-cpp-python, which compiles from source when no prebuilt wheel matches your Python/platform \u2014 that build can run 10-20 minutes and is memory-heavy, so it may be killed by the OS (out of memory) or look stuck."
13524
+ );
13525
+ console.error("Re-run it directly to see full output without any wrapper timeout:");
13526
+ console.error(` ${formatShellCommand(failedCommand.executable, failedCommand.args)}`);
13527
+ console.error("Cap memory use during the compile with: CMAKE_BUILD_PARALLEL_LEVEL=2 <command above>");
13528
+ if (failedCommand.executable === "uv") {
13529
+ if (failedCommand.args.includes("--python")) {
13530
+ console.error(
13531
+ "If uv could not fetch a managed CPython (offline or restricted network), drop the version pin and retry: THREADNOTE_OPENVIKING_PYTHON= threadnote install --force"
13532
+ );
13533
+ }
13534
+ console.error("If it was killed mid-build, clear the partial install first, then retry:");
13535
+ console.error(" uv cache clean");
13536
+ console.error(' rm -rf "$(uv tool dir)/openviking"');
13070
13537
  }
13071
13538
  }
13072
13539
  async function offerToInstallUv() {
@@ -13147,12 +13614,33 @@ async function getPythonSystemCertificatesInstallCommand(serverPath) {
13147
13614
  }
13148
13615
  return { executable: pythonPath, args: ["-m", "pip", "install", PYTHON_SYSTEM_CERTS_PACKAGE] };
13149
13616
  }
13617
+ function localEmbedWheelIndexUrl() {
13618
+ const override = process.env.THREADNOTE_LLAMA_WHEEL_INDEX;
13619
+ if (override !== void 0) {
13620
+ return override.trim() === "" ? void 0 : override.trim();
13621
+ }
13622
+ const base = "https://abetlen.github.io/llama-cpp-python/whl";
13623
+ return (0, import_node_os6.platform)() === "darwin" ? `${base}/metal` : `${base}/cpu`;
13624
+ }
13625
+ function openVikingToolPython() {
13626
+ const override = process.env.THREADNOTE_OPENVIKING_PYTHON;
13627
+ if (override !== void 0) {
13628
+ return override.trim() === "" ? void 0 : override.trim();
13629
+ }
13630
+ return OPENVIKING_TOOL_PYTHON;
13631
+ }
13150
13632
  async function getInstallCommands(config, preferred, force) {
13151
13633
  const packageSpec = `${OPENVIKING_PACKAGE_NAME}==${config.openVikingVersion}`;
13634
+ const wheelIndex = localEmbedWheelIndexUrl();
13152
13635
  const manager = preferred ?? await detectPackageManager();
13153
13636
  if (manager === "pipx") {
13637
+ const installArgs = force ? ["install", "--force"] : ["install"];
13638
+ if (wheelIndex) {
13639
+ installArgs.push("--pip-args", `--extra-index-url ${wheelIndex}`);
13640
+ }
13641
+ installArgs.push(packageSpec);
13154
13642
  return [
13155
- { executable: "pipx", args: force ? ["install", "--force", packageSpec] : ["install", packageSpec] },
13643
+ { executable: "pipx", args: installArgs },
13156
13644
  {
13157
13645
  executable: "pipx",
13158
13646
  args: force ? ["inject", "--force", "openviking", PYTHON_SYSTEM_CERTS_PACKAGE] : ["inject", "openviking", PYTHON_SYSTEM_CERTS_PACKAGE]
@@ -13160,7 +13648,16 @@ async function getInstallCommands(config, preferred, force) {
13160
13648
  ];
13161
13649
  }
13162
13650
  if (manager === "uv") {
13163
- const uvArgs = ["tool", "install", "--native-tls", "--with", PYTHON_SYSTEM_CERTS_PACKAGE];
13651
+ const toolPython = openVikingToolPython();
13652
+ const uvArgs = [
13653
+ "tool",
13654
+ "install",
13655
+ "--native-tls",
13656
+ ...toolPython ? ["--python", toolPython] : [],
13657
+ "--with",
13658
+ PYTHON_SYSTEM_CERTS_PACKAGE,
13659
+ ...wheelIndex ? ["--extra-index-url", wheelIndex] : []
13660
+ ];
13164
13661
  return [
13165
13662
  {
13166
13663
  executable: "uv",
@@ -13172,6 +13669,9 @@ async function getInstallCommands(config, preferred, force) {
13172
13669
  if (force) {
13173
13670
  pipArgs.push("--upgrade", "--force-reinstall");
13174
13671
  }
13672
+ if (wheelIndex) {
13673
+ pipArgs.push("--extra-index-url", wheelIndex);
13674
+ }
13175
13675
  pipArgs.push(PYTHON_SYSTEM_CERTS_PACKAGE);
13176
13676
  pipArgs.push(packageSpec);
13177
13677
  return [{ executable: "python3", args: pipArgs }];
@@ -14690,7 +15190,7 @@ async function main() {
14690
15190
  program2.command("migrate-lifecycle").description("Move clear legacy handoff memories into lifecycle-aware archive paths").option("--apply", "Perform the migration; without this, prints a dry run").option("--dry-run", "Print migration actions without writing or removing memories").option("--limit <count>", "Maximum number of legacy handoffs to migrate").action(async (options) => {
14691
15191
  await runMigrateLifecycle(getRuntimeConfig(program2), options);
14692
15192
  });
14693
- program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in exact memory/resource matches").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
15193
+ program2.command("recall").description("Search shared OpenViking context").requiredOption("--query <query>", "Search query").option("--dry-run", "Print ov command without searching").option("--include-archived", "Include archived memories in recall results").option("-n, --node-limit <count>", "Maximum number of search results").option("--no-infer-scope", "Disable query-based scope inference").option("--project <name>", "Prioritize a project: add a scoped pass over its memories alongside the global search").option("--threshold <score>", "Minimum relevance score 0-1 (default 0.45); lower to broaden when recall is empty").option("--uri <uri>", "Restrict search to a viking:// URI").action(async (options) => {
14694
15194
  await runRecall(getRuntimeConfig(program2), options);
14695
15195
  });
14696
15196
  program2.command("compact").description("Plan or apply scoped memory hygiene for active personal memories").requiredOption("--project <name>", "Project/repo namespace to inspect").option("--apply", "Apply the compact plan; without this, prints a dry run").option("--dry-run", "Print the compact plan without changing anything").option("--kind <kind>", "durable, handoff, or incident", parseCompactKind).option("--topic <name>", "Stable topic name to inspect").action(async (options) => {
@@ -14733,6 +15233,15 @@ async function main() {
14733
15233
  ).action(async (uri, options) => {
14734
15234
  await runSharePublish(getRuntimeConfig(program2), uri, options);
14735
15235
  });
15236
+ share.command("publish-artifact").description("Publish a local Codex/Claude skill or Claude command into the shared team repo").argument("<path>", "Path to SKILL.md or a Claude command markdown file").option("--team <name>", "Team name; defaults to the configured default team").option("--agent <agent>", "Agent owner when path inference is ambiguous: codex or claude").option("--kind <kind>", "Artifact kind when path inference is ambiguous: skill or command").option("--name <name>", "Shared artifact name; defaults to skill directory or command file stem").option("--message <text>", "Commit message override").option("--force", "Replace an existing shared artifact with different content").option("--no-push", "Skip the push step").option("--dry-run", "Print actions without running them").option("--preview", "Print the exact artifact bytes that would land in the shared git repo").option(
15237
+ "--redact",
15238
+ "Replace soft-leak matches (local paths) with placeholders and continue; credentials still block"
15239
+ ).action(async (path, options) => {
15240
+ await runSharePublishArtifact(getRuntimeConfig(program2), path, options);
15241
+ });
15242
+ share.command("install-artifacts").description("Install shared Codex/Claude skills and Claude commands from a team repo").option("--team <name>", "Team name; defaults to the configured default team").option("--agent <agent>", "Install only artifacts for this agent: codex or claude").option("--kind <kind>", "Install only artifacts of this kind: skill or command").option("--name <name>", "Install only this shared artifact name").option("--apply", "Actually write files into local agent skill/command directories").option("--force", "Replace existing installed artifacts with different content").option("--no-sync", "Skip pulling shared team updates before listing/installing artifacts").option("--dry-run", "Preview install actions without writing files").action(async (options) => {
15243
+ await runShareInstallArtifacts(getRuntimeConfig(program2), options);
15244
+ });
14736
15245
  share.command("unpublish").description("Pull a shared memory back into the personal namespace, commit removal and push").argument("<viking-uri>", "viking:// memory URI inside a team shared subtree").option("--team <name>", "Team name; defaults to the configured default team").option("--message <text>", "Commit message override").option("--no-push", "Skip the push step").option("--dry-run", "Print actions without running them").action(async (uri, options) => {
14737
15246
  await runShareUnpublish(getRuntimeConfig(program2), uri, options);
14738
15247
  });