threadnote 1.1.2 → 1.1.4

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.
@@ -3484,7 +3484,7 @@ var PYTHON_SYSTEM_CERTS_PACKAGE = "pip-system-certs";
3484
3484
  var LAUNCHD_LABEL = "io.threadnote.openviking";
3485
3485
  var MAX_SECRET_MATCHES_TO_PRINT = 5;
3486
3486
  var START_HEALTH_POLL_INTERVAL_MS = 500;
3487
- var START_HEALTH_TIMEOUT_MS = 15e3;
3487
+ var START_HEALTH_TIMEOUT_MS = 6e4;
3488
3488
  var SHIM_MARKER = "Generated by threadnote";
3489
3489
  var USER_INSTRUCTIONS_START_MARKER = "<!-- BEGIN THREADNOTE USER INSTRUCTIONS -->";
3490
3490
  var USER_INSTRUCTIONS_END_MARKER = "<!-- END THREADNOTE USER INSTRUCTIONS -->";
@@ -3743,18 +3743,57 @@ function escapeRegExp(value) {
3743
3743
  return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
3744
3744
  }
3745
3745
  async function requiredOpenVikingCli() {
3746
- const command2 = await findExecutable(["ov", "openviking"]);
3746
+ const command2 = await findOpenVikingCli();
3747
3747
  if (!command2) {
3748
- throw new Error("Neither ov nor openviking was found in PATH. Run threadnote install first.");
3748
+ throw new Error(
3749
+ "Neither ov nor openviking was found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run threadnote install first."
3750
+ );
3749
3751
  }
3750
3752
  return command2;
3751
3753
  }
3752
3754
  async function openVikingCliForMode(dryRun) {
3753
3755
  if (dryRun) {
3754
- return await findExecutable(["ov", "openviking"]) ?? "ov";
3756
+ return await findOpenVikingCli() ?? "ov";
3755
3757
  }
3756
3758
  return requiredOpenVikingCli();
3757
3759
  }
3760
+ async function findOpenVikingCli() {
3761
+ const override = process.env.THREADNOTE_OV?.trim();
3762
+ if (override) {
3763
+ return override;
3764
+ }
3765
+ const onPath = await findExecutable(["ov", "openviking"]);
3766
+ if (onPath) {
3767
+ return onPath;
3768
+ }
3769
+ for (const candidateDir of await openVikingToolCandidateDirs()) {
3770
+ for (const command2 of ["ov", "openviking"]) {
3771
+ const candidate = (0, import_node_path.join)(candidateDir, command2);
3772
+ if (await isExecutable(candidate)) {
3773
+ return candidate;
3774
+ }
3775
+ }
3776
+ }
3777
+ return void 0;
3778
+ }
3779
+ async function openVikingToolCandidateDirs() {
3780
+ const dirs = [];
3781
+ const uv = await findExecutable(["uv"]);
3782
+ if (uv) {
3783
+ const result = await runCommand(uv, ["tool", "dir", "--bin"], { allowFailure: true });
3784
+ if (result.exitCode === 0) {
3785
+ const dir = result.stdout.trim();
3786
+ if (dir) {
3787
+ dirs.push(dir);
3788
+ }
3789
+ }
3790
+ }
3791
+ if (process.env.UV_TOOL_BIN_DIR) {
3792
+ dirs.push(process.env.UV_TOOL_BIN_DIR);
3793
+ }
3794
+ dirs.push((0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin"));
3795
+ return Array.from(new Set(dirs));
3796
+ }
3758
3797
  async function requiredExecutable(command2) {
3759
3798
  const executable = await findExecutable([command2]);
3760
3799
  if (!executable) {
@@ -8457,6 +8496,9 @@ var import_node_path6 = require("node:path");
8457
8496
  var TEAMS_FILE_VERSION = 1;
8458
8497
  var SHARED_SEGMENT = "shared";
8459
8498
  var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
8499
+ var SHAREABLE_ARTIFACT_DIR = "agent-artifacts";
8500
+ var SHAREABLE_TOP_LEVEL_DIRS = [...SHAREABLE_MEMORY_KIND_DIRS, SHAREABLE_ARTIFACT_DIR];
8501
+ var ARTIFACT_INSTALL_METADATA_VERSION = 1;
8460
8502
  var AUTO_SHARE_FETCH_INTERVAL_MS = 5 * 60 * 1e3;
8461
8503
  var DEFAULT_GIT_REMOTE_NAME = "origin";
8462
8504
  var SCRUBBER_PATTERNS = [
@@ -8548,7 +8590,7 @@ async function runShareInit(config, remoteUrl, options) {
8548
8590
  if (!dryRun) {
8549
8591
  await ensureSharedGitignore(worktree, git, options.push !== false);
8550
8592
  const ingested = await ingestWorktreeFiles(config, newConfig, "create");
8551
- console.log(`Ingested ${ingested} shared memory file(s) into OpenViking.`);
8593
+ console.log(`Ingested ${ingested} shared file(s) into OpenViking.`);
8552
8594
  }
8553
8595
  }
8554
8596
  var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
@@ -8875,7 +8917,7 @@ async function runShareSyncQuiet(config, state, options) {
8875
8917
  return void 0;
8876
8918
  }
8877
8919
  async function stageShareableChanges(dryRun, git, worktree) {
8878
- const pathspecs = [":(top)README.md", ":(top).gitignore", ...SHAREABLE_MEMORY_KIND_DIRS.map((dir) => `:(top)${dir}`)];
8920
+ const pathspecs = [":(top)README.md", ":(top).gitignore", ...SHAREABLE_TOP_LEVEL_DIRS.map((dir) => `:(top)${dir}`)];
8879
8921
  await maybeRun(dryRun, git, ["-C", worktree, "add", "--", ...pathspecs], { allowFailure: true });
8880
8922
  }
8881
8923
  async function publishShareGitChange(worktree, relativePath, commitMessage, options = {}) {
@@ -8997,6 +9039,157 @@ ${err instanceof Error ? err.message : String(err)}`,
8997
9039
  }
8998
9040
  console.log(`Published ${sourceUri} -> ${targetUri}`);
8999
9041
  }
9042
+ async function runSharePublishArtifact(config, sourcePath, options) {
9043
+ const result = await shareAgentArtifact(config, sourcePath, options);
9044
+ printShareArtifactResult(result, options.preview === true);
9045
+ }
9046
+ async function shareAgentArtifact(config, sourcePath, options) {
9047
+ const team = await resolveTeam(config, options.team);
9048
+ const dryRun = options.dryRun === true;
9049
+ const preview = options.preview === true;
9050
+ const resolvedSourcePath = expandPath(sourcePath);
9051
+ if (!await isRegularFileNoSymlink(resolvedSourcePath)) {
9052
+ throw new Error(`Agent artifact source is not a regular file: ${resolvedSourcePath}`);
9053
+ }
9054
+ const artifact = inferShareArtifact(resolvedSourcePath, options);
9055
+ const rawContent = await (0, import_promises5.readFile)(resolvedSourcePath, "utf8");
9056
+ if (!rawContent.trim()) {
9057
+ throw new Error(`Refusing to share empty agent artifact: ${resolvedSourcePath}`);
9058
+ }
9059
+ const scrub = applyScrubber(rawContent, { redact: options.redact === true });
9060
+ const relativePath = sharedArtifactRelativePath(artifact);
9061
+ const targetPath = (0, import_node_path6.join)(team.config.worktree, ...relativePath.split("/"));
9062
+ const targetUri = workfileToVikingUri(config, team.config, targetPath);
9063
+ const messages = [
9064
+ `${preview ? "Previewing" : dryRun ? "Would share" : "Sharing"} ${artifact.kind} ${artifact.agent}/${artifact.name}`,
9065
+ `Source: ${portablePath(resolvedSourcePath)}`,
9066
+ `Destination: ${targetUri}`
9067
+ ];
9068
+ if (preview) {
9069
+ if (scrub.blocker) {
9070
+ messages.push(`PREVIEW BLOCKED: ${scrub.blocker}. Strip the sensitive value or pass --redact.`);
9071
+ return {
9072
+ artifact,
9073
+ gitMessages: [],
9074
+ messages,
9075
+ sourcePath: resolvedSourcePath,
9076
+ targetPath,
9077
+ targetUri
9078
+ };
9079
+ }
9080
+ for (const redaction of scrub.redactions) {
9081
+ messages.push(`PREVIEW redact: ${redaction.count}\xD7 ${redaction.name}`);
9082
+ }
9083
+ return {
9084
+ artifact,
9085
+ gitMessages: [],
9086
+ messages,
9087
+ previewContent: scrub.cleaned,
9088
+ sourcePath: resolvedSourcePath,
9089
+ targetPath,
9090
+ targetUri
9091
+ };
9092
+ }
9093
+ if (scrub.blocker) {
9094
+ throw new Error(
9095
+ `Refusing to share ${resolvedSourcePath}: possible ${scrub.blocker}. Strip the sensitive value or pass --redact for soft-leak patterns.`
9096
+ );
9097
+ }
9098
+ for (const redaction of scrub.redactions) {
9099
+ messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before sharing.`);
9100
+ }
9101
+ const content = scrub.cleaned;
9102
+ const existingContent = await readFileIfExists(targetPath) ?? void 0;
9103
+ if (existingContent !== void 0 && existingContent !== content && options.force !== true) {
9104
+ throw new Error(
9105
+ `Shared artifact already exists with different content: ${portablePath(targetPath)}. Pass --force to replace it.`
9106
+ );
9107
+ }
9108
+ if (dryRun) {
9109
+ messages.push(`Would write shared artifact: ${portablePath(targetPath)}`);
9110
+ }
9111
+ const ov = await openVikingCliForMode(dryRun);
9112
+ const ovHasResource = !dryRun && await vikingResourceExists(ov, config, targetUri);
9113
+ await ensureSharedDirectoryChain(config, ov, targetUri, dryRun, { quiet: true });
9114
+ await writeMemoryFile(config, ov, targetUri, content, ovHasResource ? "replace" : "create", dryRun, { quiet: true });
9115
+ const message = options.message ?? `share: publish ${relativePath}`;
9116
+ const gitMessages = await publishShareGitChange(team.config.worktree, relativePath, message, {
9117
+ dryRun,
9118
+ push: options.push
9119
+ });
9120
+ return { artifact, gitMessages, messages, sourcePath: resolvedSourcePath, targetPath, targetUri };
9121
+ }
9122
+ async function runShareInstallArtifacts(config, options) {
9123
+ const result = await installSharedAgentArtifacts(config, options);
9124
+ if (result.syncedTeams.length > 0) {
9125
+ console.log(`Synced shared teams: ${result.syncedTeams.join(", ")}`);
9126
+ }
9127
+ for (const warning2 of result.warnings) {
9128
+ console.warn(`Warning: ${warning2}`);
9129
+ }
9130
+ for (const message of result.messages) {
9131
+ console.log(message);
9132
+ }
9133
+ }
9134
+ async function installSharedAgentArtifacts(config, options) {
9135
+ const syncResult = await maybeSyncSharedArtifacts(config, options);
9136
+ const team = await resolveTeam(config, options.team);
9137
+ const dryRun = options.dryRun === true || options.apply !== true;
9138
+ const allArtifacts = await collectSharedArtifacts(team.config.worktree, team.name);
9139
+ const artifacts = filterSharedArtifacts(allArtifacts, options);
9140
+ const messages = [];
9141
+ if (artifacts.length === 0) {
9142
+ const filters = sharedArtifactFilterLabel(options);
9143
+ if (filters) {
9144
+ throw new Error(`No shared agent artifacts found for team "${team.name}" matching ${filters}.`);
9145
+ }
9146
+ return {
9147
+ installedCount: 0,
9148
+ messages: [`No shared agent artifacts found for team "${team.name}".`],
9149
+ syncedTeams: syncResult.syncedTeams,
9150
+ team: team.name,
9151
+ warnings: syncResult.warnings
9152
+ };
9153
+ }
9154
+ if (options.name !== void 0 && artifacts.length > 1 && (options.agent === void 0 || options.kind === void 0)) {
9155
+ throw new Error(
9156
+ `Shared artifact "${options.name}" is ambiguous. Specify agent and kind. Matches: ${artifacts.map((artifact) => sharedArtifactLabel(artifact.artifact)).join(", ")}`
9157
+ );
9158
+ }
9159
+ let installedCount = 0;
9160
+ for (const artifact of artifacts) {
9161
+ const label = sharedArtifactLabel(artifact.artifact);
9162
+ const state = await sharedArtifactInstallState(artifact);
9163
+ if (dryRun) {
9164
+ const verb = sharedArtifactDryRunVerb(state.status, options.force === true);
9165
+ const suffix = sharedArtifactDryRunSuffix(state.status, options.force === true);
9166
+ messages.push(`${verb} ${label}: ${portablePath(artifact.installPath)}${suffix}`);
9167
+ continue;
9168
+ }
9169
+ if ((state.status === "local_modified" || state.status === "remote_changed_and_local_modified") && options.force !== true) {
9170
+ throw new Error(`Refusing to overwrite ${portablePath(artifact.installPath)}. Pass force=true or --force.`);
9171
+ }
9172
+ if (state.status === "current") {
9173
+ await writeSharedArtifactMetadata(artifact, state.sourceSha);
9174
+ messages.push(`Already installed ${label}: ${portablePath(artifact.installPath)}`);
9175
+ continue;
9176
+ }
9177
+ await ensureDirectory((0, import_node_path6.dirname)(artifact.installPath), false);
9178
+ await (0, import_promises5.writeFile)(artifact.installPath, state.sourceContent, { encoding: "utf8", mode: 384 });
9179
+ await writeSharedArtifactMetadata(artifact, state.sourceSha);
9180
+ installedCount += 1;
9181
+ messages.push(
9182
+ `${sharedArtifactInstallVerb(state.status, options.force === true)} ${label}: ${portablePath(artifact.installPath)}`
9183
+ );
9184
+ }
9185
+ return {
9186
+ installedCount,
9187
+ messages,
9188
+ syncedTeams: syncResult.syncedTeams,
9189
+ team: team.name,
9190
+ warnings: syncResult.warnings
9191
+ };
9192
+ }
9000
9193
  async function runShareUnpublish(config, sourceUri, options) {
9001
9194
  assertVikingUri(sourceUri);
9002
9195
  const team = await resolveTeam(config, options.team);
@@ -9089,7 +9282,7 @@ async function runShareRename(config, options) {
9089
9282
  console.log(`Would rename worktree: ${portablePath(oldTeam.config.worktree)} -> ${portablePath(newWorktree)}`);
9090
9283
  console.log(`Would rename gitdir: ${portablePath(oldTeam.config.gitdir)} -> ${portablePath(newGitdir)}`);
9091
9284
  console.log(`Would update git core.worktree for team "${newName}".`);
9092
- console.log(`Would reindex shared memories under team "${newName}" and remove old shared URI tree.`);
9285
+ console.log(`Would reindex shared context under team "${newName}" and remove old shared URI tree.`);
9093
9286
  console.log(`Would write teams file: ${teamsFilePath(config)}`);
9094
9287
  return;
9095
9288
  }
@@ -9107,7 +9300,7 @@ async function runShareRename(config, options) {
9107
9300
  false
9108
9301
  );
9109
9302
  console.log(`Renamed shared team "${oldTeam.name}" -> "${newName}".`);
9110
- console.log(`Reindexed ${ingested} shared memory file(s).`);
9303
+ console.log(`Reindexed ${ingested} shared file(s).`);
9111
9304
  }
9112
9305
  async function runShareSetUrl(config, remoteUrl, options) {
9113
9306
  if (!remoteUrl.trim()) {
@@ -9350,7 +9543,7 @@ async function walkMemoryFiles(root) {
9350
9543
  }
9351
9544
  const full = (0, import_node_path6.join)(path, entry.name);
9352
9545
  if (entry.isDirectory()) {
9353
- if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
9546
+ if (depth === 0 && !SHAREABLE_TOP_LEVEL_DIRS.includes(entry.name)) {
9354
9547
  continue;
9355
9548
  }
9356
9549
  await visit(full, depth + 1);
@@ -9432,6 +9625,264 @@ function vikingUriToWorktreeRelative(config, uri, team) {
9432
9625
  }
9433
9626
  return uri.slice(prefix.length);
9434
9627
  }
9628
+ async function isRegularFileNoSymlink(path) {
9629
+ try {
9630
+ const stat5 = await (0, import_promises5.lstat)(path);
9631
+ return stat5.isFile();
9632
+ } catch (_err) {
9633
+ return false;
9634
+ }
9635
+ }
9636
+ function inferShareArtifact(path, options) {
9637
+ const normalizedPath = path.split(import_node_path6.sep).join("/");
9638
+ const fileName = (0, import_node_path6.basename)(path);
9639
+ const lowerFileName = fileName.toLowerCase();
9640
+ const lowerPath = normalizedPath.toLowerCase();
9641
+ const inferredKind = lowerFileName === "skill.md" ? "skill" : lowerPath.includes("/.claude/commands/") && lowerFileName.endsWith(".md") ? "command" : void 0;
9642
+ const inferredAgent = lowerPath.includes("/.codex/skills/") ? "codex" : lowerPath.includes("/.claude/skills/") || lowerPath.includes("/.claude/commands/") ? "claude" : void 0;
9643
+ const extensionIndex = fileName.lastIndexOf(".");
9644
+ const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
9645
+ const inferredName = lowerFileName === "skill.md" ? (0, import_node_path6.basename)((0, import_node_path6.dirname)(path)) : stem;
9646
+ const kind = options.kind ?? inferredKind;
9647
+ const agent = options.agent ?? inferredAgent;
9648
+ const name = options.name ?? inferredName;
9649
+ if (kind !== "skill" && kind !== "command") {
9650
+ throw new Error("Could not infer artifact kind. Pass --kind skill or --kind command.");
9651
+ }
9652
+ if (agent !== "codex" && agent !== "claude") {
9653
+ throw new Error("Could not infer artifact agent. Pass --agent codex or --agent claude.");
9654
+ }
9655
+ if (kind === "skill" && lowerFileName !== "skill.md") {
9656
+ throw new Error("Skill artifacts must point at a SKILL.md file.");
9657
+ }
9658
+ if (kind === "command" && !lowerFileName.endsWith(".md")) {
9659
+ throw new Error("Command artifacts must be Markdown files.");
9660
+ }
9661
+ if (kind === "command" && agent !== "claude") {
9662
+ throw new Error("Only Claude command artifacts are supported.");
9663
+ }
9664
+ if (name.trim().length === 0) {
9665
+ throw new Error("Artifact name cannot be empty.");
9666
+ }
9667
+ return { agent, kind, name: uriSegment(name) };
9668
+ }
9669
+ function sharedArtifactRelativePath(artifact) {
9670
+ if (artifact.kind === "skill") {
9671
+ return `${SHAREABLE_ARTIFACT_DIR}/skills/${artifact.agent}/${artifact.name}/SKILL.md`;
9672
+ }
9673
+ return `${SHAREABLE_ARTIFACT_DIR}/commands/${artifact.agent}/${artifact.name}.md`;
9674
+ }
9675
+ function sharedArtifactFromRelativePath(relativePath) {
9676
+ const parts = relativePath.split("/");
9677
+ if (parts[0] !== SHAREABLE_ARTIFACT_DIR) {
9678
+ return void 0;
9679
+ }
9680
+ if (parts.length === 5 && parts[1] === "skills" && (parts[2] === "codex" || parts[2] === "claude") && parts[4] === "SKILL.md") {
9681
+ return { agent: parts[2], kind: "skill", name: parts[3] };
9682
+ }
9683
+ if (parts.length === 4 && parts[1] === "commands" && parts[2] === "claude" && parts[3].endsWith(".md")) {
9684
+ return { agent: "claude", kind: "command", name: parts[3].slice(0, -".md".length) };
9685
+ }
9686
+ return void 0;
9687
+ }
9688
+ async function collectSharedArtifacts(worktree, team) {
9689
+ const root = (0, import_node_path6.join)(worktree, SHAREABLE_ARTIFACT_DIR);
9690
+ if (!await isDirectory(root)) {
9691
+ return [];
9692
+ }
9693
+ const out = [];
9694
+ async function visit(path) {
9695
+ const entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
9696
+ for (const entry of entries) {
9697
+ const full = (0, import_node_path6.join)(path, entry.name);
9698
+ if (entry.isDirectory()) {
9699
+ await visit(full);
9700
+ continue;
9701
+ }
9702
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
9703
+ continue;
9704
+ }
9705
+ const relativePath = (0, import_node_path6.relative)(worktree, full).split(import_node_path6.sep).join("/");
9706
+ const artifact = sharedArtifactFromRelativePath(relativePath);
9707
+ if (artifact === void 0) {
9708
+ continue;
9709
+ }
9710
+ out.push({
9711
+ artifact,
9712
+ installPath: sharedArtifactInstallPath(team, artifact),
9713
+ sourcePath: full,
9714
+ sourceRelativePath: relativePath,
9715
+ team
9716
+ });
9717
+ }
9718
+ }
9719
+ await visit(root);
9720
+ return out.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
9721
+ }
9722
+ function filterSharedArtifacts(artifacts, options) {
9723
+ const name = options.name === void 0 ? void 0 : uriSegment(options.name);
9724
+ return artifacts.filter((artifact) => {
9725
+ if (options.agent !== void 0 && artifact.artifact.agent !== options.agent) {
9726
+ return false;
9727
+ }
9728
+ if (options.kind !== void 0 && artifact.artifact.kind !== options.kind) {
9729
+ return false;
9730
+ }
9731
+ if (name !== void 0 && artifact.artifact.name !== name) {
9732
+ return false;
9733
+ }
9734
+ return true;
9735
+ });
9736
+ }
9737
+ async function maybeSyncSharedArtifacts(config, options) {
9738
+ if (options.sync === false) {
9739
+ return { syncedTeams: [], warnings: [] };
9740
+ }
9741
+ return syncSharedReposBeforeAgentRead(config);
9742
+ }
9743
+ async function sharedArtifactInstallState(artifact) {
9744
+ const sourceContent = await (0, import_promises5.readFile)(artifact.sourcePath, "utf8");
9745
+ const sourceSha = sha256(sourceContent);
9746
+ const existingContent = await readFileIfExists(artifact.installPath) ?? void 0;
9747
+ if (existingContent === void 0) {
9748
+ return { sourceContent, sourceSha, status: "not_installed" };
9749
+ }
9750
+ const existingSha = sha256(existingContent);
9751
+ const metadata = await readSharedArtifactMetadata(artifact);
9752
+ if (existingSha === sourceSha) {
9753
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "current" };
9754
+ }
9755
+ if (metadata === void 0) {
9756
+ return { existingContent, existingSha, sourceContent, sourceSha, status: "local_modified" };
9757
+ }
9758
+ const remoteChanged = metadata.sourceSha256 !== sourceSha;
9759
+ const localChanged = metadata.installedSha256 !== existingSha;
9760
+ if (remoteChanged && localChanged) {
9761
+ return {
9762
+ existingContent,
9763
+ existingSha,
9764
+ metadata,
9765
+ sourceContent,
9766
+ sourceSha,
9767
+ status: "remote_changed_and_local_modified"
9768
+ };
9769
+ }
9770
+ if (remoteChanged) {
9771
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "update_available" };
9772
+ }
9773
+ return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "local_modified" };
9774
+ }
9775
+ async function readSharedArtifactMetadata(artifact) {
9776
+ const raw = await readFileIfExists(sharedArtifactMetadataPath(artifact));
9777
+ if (raw === void 0) {
9778
+ return void 0;
9779
+ }
9780
+ const parsed = parseJsonConfigObject(raw);
9781
+ if (parsed === void 0 || parsed.version !== ARTIFACT_INSTALL_METADATA_VERSION) {
9782
+ return void 0;
9783
+ }
9784
+ const artifactValue = parsed.artifact;
9785
+ 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)) {
9786
+ return void 0;
9787
+ }
9788
+ const metadataArtifact = artifactValue;
9789
+ if (metadataArtifact.agent !== artifact.artifact.agent || metadataArtifact.kind !== artifact.artifact.kind || metadataArtifact.name !== artifact.artifact.name) {
9790
+ return void 0;
9791
+ }
9792
+ return {
9793
+ artifact: artifact.artifact,
9794
+ installedAt: parsed.installedAt,
9795
+ installedSha256: parsed.installedSha256,
9796
+ source: parsed.source,
9797
+ sourceSha256: parsed.sourceSha256,
9798
+ team: parsed.team,
9799
+ version: ARTIFACT_INSTALL_METADATA_VERSION
9800
+ };
9801
+ }
9802
+ async function writeSharedArtifactMetadata(artifact, sourceSha) {
9803
+ const metadata = {
9804
+ artifact: artifact.artifact,
9805
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
9806
+ installedSha256: sourceSha,
9807
+ source: artifact.sourceRelativePath,
9808
+ sourceSha256: sourceSha,
9809
+ team: artifact.team,
9810
+ version: ARTIFACT_INSTALL_METADATA_VERSION
9811
+ };
9812
+ const metadataPath = sharedArtifactMetadataPath(artifact);
9813
+ await ensureDirectory((0, import_node_path6.dirname)(metadataPath), false);
9814
+ await (0, import_promises5.writeFile)(metadataPath, `${JSON.stringify(metadata, void 0, 2)}
9815
+ `, { encoding: "utf8", mode: 384 });
9816
+ }
9817
+ function sharedArtifactMetadataPath(artifact) {
9818
+ return `${artifact.installPath}.threadnote-install.json`;
9819
+ }
9820
+ function sharedArtifactDryRunVerb(status, force) {
9821
+ switch (status) {
9822
+ case "not_installed":
9823
+ return "Would install";
9824
+ case "current":
9825
+ return "Already installed";
9826
+ case "update_available":
9827
+ return "Would update";
9828
+ case "local_modified":
9829
+ case "remote_changed_and_local_modified":
9830
+ return force ? "Would replace" : "Would skip modified";
9831
+ }
9832
+ }
9833
+ function sharedArtifactDryRunSuffix(status, force) {
9834
+ if ((status === "local_modified" || status === "remote_changed_and_local_modified") && !force) {
9835
+ return " (pass --force to replace local changes)";
9836
+ }
9837
+ return "";
9838
+ }
9839
+ function sharedArtifactInstallVerb(status, force) {
9840
+ if (force && (status === "local_modified" || status === "remote_changed_and_local_modified")) {
9841
+ return "Replaced";
9842
+ }
9843
+ if (status === "update_available") {
9844
+ return "Updated";
9845
+ }
9846
+ return "Installed";
9847
+ }
9848
+ function sharedArtifactFilterLabel(options) {
9849
+ const filters = [];
9850
+ if (options.kind !== void 0) {
9851
+ filters.push(`kind=${options.kind}`);
9852
+ }
9853
+ if (options.agent !== void 0) {
9854
+ filters.push(`agent=${options.agent}`);
9855
+ }
9856
+ if (options.name !== void 0) {
9857
+ filters.push(`name=${uriSegment(options.name)}`);
9858
+ }
9859
+ return filters.join(", ");
9860
+ }
9861
+ function sharedArtifactLabel(artifact) {
9862
+ return `${artifact.kind} ${artifact.agent}/${artifact.name}`;
9863
+ }
9864
+ function sharedArtifactInstallPath(team, artifact) {
9865
+ if (artifact.kind === "skill" && artifact.agent === "codex") {
9866
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".codex", "skills", "threadnote", team, artifact.name, "SKILL.md");
9867
+ }
9868
+ if (artifact.kind === "skill" && artifact.agent === "claude") {
9869
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".claude", "skills", "threadnote", team, artifact.name, "SKILL.md");
9870
+ }
9871
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".claude", "commands", "threadnote", team, `${artifact.name}.md`);
9872
+ }
9873
+ function printShareArtifactResult(result, preview) {
9874
+ for (const message of result.messages) {
9875
+ console.log(message);
9876
+ }
9877
+ for (const gitMessage of result.gitMessages) {
9878
+ console.log(gitMessage);
9879
+ }
9880
+ if (preview && result.previewContent !== void 0) {
9881
+ console.log("-----BEGIN PREVIEW-----");
9882
+ console.log(result.previewContent);
9883
+ console.log("-----END PREVIEW-----");
9884
+ }
9885
+ }
9435
9886
  function stripPersonalProvenance(content) {
9436
9887
  const lines = content.split("\n");
9437
9888
  let headerEnd = lines.length;
@@ -9480,9 +9931,9 @@ async function ensureSharedDirectoryChain(config, ov, memoryUri, dryRun, options
9480
9931
  continue;
9481
9932
  }
9482
9933
  if (options.quiet === true) {
9483
- await runCommand(ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared memories."]));
9934
+ await runCommand(ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared context."]));
9484
9935
  } else {
9485
- await maybeRun(false, ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared memories."]));
9936
+ await maybeRun(false, ov, withIdentity(config, ["mkdir", uri, "--description", "Threadnote shared context."]));
9486
9937
  }
9487
9938
  }
9488
9939
  }
@@ -9711,7 +10162,7 @@ async function applyChangesToOpenViking(config, team, changes, options = {}) {
9711
10162
  continue;
9712
10163
  }
9713
10164
  const firstSegment = change.relativePath.split("/")[0];
9714
- if (!SHAREABLE_MEMORY_KIND_DIRS.includes(firstSegment)) {
10165
+ if (!SHAREABLE_TOP_LEVEL_DIRS.includes(firstSegment)) {
9715
10166
  continue;
9716
10167
  }
9717
10168
  const uri = workfileToVikingUri(config, team, change.path);
@@ -11969,7 +12420,7 @@ async function maybeRunPostUpdateAfterRepair(config, options) {
11969
12420
  });
11970
12421
  }
11971
12422
  async function ensurePinnedOpenVikingInstalled(config, options) {
11972
- const ov = await findExecutable(["ov", "openviking"]);
12423
+ const ov = await findOpenVikingCli();
11973
12424
  if (!ov) {
11974
12425
  return;
11975
12426
  }
@@ -12401,7 +12852,7 @@ async function collectDoctorChecks(config, options = {}) {
12401
12852
  checks.push(await commandCheck("node", ["--version"]));
12402
12853
  checks.push(await commandCheck("python3", ["--version"]));
12403
12854
  checks.push(await openVikingServerCheck());
12404
- checks.push(await firstCommandCheck("openviking cli", ["ov", "openviking"], ["--help"]));
12855
+ checks.push(await openVikingCliCheck());
12405
12856
  checks.push(await localEmbeddingCheck());
12406
12857
  checks.push(await pythonSystemCertificatesCheck());
12407
12858
  checks.push(await firstCommandCheck("python installer", ["pipx", "uv", "pip3"], ["--version"]));
@@ -12606,9 +13057,9 @@ async function repairManifest(config, dryRun) {
12606
13057
  }
12607
13058
  async function repairRecallIndex(config, dryRun) {
12608
13059
  console.log("\nRepairing recall index freshness.");
12609
- const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
13060
+ const ov = dryRun ? await findOpenVikingCli() ?? "ov" : await findOpenVikingCli();
12610
13061
  if (!ov) {
12611
- console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
13062
+ console.log("Skipping recall index repair: neither ov nor openviking was found.");
12612
13063
  return;
12613
13064
  }
12614
13065
  const progress = startProgress("Scanning recall index freshness across memories and seeded resources.");
@@ -12659,7 +13110,7 @@ async function repairRecallIndex(config, dryRun) {
12659
13110
  }
12660
13111
  }
12661
13112
  async function configureOpenVikingCliLanguage(config, dryRun) {
12662
- const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
13113
+ const ov = dryRun ? await findOpenVikingCli() ?? "ov" : await findOpenVikingCli();
12663
13114
  if (!ov) {
12664
13115
  return;
12665
13116
  }
@@ -12785,7 +13236,11 @@ async function runStart(config, options) {
12785
13236
  child.unref();
12786
13237
  await (0, import_promises12.writeFile)((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
12787
13238
  `, "utf8");
12788
- const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
13239
+ const health = await waitForOpenVikingHealth(
13240
+ config,
13241
+ START_HEALTH_TIMEOUT_MS,
13242
+ `Waiting for OpenViking health at ${healthUrl}.`
13243
+ );
12789
13244
  if (health) {
12790
13245
  console.log(`Started OpenViking with pid ${child.pid}. Health OK at ${healthUrl}. Logs: ${logPath}`);
12791
13246
  return;
@@ -12896,6 +13351,20 @@ async function firstCommandCheck(name, commands, args) {
12896
13351
  }
12897
13352
  return { name, status: "fail", detail: `none found: ${commands.join(", ")}` };
12898
13353
  }
13354
+ async function openVikingCliCheck() {
13355
+ const executable = await findOpenVikingCli();
13356
+ if (!executable) {
13357
+ return { name: "openviking cli", status: "fail", detail: "none found: ov, openviking" };
13358
+ }
13359
+ const result = await runCommand(executable, ["--help"], { allowFailure: true });
13360
+ const onPath = await findExecutable(["ov", "openviking"]);
13361
+ const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path13.dirname)(executable)} to PATH)`;
13362
+ return {
13363
+ name: "openviking cli",
13364
+ status: result.exitCode === 0 ? "ok" : "warn",
13365
+ detail: result.exitCode === 0 ? detail : firstLine(result.stderr || result.stdout) || detail
13366
+ };
13367
+ }
12899
13368
  async function localEmbeddingCheck() {
12900
13369
  const serverPath = await findOpenVikingServer();
12901
13370
  if (!serverPath) {
@@ -13063,21 +13532,26 @@ async function readOpenVikingHealthIfAvailable(config, timeoutMs) {
13063
13532
  return void 0;
13064
13533
  }
13065
13534
  }
13066
- async function waitForOpenVikingHealth(config, timeoutMs) {
13067
- const deadline = Date.now() + timeoutMs;
13068
- while (Date.now() <= deadline) {
13069
- const requestTimeoutMs = Math.max(100, Math.min(1e3, deadline - Date.now()));
13070
- const health = await readOpenVikingHealthIfAvailable(config, requestTimeoutMs);
13071
- if (health) {
13072
- return health;
13073
- }
13074
- const remainingMs = deadline - Date.now();
13075
- if (remainingMs <= 0) {
13076
- break;
13535
+ async function waitForOpenVikingHealth(config, timeoutMs, progressMessage) {
13536
+ const progress = progressMessage ? startProgress(progressMessage) : void 0;
13537
+ try {
13538
+ const deadline = Date.now() + timeoutMs;
13539
+ while (Date.now() <= deadline) {
13540
+ const requestTimeoutMs = Math.max(100, Math.min(1e3, deadline - Date.now()));
13541
+ const health = await readOpenVikingHealthIfAvailable(config, requestTimeoutMs);
13542
+ if (health) {
13543
+ return health;
13544
+ }
13545
+ const remainingMs = deadline - Date.now();
13546
+ if (remainingMs <= 0) {
13547
+ break;
13548
+ }
13549
+ await sleep(Math.min(START_HEALTH_POLL_INTERVAL_MS, remainingMs));
13077
13550
  }
13078
- await sleep(Math.min(START_HEALTH_POLL_INTERVAL_MS, remainingMs));
13551
+ return void 0;
13552
+ } finally {
13553
+ progress?.stop();
13079
13554
  }
13080
- return void 0;
13081
13555
  }
13082
13556
  async function runInstallCommands(config, preferred, force, dryRun) {
13083
13557
  let manager = preferred;
@@ -13538,7 +14012,11 @@ async function installLaunchAgent(config, dryRun) {
13538
14012
  await maybeRun(false, "launchctl", ["load", destination]);
13539
14013
  await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
13540
14014
  const healthUrl = openVikingHealthUrl(config);
13541
- const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
14015
+ const health = await waitForOpenVikingHealth(
14016
+ config,
14017
+ START_HEALTH_TIMEOUT_MS,
14018
+ `Waiting for OpenViking health at ${healthUrl}.`
14019
+ );
13542
14020
  if (health) {
13543
14021
  console.log(`Installed and started ${LAUNCHD_LABEL}. Health OK at ${healthUrl}`);
13544
14022
  return;
@@ -14821,6 +15299,15 @@ async function main() {
14821
15299
  ).action(async (uri, options) => {
14822
15300
  await runSharePublish(getRuntimeConfig(program2), uri, options);
14823
15301
  });
15302
+ 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(
15303
+ "--redact",
15304
+ "Replace soft-leak matches (local paths) with placeholders and continue; credentials still block"
15305
+ ).action(async (path, options) => {
15306
+ await runSharePublishArtifact(getRuntimeConfig(program2), path, options);
15307
+ });
15308
+ 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) => {
15309
+ await runShareInstallArtifacts(getRuntimeConfig(program2), options);
15310
+ });
14824
15311
  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) => {
14825
15312
  await runShareUnpublish(getRuntimeConfig(program2), uri, options);
14826
15313
  });