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.
- package/LICENSE +67 -80
- package/README.md +19 -2
- package/THIRD_PARTY.md +36 -0
- package/dist/mcp_server.cjs +618 -19
- package/dist/threadnote.cjs +519 -32
- package/docs/agent-instructions.md +22 -5
- package/docs/index.html +2 -2
- package/docs/share.md +100 -10
- package/docs/troubleshooting.md +11 -3
- package/package.json +4 -3
- package/scripts/install.sh +5 -1
package/dist/mcp_server.cjs
CHANGED
|
@@ -33081,7 +33081,6 @@ var StreamableHTTPClientTransport = class {
|
|
|
33081
33081
|
|
|
33082
33082
|
// src/mcp_server.ts
|
|
33083
33083
|
var import_promises5 = require("node:fs/promises");
|
|
33084
|
-
var import_node_os3 = require("node:os");
|
|
33085
33084
|
var import_node_path5 = require("node:path");
|
|
33086
33085
|
|
|
33087
33086
|
// src/constants.ts
|
|
@@ -35724,6 +35723,7 @@ var jsYaml = {
|
|
|
35724
35723
|
// src/utils.ts
|
|
35725
35724
|
var import_node_child_process = require("node:child_process");
|
|
35726
35725
|
var import_node_crypto = require("node:crypto");
|
|
35726
|
+
var import_node_fs = require("node:fs");
|
|
35727
35727
|
var import_promises = require("node:fs/promises");
|
|
35728
35728
|
var import_node_os = require("node:os");
|
|
35729
35729
|
var import_node_path = require("node:path");
|
|
@@ -35778,18 +35778,57 @@ function redactText(content) {
|
|
|
35778
35778
|
).replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9_-]{16,}/g, "sk-[REDACTED]").replace(/gh[pousr]_[A-Za-z0-9_]{16,}/g, "gh_[REDACTED]");
|
|
35779
35779
|
}
|
|
35780
35780
|
async function requiredOpenVikingCli() {
|
|
35781
|
-
const command2 = await
|
|
35781
|
+
const command2 = await findOpenVikingCli();
|
|
35782
35782
|
if (!command2) {
|
|
35783
|
-
throw new Error(
|
|
35783
|
+
throw new Error(
|
|
35784
|
+
"Neither ov nor openviking was found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run threadnote install first."
|
|
35785
|
+
);
|
|
35784
35786
|
}
|
|
35785
35787
|
return command2;
|
|
35786
35788
|
}
|
|
35787
35789
|
async function openVikingCliForMode(dryRun) {
|
|
35788
35790
|
if (dryRun) {
|
|
35789
|
-
return await
|
|
35791
|
+
return await findOpenVikingCli() ?? "ov";
|
|
35790
35792
|
}
|
|
35791
35793
|
return requiredOpenVikingCli();
|
|
35792
35794
|
}
|
|
35795
|
+
async function findOpenVikingCli() {
|
|
35796
|
+
const override = process.env.THREADNOTE_OV?.trim();
|
|
35797
|
+
if (override) {
|
|
35798
|
+
return override;
|
|
35799
|
+
}
|
|
35800
|
+
const onPath = await findExecutable(["ov", "openviking"]);
|
|
35801
|
+
if (onPath) {
|
|
35802
|
+
return onPath;
|
|
35803
|
+
}
|
|
35804
|
+
for (const candidateDir of await openVikingToolCandidateDirs()) {
|
|
35805
|
+
for (const command2 of ["ov", "openviking"]) {
|
|
35806
|
+
const candidate = (0, import_node_path.join)(candidateDir, command2);
|
|
35807
|
+
if (await isExecutable(candidate)) {
|
|
35808
|
+
return candidate;
|
|
35809
|
+
}
|
|
35810
|
+
}
|
|
35811
|
+
}
|
|
35812
|
+
return void 0;
|
|
35813
|
+
}
|
|
35814
|
+
async function openVikingToolCandidateDirs() {
|
|
35815
|
+
const dirs = [];
|
|
35816
|
+
const uv = await findExecutable(["uv"]);
|
|
35817
|
+
if (uv) {
|
|
35818
|
+
const result = await runCommand(uv, ["tool", "dir", "--bin"], { allowFailure: true });
|
|
35819
|
+
if (result.exitCode === 0) {
|
|
35820
|
+
const dir = result.stdout.trim();
|
|
35821
|
+
if (dir) {
|
|
35822
|
+
dirs.push(dir);
|
|
35823
|
+
}
|
|
35824
|
+
}
|
|
35825
|
+
}
|
|
35826
|
+
if (process.env.UV_TOOL_BIN_DIR) {
|
|
35827
|
+
dirs.push(process.env.UV_TOOL_BIN_DIR);
|
|
35828
|
+
}
|
|
35829
|
+
dirs.push((0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin"));
|
|
35830
|
+
return Array.from(new Set(dirs));
|
|
35831
|
+
}
|
|
35793
35832
|
async function requiredExecutable(command2) {
|
|
35794
35833
|
const executable = await findExecutable([command2]);
|
|
35795
35834
|
if (!executable) {
|
|
@@ -35946,6 +35985,14 @@ async function exists(path) {
|
|
|
35946
35985
|
return false;
|
|
35947
35986
|
}
|
|
35948
35987
|
}
|
|
35988
|
+
async function isExecutable(path) {
|
|
35989
|
+
try {
|
|
35990
|
+
await (0, import_promises.access)(path, import_node_fs.constants.X_OK);
|
|
35991
|
+
return true;
|
|
35992
|
+
} catch (_err) {
|
|
35993
|
+
return false;
|
|
35994
|
+
}
|
|
35995
|
+
}
|
|
35949
35996
|
async function isFile(path) {
|
|
35950
35997
|
try {
|
|
35951
35998
|
return (await (0, import_promises.stat)(path)).isFile();
|
|
@@ -35953,6 +36000,13 @@ async function isFile(path) {
|
|
|
35953
36000
|
return false;
|
|
35954
36001
|
}
|
|
35955
36002
|
}
|
|
36003
|
+
async function isDirectory(path) {
|
|
36004
|
+
try {
|
|
36005
|
+
return (await (0, import_promises.stat)(path)).isDirectory();
|
|
36006
|
+
} catch (_err) {
|
|
36007
|
+
return false;
|
|
36008
|
+
}
|
|
36009
|
+
}
|
|
35956
36010
|
async function readFileIfExists(path) {
|
|
35957
36011
|
try {
|
|
35958
36012
|
return await (0, import_promises.readFile)(path, "utf8");
|
|
@@ -35976,6 +36030,17 @@ function expandPath(path) {
|
|
|
35976
36030
|
}
|
|
35977
36031
|
return (0, import_node_path.isAbsolute)(path) ? path : (0, import_node_path.resolve)(getInvocationCwd(), path);
|
|
35978
36032
|
}
|
|
36033
|
+
function portablePath(path) {
|
|
36034
|
+
const home = (0, import_node_os.homedir)();
|
|
36035
|
+
const resolvedPath = (0, import_node_path.resolve)(path);
|
|
36036
|
+
if (resolvedPath === home) {
|
|
36037
|
+
return "~";
|
|
36038
|
+
}
|
|
36039
|
+
if (resolvedPath.startsWith(`${home}${import_node_path.sep}`)) {
|
|
36040
|
+
return `~/${resolvedPath.slice(home.length + 1).split(import_node_path.sep).join("/")}`;
|
|
36041
|
+
}
|
|
36042
|
+
return resolvedPath;
|
|
36043
|
+
}
|
|
35979
36044
|
function getInvocationCwd() {
|
|
35980
36045
|
return process.env.THREADNOTE_CALLER_CWD ?? process.cwd();
|
|
35981
36046
|
}
|
|
@@ -37082,6 +37147,9 @@ var import_node_path4 = require("node:path");
|
|
|
37082
37147
|
var TEAMS_FILE_VERSION = 1;
|
|
37083
37148
|
var SHARED_SEGMENT = "shared";
|
|
37084
37149
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
37150
|
+
var SHAREABLE_ARTIFACT_DIR = "agent-artifacts";
|
|
37151
|
+
var SHAREABLE_TOP_LEVEL_DIRS = [...SHAREABLE_MEMORY_KIND_DIRS, SHAREABLE_ARTIFACT_DIR];
|
|
37152
|
+
var ARTIFACT_INSTALL_METADATA_VERSION = 1;
|
|
37085
37153
|
var AUTO_SHARE_FETCH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
37086
37154
|
var DEFAULT_GIT_REMOTE_NAME = "origin";
|
|
37087
37155
|
var SCRUBBER_PATTERNS = [
|
|
@@ -37401,6 +37469,155 @@ async function runGitCommand(dryRun, git, args, failureLabel) {
|
|
|
37401
37469
|
}
|
|
37402
37470
|
return result;
|
|
37403
37471
|
}
|
|
37472
|
+
async function shareAgentArtifact(config2, sourcePath, options) {
|
|
37473
|
+
const team = await resolveTeam(config2, options.team);
|
|
37474
|
+
const dryRun = options.dryRun === true;
|
|
37475
|
+
const preview = options.preview === true;
|
|
37476
|
+
const resolvedSourcePath = expandPath(sourcePath);
|
|
37477
|
+
if (!await isRegularFileNoSymlink(resolvedSourcePath)) {
|
|
37478
|
+
throw new Error(`Agent artifact source is not a regular file: ${resolvedSourcePath}`);
|
|
37479
|
+
}
|
|
37480
|
+
const artifact = inferShareArtifact(resolvedSourcePath, options);
|
|
37481
|
+
const rawContent = await (0, import_promises4.readFile)(resolvedSourcePath, "utf8");
|
|
37482
|
+
if (!rawContent.trim()) {
|
|
37483
|
+
throw new Error(`Refusing to share empty agent artifact: ${resolvedSourcePath}`);
|
|
37484
|
+
}
|
|
37485
|
+
const scrub = applyScrubber(rawContent, { redact: options.redact === true });
|
|
37486
|
+
const relativePath = sharedArtifactRelativePath(artifact);
|
|
37487
|
+
const targetPath = (0, import_node_path4.join)(team.config.worktree, ...relativePath.split("/"));
|
|
37488
|
+
const targetUri = workfileToVikingUri(config2, team.config, targetPath);
|
|
37489
|
+
const messages = [
|
|
37490
|
+
`${preview ? "Previewing" : dryRun ? "Would share" : "Sharing"} ${artifact.kind} ${artifact.agent}/${artifact.name}`,
|
|
37491
|
+
`Source: ${portablePath(resolvedSourcePath)}`,
|
|
37492
|
+
`Destination: ${targetUri}`
|
|
37493
|
+
];
|
|
37494
|
+
if (preview) {
|
|
37495
|
+
if (scrub.blocker) {
|
|
37496
|
+
messages.push(`PREVIEW BLOCKED: ${scrub.blocker}. Strip the sensitive value or pass --redact.`);
|
|
37497
|
+
return {
|
|
37498
|
+
artifact,
|
|
37499
|
+
gitMessages: [],
|
|
37500
|
+
messages,
|
|
37501
|
+
sourcePath: resolvedSourcePath,
|
|
37502
|
+
targetPath,
|
|
37503
|
+
targetUri
|
|
37504
|
+
};
|
|
37505
|
+
}
|
|
37506
|
+
for (const redaction of scrub.redactions) {
|
|
37507
|
+
messages.push(`PREVIEW redact: ${redaction.count}\xD7 ${redaction.name}`);
|
|
37508
|
+
}
|
|
37509
|
+
return {
|
|
37510
|
+
artifact,
|
|
37511
|
+
gitMessages: [],
|
|
37512
|
+
messages,
|
|
37513
|
+
previewContent: scrub.cleaned,
|
|
37514
|
+
sourcePath: resolvedSourcePath,
|
|
37515
|
+
targetPath,
|
|
37516
|
+
targetUri
|
|
37517
|
+
};
|
|
37518
|
+
}
|
|
37519
|
+
if (scrub.blocker) {
|
|
37520
|
+
throw new Error(
|
|
37521
|
+
`Refusing to share ${resolvedSourcePath}: possible ${scrub.blocker}. Strip the sensitive value or pass --redact for soft-leak patterns.`
|
|
37522
|
+
);
|
|
37523
|
+
}
|
|
37524
|
+
for (const redaction of scrub.redactions) {
|
|
37525
|
+
messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before sharing.`);
|
|
37526
|
+
}
|
|
37527
|
+
const content = scrub.cleaned;
|
|
37528
|
+
const existingContent = await readFileIfExists(targetPath) ?? void 0;
|
|
37529
|
+
if (existingContent !== void 0 && existingContent !== content && options.force !== true) {
|
|
37530
|
+
throw new Error(
|
|
37531
|
+
`Shared artifact already exists with different content: ${portablePath(targetPath)}. Pass --force to replace it.`
|
|
37532
|
+
);
|
|
37533
|
+
}
|
|
37534
|
+
if (dryRun) {
|
|
37535
|
+
messages.push(`Would write shared artifact: ${portablePath(targetPath)}`);
|
|
37536
|
+
}
|
|
37537
|
+
const ov = await openVikingCliForMode(dryRun);
|
|
37538
|
+
const ovHasResource = !dryRun && await vikingResourceExists(ov, config2, targetUri);
|
|
37539
|
+
await ensureSharedDirectoryChain(config2, ov, targetUri, dryRun, { quiet: true });
|
|
37540
|
+
await writeMemoryFile(config2, ov, targetUri, content, ovHasResource ? "replace" : "create", dryRun, { quiet: true });
|
|
37541
|
+
const message = options.message ?? `share: publish ${relativePath}`;
|
|
37542
|
+
const gitMessages = await publishShareGitChange(team.config.worktree, relativePath, message, {
|
|
37543
|
+
dryRun,
|
|
37544
|
+
push: options.push
|
|
37545
|
+
});
|
|
37546
|
+
return { artifact, gitMessages, messages, sourcePath: resolvedSourcePath, targetPath, targetUri };
|
|
37547
|
+
}
|
|
37548
|
+
async function listSharedAgentArtifacts(config2, options = {}) {
|
|
37549
|
+
const syncResult = await maybeSyncSharedArtifacts(config2, options);
|
|
37550
|
+
const team = await resolveTeam(config2, options.team);
|
|
37551
|
+
const artifacts = filterSharedArtifacts(await collectSharedArtifacts(team.config.worktree, team.name), options);
|
|
37552
|
+
const summaries = [];
|
|
37553
|
+
for (const artifact of artifacts) {
|
|
37554
|
+
summaries.push({
|
|
37555
|
+
...artifact,
|
|
37556
|
+
installStatus: await sharedArtifactInstallStatus(artifact),
|
|
37557
|
+
metadataPath: sharedArtifactMetadataPath(artifact)
|
|
37558
|
+
});
|
|
37559
|
+
}
|
|
37560
|
+
return { artifacts: summaries, syncedTeams: syncResult.syncedTeams, team: team.name, warnings: syncResult.warnings };
|
|
37561
|
+
}
|
|
37562
|
+
async function installSharedAgentArtifacts(config2, options) {
|
|
37563
|
+
const syncResult = await maybeSyncSharedArtifacts(config2, options);
|
|
37564
|
+
const team = await resolveTeam(config2, options.team);
|
|
37565
|
+
const dryRun = options.dryRun === true || options.apply !== true;
|
|
37566
|
+
const allArtifacts = await collectSharedArtifacts(team.config.worktree, team.name);
|
|
37567
|
+
const artifacts = filterSharedArtifacts(allArtifacts, options);
|
|
37568
|
+
const messages = [];
|
|
37569
|
+
if (artifacts.length === 0) {
|
|
37570
|
+
const filters = sharedArtifactFilterLabel(options);
|
|
37571
|
+
if (filters) {
|
|
37572
|
+
throw new Error(`No shared agent artifacts found for team "${team.name}" matching ${filters}.`);
|
|
37573
|
+
}
|
|
37574
|
+
return {
|
|
37575
|
+
installedCount: 0,
|
|
37576
|
+
messages: [`No shared agent artifacts found for team "${team.name}".`],
|
|
37577
|
+
syncedTeams: syncResult.syncedTeams,
|
|
37578
|
+
team: team.name,
|
|
37579
|
+
warnings: syncResult.warnings
|
|
37580
|
+
};
|
|
37581
|
+
}
|
|
37582
|
+
if (options.name !== void 0 && artifacts.length > 1 && (options.agent === void 0 || options.kind === void 0)) {
|
|
37583
|
+
throw new Error(
|
|
37584
|
+
`Shared artifact "${options.name}" is ambiguous. Specify agent and kind. Matches: ${artifacts.map((artifact) => sharedArtifactLabel(artifact.artifact)).join(", ")}`
|
|
37585
|
+
);
|
|
37586
|
+
}
|
|
37587
|
+
let installedCount = 0;
|
|
37588
|
+
for (const artifact of artifacts) {
|
|
37589
|
+
const label = sharedArtifactLabel(artifact.artifact);
|
|
37590
|
+
const state = await sharedArtifactInstallState(artifact);
|
|
37591
|
+
if (dryRun) {
|
|
37592
|
+
const verb = sharedArtifactDryRunVerb(state.status, options.force === true);
|
|
37593
|
+
const suffix = sharedArtifactDryRunSuffix(state.status, options.force === true);
|
|
37594
|
+
messages.push(`${verb} ${label}: ${portablePath(artifact.installPath)}${suffix}`);
|
|
37595
|
+
continue;
|
|
37596
|
+
}
|
|
37597
|
+
if ((state.status === "local_modified" || state.status === "remote_changed_and_local_modified") && options.force !== true) {
|
|
37598
|
+
throw new Error(`Refusing to overwrite ${portablePath(artifact.installPath)}. Pass force=true or --force.`);
|
|
37599
|
+
}
|
|
37600
|
+
if (state.status === "current") {
|
|
37601
|
+
await writeSharedArtifactMetadata(artifact, state.sourceSha);
|
|
37602
|
+
messages.push(`Already installed ${label}: ${portablePath(artifact.installPath)}`);
|
|
37603
|
+
continue;
|
|
37604
|
+
}
|
|
37605
|
+
await ensureDirectory((0, import_node_path4.dirname)(artifact.installPath), false);
|
|
37606
|
+
await (0, import_promises4.writeFile)(artifact.installPath, state.sourceContent, { encoding: "utf8", mode: 384 });
|
|
37607
|
+
await writeSharedArtifactMetadata(artifact, state.sourceSha);
|
|
37608
|
+
installedCount += 1;
|
|
37609
|
+
messages.push(
|
|
37610
|
+
`${sharedArtifactInstallVerb(state.status, options.force === true)} ${label}: ${portablePath(artifact.installPath)}`
|
|
37611
|
+
);
|
|
37612
|
+
}
|
|
37613
|
+
return {
|
|
37614
|
+
installedCount,
|
|
37615
|
+
messages,
|
|
37616
|
+
syncedTeams: syncResult.syncedTeams,
|
|
37617
|
+
team: team.name,
|
|
37618
|
+
warnings: syncResult.warnings
|
|
37619
|
+
};
|
|
37620
|
+
}
|
|
37404
37621
|
function normalizeTeamName(input) {
|
|
37405
37622
|
const candidate = (input ?? "default").trim();
|
|
37406
37623
|
if (!candidate) {
|
|
@@ -37535,6 +37752,254 @@ function vikingUriToWorktreeRelative(config2, uri, team) {
|
|
|
37535
37752
|
}
|
|
37536
37753
|
return uri.slice(prefix.length);
|
|
37537
37754
|
}
|
|
37755
|
+
async function isRegularFileNoSymlink(path) {
|
|
37756
|
+
try {
|
|
37757
|
+
const stat2 = await (0, import_promises4.lstat)(path);
|
|
37758
|
+
return stat2.isFile();
|
|
37759
|
+
} catch (_err) {
|
|
37760
|
+
return false;
|
|
37761
|
+
}
|
|
37762
|
+
}
|
|
37763
|
+
function inferShareArtifact(path, options) {
|
|
37764
|
+
const normalizedPath = path.split(import_node_path4.sep).join("/");
|
|
37765
|
+
const fileName = (0, import_node_path4.basename)(path);
|
|
37766
|
+
const lowerFileName = fileName.toLowerCase();
|
|
37767
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
37768
|
+
const inferredKind = lowerFileName === "skill.md" ? "skill" : lowerPath.includes("/.claude/commands/") && lowerFileName.endsWith(".md") ? "command" : void 0;
|
|
37769
|
+
const inferredAgent = lowerPath.includes("/.codex/skills/") ? "codex" : lowerPath.includes("/.claude/skills/") || lowerPath.includes("/.claude/commands/") ? "claude" : void 0;
|
|
37770
|
+
const extensionIndex = fileName.lastIndexOf(".");
|
|
37771
|
+
const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
|
|
37772
|
+
const inferredName = lowerFileName === "skill.md" ? (0, import_node_path4.basename)((0, import_node_path4.dirname)(path)) : stem;
|
|
37773
|
+
const kind = options.kind ?? inferredKind;
|
|
37774
|
+
const agent = options.agent ?? inferredAgent;
|
|
37775
|
+
const name = options.name ?? inferredName;
|
|
37776
|
+
if (kind !== "skill" && kind !== "command") {
|
|
37777
|
+
throw new Error("Could not infer artifact kind. Pass --kind skill or --kind command.");
|
|
37778
|
+
}
|
|
37779
|
+
if (agent !== "codex" && agent !== "claude") {
|
|
37780
|
+
throw new Error("Could not infer artifact agent. Pass --agent codex or --agent claude.");
|
|
37781
|
+
}
|
|
37782
|
+
if (kind === "skill" && lowerFileName !== "skill.md") {
|
|
37783
|
+
throw new Error("Skill artifacts must point at a SKILL.md file.");
|
|
37784
|
+
}
|
|
37785
|
+
if (kind === "command" && !lowerFileName.endsWith(".md")) {
|
|
37786
|
+
throw new Error("Command artifacts must be Markdown files.");
|
|
37787
|
+
}
|
|
37788
|
+
if (kind === "command" && agent !== "claude") {
|
|
37789
|
+
throw new Error("Only Claude command artifacts are supported.");
|
|
37790
|
+
}
|
|
37791
|
+
if (name.trim().length === 0) {
|
|
37792
|
+
throw new Error("Artifact name cannot be empty.");
|
|
37793
|
+
}
|
|
37794
|
+
return { agent, kind, name: uriSegment(name) };
|
|
37795
|
+
}
|
|
37796
|
+
function sharedArtifactRelativePath(artifact) {
|
|
37797
|
+
if (artifact.kind === "skill") {
|
|
37798
|
+
return `${SHAREABLE_ARTIFACT_DIR}/skills/${artifact.agent}/${artifact.name}/SKILL.md`;
|
|
37799
|
+
}
|
|
37800
|
+
return `${SHAREABLE_ARTIFACT_DIR}/commands/${artifact.agent}/${artifact.name}.md`;
|
|
37801
|
+
}
|
|
37802
|
+
function sharedArtifactFromRelativePath(relativePath) {
|
|
37803
|
+
const parts = relativePath.split("/");
|
|
37804
|
+
if (parts[0] !== SHAREABLE_ARTIFACT_DIR) {
|
|
37805
|
+
return void 0;
|
|
37806
|
+
}
|
|
37807
|
+
if (parts.length === 5 && parts[1] === "skills" && (parts[2] === "codex" || parts[2] === "claude") && parts[4] === "SKILL.md") {
|
|
37808
|
+
return { agent: parts[2], kind: "skill", name: parts[3] };
|
|
37809
|
+
}
|
|
37810
|
+
if (parts.length === 4 && parts[1] === "commands" && parts[2] === "claude" && parts[3].endsWith(".md")) {
|
|
37811
|
+
return { agent: "claude", kind: "command", name: parts[3].slice(0, -".md".length) };
|
|
37812
|
+
}
|
|
37813
|
+
return void 0;
|
|
37814
|
+
}
|
|
37815
|
+
async function collectSharedArtifacts(worktree, team) {
|
|
37816
|
+
const root = (0, import_node_path4.join)(worktree, SHAREABLE_ARTIFACT_DIR);
|
|
37817
|
+
if (!await isDirectory(root)) {
|
|
37818
|
+
return [];
|
|
37819
|
+
}
|
|
37820
|
+
const out = [];
|
|
37821
|
+
async function visit(path) {
|
|
37822
|
+
const entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
|
|
37823
|
+
for (const entry of entries) {
|
|
37824
|
+
const full = (0, import_node_path4.join)(path, entry.name);
|
|
37825
|
+
if (entry.isDirectory()) {
|
|
37826
|
+
await visit(full);
|
|
37827
|
+
continue;
|
|
37828
|
+
}
|
|
37829
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
37830
|
+
continue;
|
|
37831
|
+
}
|
|
37832
|
+
const relativePath = (0, import_node_path4.relative)(worktree, full).split(import_node_path4.sep).join("/");
|
|
37833
|
+
const artifact = sharedArtifactFromRelativePath(relativePath);
|
|
37834
|
+
if (artifact === void 0) {
|
|
37835
|
+
continue;
|
|
37836
|
+
}
|
|
37837
|
+
out.push({
|
|
37838
|
+
artifact,
|
|
37839
|
+
installPath: sharedArtifactInstallPath(team, artifact),
|
|
37840
|
+
sourcePath: full,
|
|
37841
|
+
sourceRelativePath: relativePath,
|
|
37842
|
+
team
|
|
37843
|
+
});
|
|
37844
|
+
}
|
|
37845
|
+
}
|
|
37846
|
+
await visit(root);
|
|
37847
|
+
return out.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
|
|
37848
|
+
}
|
|
37849
|
+
function filterSharedArtifacts(artifacts, options) {
|
|
37850
|
+
const name = options.name === void 0 ? void 0 : uriSegment(options.name);
|
|
37851
|
+
return artifacts.filter((artifact) => {
|
|
37852
|
+
if (options.agent !== void 0 && artifact.artifact.agent !== options.agent) {
|
|
37853
|
+
return false;
|
|
37854
|
+
}
|
|
37855
|
+
if (options.kind !== void 0 && artifact.artifact.kind !== options.kind) {
|
|
37856
|
+
return false;
|
|
37857
|
+
}
|
|
37858
|
+
if (name !== void 0 && artifact.artifact.name !== name) {
|
|
37859
|
+
return false;
|
|
37860
|
+
}
|
|
37861
|
+
return true;
|
|
37862
|
+
});
|
|
37863
|
+
}
|
|
37864
|
+
async function maybeSyncSharedArtifacts(config2, options) {
|
|
37865
|
+
if (options.sync === false) {
|
|
37866
|
+
return { syncedTeams: [], warnings: [] };
|
|
37867
|
+
}
|
|
37868
|
+
return syncSharedReposBeforeAgentRead(config2);
|
|
37869
|
+
}
|
|
37870
|
+
async function sharedArtifactInstallStatus(artifact) {
|
|
37871
|
+
return (await sharedArtifactInstallState(artifact)).status;
|
|
37872
|
+
}
|
|
37873
|
+
async function sharedArtifactInstallState(artifact) {
|
|
37874
|
+
const sourceContent = await (0, import_promises4.readFile)(artifact.sourcePath, "utf8");
|
|
37875
|
+
const sourceSha = sha256(sourceContent);
|
|
37876
|
+
const existingContent = await readFileIfExists(artifact.installPath) ?? void 0;
|
|
37877
|
+
if (existingContent === void 0) {
|
|
37878
|
+
return { sourceContent, sourceSha, status: "not_installed" };
|
|
37879
|
+
}
|
|
37880
|
+
const existingSha = sha256(existingContent);
|
|
37881
|
+
const metadata = await readSharedArtifactMetadata(artifact);
|
|
37882
|
+
if (existingSha === sourceSha) {
|
|
37883
|
+
return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "current" };
|
|
37884
|
+
}
|
|
37885
|
+
if (metadata === void 0) {
|
|
37886
|
+
return { existingContent, existingSha, sourceContent, sourceSha, status: "local_modified" };
|
|
37887
|
+
}
|
|
37888
|
+
const remoteChanged = metadata.sourceSha256 !== sourceSha;
|
|
37889
|
+
const localChanged = metadata.installedSha256 !== existingSha;
|
|
37890
|
+
if (remoteChanged && localChanged) {
|
|
37891
|
+
return {
|
|
37892
|
+
existingContent,
|
|
37893
|
+
existingSha,
|
|
37894
|
+
metadata,
|
|
37895
|
+
sourceContent,
|
|
37896
|
+
sourceSha,
|
|
37897
|
+
status: "remote_changed_and_local_modified"
|
|
37898
|
+
};
|
|
37899
|
+
}
|
|
37900
|
+
if (remoteChanged) {
|
|
37901
|
+
return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "update_available" };
|
|
37902
|
+
}
|
|
37903
|
+
return { existingContent, existingSha, metadata, sourceContent, sourceSha, status: "local_modified" };
|
|
37904
|
+
}
|
|
37905
|
+
async function readSharedArtifactMetadata(artifact) {
|
|
37906
|
+
const raw = await readFileIfExists(sharedArtifactMetadataPath(artifact));
|
|
37907
|
+
if (raw === void 0) {
|
|
37908
|
+
return void 0;
|
|
37909
|
+
}
|
|
37910
|
+
const parsed = parseJsonConfigObject(raw);
|
|
37911
|
+
if (parsed === void 0 || parsed.version !== ARTIFACT_INSTALL_METADATA_VERSION) {
|
|
37912
|
+
return void 0;
|
|
37913
|
+
}
|
|
37914
|
+
const artifactValue = parsed.artifact;
|
|
37915
|
+
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)) {
|
|
37916
|
+
return void 0;
|
|
37917
|
+
}
|
|
37918
|
+
const metadataArtifact = artifactValue;
|
|
37919
|
+
if (metadataArtifact.agent !== artifact.artifact.agent || metadataArtifact.kind !== artifact.artifact.kind || metadataArtifact.name !== artifact.artifact.name) {
|
|
37920
|
+
return void 0;
|
|
37921
|
+
}
|
|
37922
|
+
return {
|
|
37923
|
+
artifact: artifact.artifact,
|
|
37924
|
+
installedAt: parsed.installedAt,
|
|
37925
|
+
installedSha256: parsed.installedSha256,
|
|
37926
|
+
source: parsed.source,
|
|
37927
|
+
sourceSha256: parsed.sourceSha256,
|
|
37928
|
+
team: parsed.team,
|
|
37929
|
+
version: ARTIFACT_INSTALL_METADATA_VERSION
|
|
37930
|
+
};
|
|
37931
|
+
}
|
|
37932
|
+
async function writeSharedArtifactMetadata(artifact, sourceSha) {
|
|
37933
|
+
const metadata = {
|
|
37934
|
+
artifact: artifact.artifact,
|
|
37935
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37936
|
+
installedSha256: sourceSha,
|
|
37937
|
+
source: artifact.sourceRelativePath,
|
|
37938
|
+
sourceSha256: sourceSha,
|
|
37939
|
+
team: artifact.team,
|
|
37940
|
+
version: ARTIFACT_INSTALL_METADATA_VERSION
|
|
37941
|
+
};
|
|
37942
|
+
const metadataPath = sharedArtifactMetadataPath(artifact);
|
|
37943
|
+
await ensureDirectory((0, import_node_path4.dirname)(metadataPath), false);
|
|
37944
|
+
await (0, import_promises4.writeFile)(metadataPath, `${JSON.stringify(metadata, void 0, 2)}
|
|
37945
|
+
`, { encoding: "utf8", mode: 384 });
|
|
37946
|
+
}
|
|
37947
|
+
function sharedArtifactMetadataPath(artifact) {
|
|
37948
|
+
return `${artifact.installPath}.threadnote-install.json`;
|
|
37949
|
+
}
|
|
37950
|
+
function sharedArtifactDryRunVerb(status, force) {
|
|
37951
|
+
switch (status) {
|
|
37952
|
+
case "not_installed":
|
|
37953
|
+
return "Would install";
|
|
37954
|
+
case "current":
|
|
37955
|
+
return "Already installed";
|
|
37956
|
+
case "update_available":
|
|
37957
|
+
return "Would update";
|
|
37958
|
+
case "local_modified":
|
|
37959
|
+
case "remote_changed_and_local_modified":
|
|
37960
|
+
return force ? "Would replace" : "Would skip modified";
|
|
37961
|
+
}
|
|
37962
|
+
}
|
|
37963
|
+
function sharedArtifactDryRunSuffix(status, force) {
|
|
37964
|
+
if ((status === "local_modified" || status === "remote_changed_and_local_modified") && !force) {
|
|
37965
|
+
return " (pass --force to replace local changes)";
|
|
37966
|
+
}
|
|
37967
|
+
return "";
|
|
37968
|
+
}
|
|
37969
|
+
function sharedArtifactInstallVerb(status, force) {
|
|
37970
|
+
if (force && (status === "local_modified" || status === "remote_changed_and_local_modified")) {
|
|
37971
|
+
return "Replaced";
|
|
37972
|
+
}
|
|
37973
|
+
if (status === "update_available") {
|
|
37974
|
+
return "Updated";
|
|
37975
|
+
}
|
|
37976
|
+
return "Installed";
|
|
37977
|
+
}
|
|
37978
|
+
function sharedArtifactFilterLabel(options) {
|
|
37979
|
+
const filters = [];
|
|
37980
|
+
if (options.kind !== void 0) {
|
|
37981
|
+
filters.push(`kind=${options.kind}`);
|
|
37982
|
+
}
|
|
37983
|
+
if (options.agent !== void 0) {
|
|
37984
|
+
filters.push(`agent=${options.agent}`);
|
|
37985
|
+
}
|
|
37986
|
+
if (options.name !== void 0) {
|
|
37987
|
+
filters.push(`name=${uriSegment(options.name)}`);
|
|
37988
|
+
}
|
|
37989
|
+
return filters.join(", ");
|
|
37990
|
+
}
|
|
37991
|
+
function sharedArtifactLabel(artifact) {
|
|
37992
|
+
return `${artifact.kind} ${artifact.agent}/${artifact.name}`;
|
|
37993
|
+
}
|
|
37994
|
+
function sharedArtifactInstallPath(team, artifact) {
|
|
37995
|
+
if (artifact.kind === "skill" && artifact.agent === "codex") {
|
|
37996
|
+
return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".codex", "skills", "threadnote", team, artifact.name, "SKILL.md");
|
|
37997
|
+
}
|
|
37998
|
+
if (artifact.kind === "skill" && artifact.agent === "claude") {
|
|
37999
|
+
return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".claude", "skills", "threadnote", team, artifact.name, "SKILL.md");
|
|
38000
|
+
}
|
|
38001
|
+
return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".claude", "commands", "threadnote", team, `${artifact.name}.md`);
|
|
38002
|
+
}
|
|
37538
38003
|
function stripPersonalProvenance(content) {
|
|
37539
38004
|
const lines = content.split("\n");
|
|
37540
38005
|
let headerEnd = lines.length;
|
|
@@ -37571,9 +38036,9 @@ async function ensureSharedDirectoryChain(config2, ov, memoryUri, dryRun, option
|
|
|
37571
38036
|
continue;
|
|
37572
38037
|
}
|
|
37573
38038
|
if (options.quiet === true) {
|
|
37574
|
-
await runCommand(ov, withIdentity(config2, ["mkdir", uri, "--description", "Threadnote shared
|
|
38039
|
+
await runCommand(ov, withIdentity(config2, ["mkdir", uri, "--description", "Threadnote shared context."]));
|
|
37575
38040
|
} else {
|
|
37576
|
-
await maybeRun(false, ov, withIdentity(config2, ["mkdir", uri, "--description", "Threadnote shared
|
|
38041
|
+
await maybeRun(false, ov, withIdentity(config2, ["mkdir", uri, "--description", "Threadnote shared context."]));
|
|
37577
38042
|
}
|
|
37578
38043
|
}
|
|
37579
38044
|
}
|
|
@@ -37802,7 +38267,7 @@ async function applyChangesToOpenViking(config2, team, changes, options = {}) {
|
|
|
37802
38267
|
continue;
|
|
37803
38268
|
}
|
|
37804
38269
|
const firstSegment = change.relativePath.split("/")[0];
|
|
37805
|
-
if (!
|
|
38270
|
+
if (!SHAREABLE_TOP_LEVEL_DIRS.includes(firstSegment)) {
|
|
37806
38271
|
continue;
|
|
37807
38272
|
}
|
|
37808
38273
|
const uri = workfileToVikingUri(config2, team, change.path);
|
|
@@ -37879,6 +38344,8 @@ async function main() {
|
|
|
37879
38344
|
"When updating the same active issue, pass project/topic or replaceUri to remember_context so duplicate durable memories or handoffs do not accumulate.",
|
|
37880
38345
|
"Use compact_context with dryRun=true for scoped memory hygiene when recall surfaces overlapping active memories.",
|
|
37881
38346
|
"To share a durable memory with teammates, call `share_publish` with its viking:// URI. share_publish scrubs for secrets, writes and pushes the shared copy first, then removes the personal copy after the push succeeds. Do not publish handoffs, preferences, or anything carrying machine-local paths or in-flight task context.",
|
|
38347
|
+
"To share a local Codex/Claude skill or Claude command with teammates, call `share_skill` with the local file path. It publishes into the shared artifact catalog after the same scrubber checks.",
|
|
38348
|
+
"To use a team shared skill as a native local skill, call `list_shared_skills` first, then `install_shared_skill` for the selected name/agent/kind.",
|
|
37882
38349
|
"Do not store secrets, customer data, raw production logs, or credentials."
|
|
37883
38350
|
].join("\n")
|
|
37884
38351
|
}
|
|
@@ -38079,6 +38546,80 @@ function registerTools(server, config2) {
|
|
|
38079
38546
|
return runSharePublishTool(config2, checkedUri.value, { message, preview, push, redact, team });
|
|
38080
38547
|
}
|
|
38081
38548
|
);
|
|
38549
|
+
server.registerTool(
|
|
38550
|
+
"share_skill",
|
|
38551
|
+
{
|
|
38552
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
38553
|
+
description: "Publish a local Codex/Claude skill or Claude command markdown file into a team's shared artifact catalog. Path inference handles ~/.codex/skills/**/SKILL.md, ~/.claude/skills/**/SKILL.md, and ~/.claude/commands/**/*.md; pass agent/kind/name when sharing from another path. Default team is used unless team is provided. Pass preview=true to inspect bytes without writing or committing.",
|
|
38554
|
+
inputSchema: {
|
|
38555
|
+
agent: external_exports.enum(["codex", "claude"]).optional().describe("Agent owner when path inference is ambiguous"),
|
|
38556
|
+
force: external_exports.boolean().optional().describe("Replace an existing shared artifact with different content"),
|
|
38557
|
+
kind: external_exports.enum(["skill", "command"]).optional().describe("Artifact kind when path inference is ambiguous"),
|
|
38558
|
+
message: external_exports.string().optional().describe('Commit message override; defaults to "share: publish <path>"'),
|
|
38559
|
+
name: external_exports.string().optional().describe("Shared artifact name; defaults to skill directory or command file stem"),
|
|
38560
|
+
path: external_exports.string().optional().describe("Required local path to SKILL.md or a Claude command markdown file"),
|
|
38561
|
+
preview: external_exports.boolean().optional().describe("Return the bytes that would land in the shared git repo"),
|
|
38562
|
+
push: external_exports.boolean().optional().describe("Push to remote after committing; defaults to true"),
|
|
38563
|
+
redact: external_exports.boolean().optional().describe("Replace soft-leak matches (local paths) with placeholders and continue; credentials still block."),
|
|
38564
|
+
team: external_exports.string().optional().describe("Team name; defaults to the configured default team")
|
|
38565
|
+
}
|
|
38566
|
+
},
|
|
38567
|
+
async ({ agent, force, kind, message, name, path, preview, push, redact, team }) => {
|
|
38568
|
+
const checkedPath = requiredText(path, "share_skill", "path", {
|
|
38569
|
+
path: "~/.codex/skills/example/SKILL.md"
|
|
38570
|
+
});
|
|
38571
|
+
if (!checkedPath.ok) {
|
|
38572
|
+
return checkedPath.error;
|
|
38573
|
+
}
|
|
38574
|
+
return runShareSkillTool(config2, checkedPath.value, {
|
|
38575
|
+
agent,
|
|
38576
|
+
force,
|
|
38577
|
+
kind,
|
|
38578
|
+
message,
|
|
38579
|
+
name,
|
|
38580
|
+
preview,
|
|
38581
|
+
push,
|
|
38582
|
+
redact,
|
|
38583
|
+
team
|
|
38584
|
+
});
|
|
38585
|
+
}
|
|
38586
|
+
);
|
|
38587
|
+
server.registerTool(
|
|
38588
|
+
"list_shared_skills",
|
|
38589
|
+
{
|
|
38590
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
38591
|
+
description: "List shared Codex/Claude skills and Claude commands available in a configured Threadnote team repo, including whether each one is already installed locally.",
|
|
38592
|
+
inputSchema: {
|
|
38593
|
+
agent: external_exports.enum(["codex", "claude"]).optional().describe("Optional agent filter"),
|
|
38594
|
+
kind: external_exports.enum(["skill", "command"]).optional().describe("Optional kind filter"),
|
|
38595
|
+
name: external_exports.string().optional().describe("Optional shared artifact name filter"),
|
|
38596
|
+
team: external_exports.string().optional().describe("Team name; defaults to the configured default team")
|
|
38597
|
+
}
|
|
38598
|
+
},
|
|
38599
|
+
async ({ agent, kind, name, team }) => runListSharedSkillsTool(config2, { agent, kind, name, team })
|
|
38600
|
+
);
|
|
38601
|
+
server.registerTool(
|
|
38602
|
+
"install_shared_skill",
|
|
38603
|
+
{
|
|
38604
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
38605
|
+
description: "Install one shared Codex/Claude skill or Claude command from a configured Threadnote team repo into the local agent skill/command directory. Use list_shared_skills first to find names and disambiguate agent/kind.",
|
|
38606
|
+
inputSchema: {
|
|
38607
|
+
agent: external_exports.enum(["codex", "claude"]).optional().describe("Agent owner; required when name is ambiguous"),
|
|
38608
|
+
dryRun: external_exports.boolean().optional().describe("Preview install without writing local files"),
|
|
38609
|
+
force: external_exports.boolean().optional().describe("Replace an existing installed artifact with different content"),
|
|
38610
|
+
kind: external_exports.enum(["skill", "command"]).optional().describe("Artifact kind; required when name is ambiguous"),
|
|
38611
|
+
name: external_exports.string().optional().describe("Required shared artifact name to install"),
|
|
38612
|
+
team: external_exports.string().optional().describe("Team name; defaults to the configured default team")
|
|
38613
|
+
}
|
|
38614
|
+
},
|
|
38615
|
+
async ({ agent, dryRun, force, kind, name, team }) => {
|
|
38616
|
+
const checkedName = requiredText(name, "install_shared_skill", "name", { name: "reviewer" });
|
|
38617
|
+
if (!checkedName.ok) {
|
|
38618
|
+
return checkedName.error;
|
|
38619
|
+
}
|
|
38620
|
+
return runInstallSharedSkillTool(config2, checkedName.value, { agent, dryRun, force, kind, team });
|
|
38621
|
+
}
|
|
38622
|
+
);
|
|
38082
38623
|
}
|
|
38083
38624
|
function registerOpenVikingParityTools(server, config2) {
|
|
38084
38625
|
server.registerTool(
|
|
@@ -39439,27 +39980,85 @@ async function runSharePublishTool(config2, sourceUri, options) {
|
|
|
39439
39980
|
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
39440
39981
|
}
|
|
39441
39982
|
}
|
|
39983
|
+
async function runShareSkillTool(config2, sourcePath, options) {
|
|
39984
|
+
try {
|
|
39985
|
+
const result = await shareAgentArtifact(config2, sourcePath, options);
|
|
39986
|
+
const lines = [...result.messages, ...result.gitMessages];
|
|
39987
|
+
if (result.previewContent !== void 0) {
|
|
39988
|
+
lines.push("-----BEGIN PREVIEW-----");
|
|
39989
|
+
lines.push(result.previewContent);
|
|
39990
|
+
lines.push("-----END PREVIEW-----");
|
|
39991
|
+
}
|
|
39992
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
39993
|
+
} catch (err) {
|
|
39994
|
+
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
39995
|
+
}
|
|
39996
|
+
}
|
|
39997
|
+
async function runListSharedSkillsTool(config2, options) {
|
|
39998
|
+
try {
|
|
39999
|
+
const result = await listSharedAgentArtifacts(config2, options);
|
|
40000
|
+
if (result.artifacts.length === 0) {
|
|
40001
|
+
const lines2 = shareArtifactToolHeader(result.team, result.syncedTeams, result.warnings);
|
|
40002
|
+
lines2.push(`No shared skills or commands found for team "${result.team}".`);
|
|
40003
|
+
return { content: [{ type: "text", text: lines2.join("\n") }] };
|
|
40004
|
+
}
|
|
40005
|
+
const lines = shareArtifactToolHeader(result.team, result.syncedTeams, result.warnings);
|
|
40006
|
+
lines.push(`Shared skills and commands for team "${result.team}":`);
|
|
40007
|
+
for (const artifact of result.artifacts) {
|
|
40008
|
+
lines.push(
|
|
40009
|
+
`- ${artifact.artifact.kind} ${artifact.artifact.agent}/${artifact.artifact.name} (${artifact.installStatus})`
|
|
40010
|
+
);
|
|
40011
|
+
lines.push(
|
|
40012
|
+
` install: install_shared_skill({"name":"${artifact.artifact.name}","agent":"${artifact.artifact.agent}","kind":"${artifact.artifact.kind}"})`
|
|
40013
|
+
);
|
|
40014
|
+
}
|
|
40015
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
40016
|
+
} catch (err) {
|
|
40017
|
+
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
40018
|
+
}
|
|
40019
|
+
}
|
|
40020
|
+
async function runInstallSharedSkillTool(config2, name, options) {
|
|
40021
|
+
try {
|
|
40022
|
+
const result = await installSharedAgentArtifacts(config2, {
|
|
40023
|
+
...options,
|
|
40024
|
+
apply: options.dryRun !== true,
|
|
40025
|
+
name
|
|
40026
|
+
});
|
|
40027
|
+
return {
|
|
40028
|
+
content: [
|
|
40029
|
+
{
|
|
40030
|
+
type: "text",
|
|
40031
|
+
text: [...shareArtifactToolHeader(result.team, result.syncedTeams, result.warnings), ...result.messages].join(
|
|
40032
|
+
"\n"
|
|
40033
|
+
)
|
|
40034
|
+
}
|
|
40035
|
+
],
|
|
40036
|
+
isError: false
|
|
40037
|
+
};
|
|
40038
|
+
} catch (err) {
|
|
40039
|
+
return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
|
|
40040
|
+
}
|
|
40041
|
+
}
|
|
40042
|
+
function shareArtifactToolHeader(team, syncedTeams, warnings) {
|
|
40043
|
+
const lines = [`Team: ${team}`];
|
|
40044
|
+
if (syncedTeams.length > 0) {
|
|
40045
|
+
lines.push(`Synced shared teams: ${syncedTeams.join(", ")}`);
|
|
40046
|
+
}
|
|
40047
|
+
for (const warning2 of warnings) {
|
|
40048
|
+
lines.push(`Warning: ${warning2}`);
|
|
40049
|
+
}
|
|
40050
|
+
return lines;
|
|
40051
|
+
}
|
|
39442
40052
|
function withIdentity2(config2, args) {
|
|
39443
40053
|
return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
|
|
39444
40054
|
}
|
|
39445
40055
|
async function requiredOpenVikingCli2() {
|
|
39446
|
-
const command2 =
|
|
40056
|
+
const command2 = await findOpenVikingCli();
|
|
39447
40057
|
if (!command2) {
|
|
39448
40058
|
throw new Error("Neither ov nor openviking was found. Run threadnote install first.");
|
|
39449
40059
|
}
|
|
39450
40060
|
return command2;
|
|
39451
40061
|
}
|
|
39452
|
-
async function firstExistingPath(paths) {
|
|
39453
|
-
for (const path of paths) {
|
|
39454
|
-
try {
|
|
39455
|
-
await (0, import_promises5.access)(path);
|
|
39456
|
-
return await (0, import_promises5.realpath)(path);
|
|
39457
|
-
} catch (_err) {
|
|
39458
|
-
continue;
|
|
39459
|
-
}
|
|
39460
|
-
}
|
|
39461
|
-
return void 0;
|
|
39462
|
-
}
|
|
39463
40062
|
main().catch((err) => {
|
|
39464
40063
|
process.stderr.write(`${errorMessage(err)}
|
|
39465
40064
|
`);
|