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/threadnote.cjs
CHANGED
|
@@ -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 =
|
|
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
|
|
3746
|
+
const command2 = await findOpenVikingCli();
|
|
3747
3747
|
if (!command2) {
|
|
3748
|
-
throw new Error(
|
|
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
|
|
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
|
|
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", ...
|
|
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
|
|
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
|
|
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 && !
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
13068
|
-
|
|
13069
|
-
const
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
});
|