@alessandroraffa/tangyr 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +179 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17378,21 +17378,33 @@ function collectProjectScopeFindings(projectRoot, tools, options) {
|
|
|
17378
17378
|
) : void 0;
|
|
17379
17379
|
const probePaths = buildProjectProbes(tool, projectRoot, profile);
|
|
17380
17380
|
for (const { resolvedPath, description, classification } of probePaths) {
|
|
17381
|
-
|
|
17381
|
+
let stat;
|
|
17382
|
+
try {
|
|
17383
|
+
stat = fs11.lstatSync(resolvedPath);
|
|
17384
|
+
} catch {
|
|
17382
17385
|
continue;
|
|
17383
17386
|
}
|
|
17384
|
-
const
|
|
17387
|
+
const type = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
17388
|
+
const ours = type === "file" && carriesTangyrMarker(resolvedPath);
|
|
17385
17389
|
findings.push({
|
|
17386
17390
|
path: resolvedPath,
|
|
17387
17391
|
tool,
|
|
17388
|
-
type
|
|
17389
|
-
classification,
|
|
17390
|
-
description
|
|
17392
|
+
type,
|
|
17393
|
+
classification: ours ? "compatible" : classification,
|
|
17394
|
+
description: ours ? `${description} (written by Tangyr)` : description
|
|
17391
17395
|
});
|
|
17392
17396
|
}
|
|
17393
17397
|
}
|
|
17394
17398
|
return findings;
|
|
17395
17399
|
}
|
|
17400
|
+
function carriesTangyrMarker(filePath) {
|
|
17401
|
+
try {
|
|
17402
|
+
const firstNonEmpty = fs11.readFileSync(filePath, "utf8").split("\n").find((line) => line.trim().length > 0) ?? "";
|
|
17403
|
+
return extractProvenanceMarker(firstNonEmpty) !== null;
|
|
17404
|
+
} catch {
|
|
17405
|
+
return false;
|
|
17406
|
+
}
|
|
17407
|
+
}
|
|
17396
17408
|
function requestedAssessTools(toolFilter) {
|
|
17397
17409
|
return toolFilter ?? [...DEFAULT_ASSESS_TARGETS];
|
|
17398
17410
|
}
|
|
@@ -21351,6 +21363,19 @@ function writeSharedFile(filePath, parsed, serializer, isFirstWrite, logger) {
|
|
|
21351
21363
|
`tangyr: first modification of ${filePath} \u2014 unmanaged data will be preserved but exact whitespace or comments may not be.`
|
|
21352
21364
|
);
|
|
21353
21365
|
}
|
|
21366
|
+
let realTarget = null;
|
|
21367
|
+
try {
|
|
21368
|
+
if (fs19.lstatSync(filePath).isSymbolicLink()) {
|
|
21369
|
+
realTarget = fs19.realpathSync(filePath);
|
|
21370
|
+
}
|
|
21371
|
+
} catch {
|
|
21372
|
+
realTarget = null;
|
|
21373
|
+
}
|
|
21374
|
+
if (realTarget && realTarget !== filePath) {
|
|
21375
|
+
logger.warn(
|
|
21376
|
+
`tangyr: ${filePath} is a symlink \u2014 the file being modified is ${realTarget}`
|
|
21377
|
+
);
|
|
21378
|
+
}
|
|
21354
21379
|
fs19.mkdirSync(path18.dirname(filePath), { recursive: true });
|
|
21355
21380
|
fs19.writeFileSync(filePath, serialized);
|
|
21356
21381
|
return true;
|
|
@@ -22169,11 +22194,23 @@ async function runUninstallCommand(options, logger) {
|
|
|
22169
22194
|
}
|
|
22170
22195
|
const claudeRoots = scope === "global" && selectedTools.has("claude-code") ? resolveClaudeRootsForUninstall(manifest, runtime) : [];
|
|
22171
22196
|
let removedCount = 0;
|
|
22197
|
+
const removedParentDirs = /* @__PURE__ */ new Set();
|
|
22172
22198
|
for (const file of useManifestPathRemoval ? manifestArtifacts : []) {
|
|
22173
22199
|
if (isMergeSurfaceKey(file.relativePath)) {
|
|
22174
|
-
|
|
22175
|
-
|
|
22200
|
+
const surfacePath = file.path ?? resolveManifestKeyToPath(
|
|
22201
|
+
file.relativePath,
|
|
22202
|
+
scope,
|
|
22203
|
+
runtime,
|
|
22204
|
+
uninstallProjectRoot
|
|
22176
22205
|
);
|
|
22206
|
+
if (surfacePath && stripTangyrFromMergeSurface(surfacePath, logger)) {
|
|
22207
|
+
removedParentDirs.add(path24.dirname(surfacePath));
|
|
22208
|
+
removedCount++;
|
|
22209
|
+
} else {
|
|
22210
|
+
logger.verbose(
|
|
22211
|
+
` left in place (merge surface, nothing of ours in it): ${surfacePath ?? file.relativePath}`
|
|
22212
|
+
);
|
|
22213
|
+
}
|
|
22177
22214
|
continue;
|
|
22178
22215
|
}
|
|
22179
22216
|
const isClaudeGlobal = file.relativePath.startsWith("claude-code/") && scope === "global";
|
|
@@ -22186,6 +22223,7 @@ async function runUninstallCommand(options, logger) {
|
|
|
22186
22223
|
for (const actualPath of paths) {
|
|
22187
22224
|
if (actualPath && (fs27.existsSync(actualPath) || isSymlink(actualPath))) {
|
|
22188
22225
|
removeManagedPath(actualPath);
|
|
22226
|
+
removedParentDirs.add(path24.dirname(actualPath));
|
|
22189
22227
|
logger.verbose(` removed: ${actualPath}`);
|
|
22190
22228
|
removedCount++;
|
|
22191
22229
|
} else {
|
|
@@ -22200,7 +22238,14 @@ async function runUninstallCommand(options, logger) {
|
|
|
22200
22238
|
uninstallProjectRoot
|
|
22201
22239
|
);
|
|
22202
22240
|
if (actualPath && (fs27.existsSync(actualPath) || isSymlink(actualPath))) {
|
|
22241
|
+
if (!stillMatchesRecord(actualPath, file.hash)) {
|
|
22242
|
+
logger.warn(
|
|
22243
|
+
` left in place (no longer what was installed): ${actualPath}`
|
|
22244
|
+
);
|
|
22245
|
+
continue;
|
|
22246
|
+
}
|
|
22203
22247
|
removeManagedPath(actualPath);
|
|
22248
|
+
removedParentDirs.add(path24.dirname(actualPath));
|
|
22204
22249
|
logger.verbose(` removed: ${actualPath}`);
|
|
22205
22250
|
removedCount++;
|
|
22206
22251
|
} else {
|
|
@@ -22228,7 +22273,16 @@ async function runUninstallCommand(options, logger) {
|
|
|
22228
22273
|
}
|
|
22229
22274
|
}
|
|
22230
22275
|
if (fullUninstall) {
|
|
22231
|
-
|
|
22276
|
+
const newestFirst = [...manifest.backups].reverse();
|
|
22277
|
+
const restored = /* @__PURE__ */ new Set();
|
|
22278
|
+
for (const backup of newestFirst) {
|
|
22279
|
+
if (restored.has(backup.originalPath)) {
|
|
22280
|
+
logger.verbose(
|
|
22281
|
+
` superseded by a newer backup, not restored: ${backup.backupLocation}`
|
|
22282
|
+
);
|
|
22283
|
+
continue;
|
|
22284
|
+
}
|
|
22285
|
+
restored.add(backup.originalPath);
|
|
22232
22286
|
try {
|
|
22233
22287
|
restoreBackup(backup, logger);
|
|
22234
22288
|
} catch (err) {
|
|
@@ -22242,6 +22296,11 @@ async function runUninstallCommand(options, logger) {
|
|
|
22242
22296
|
fs27.unlinkSync(manifestPath);
|
|
22243
22297
|
logger.verbose(` removed manifest: ${manifestPath}`);
|
|
22244
22298
|
}
|
|
22299
|
+
pruneEmptyDirs(path24.join(scopePath, "backups"), logger);
|
|
22300
|
+
const pruneBoundary = path24.dirname(scopePath);
|
|
22301
|
+
for (const dir of removedParentDirs) {
|
|
22302
|
+
pruneEmptyAncestors(dir, pruneBoundary, logger);
|
|
22303
|
+
}
|
|
22245
22304
|
try {
|
|
22246
22305
|
const remaining = fs27.readdirSync(scopePath);
|
|
22247
22306
|
if (remaining.length === 0) {
|
|
@@ -22621,6 +22680,86 @@ var MERGE_SURFACE_SLOTS = /* @__PURE__ */ new Set([
|
|
|
22621
22680
|
"mcp_shared_file",
|
|
22622
22681
|
"mcp_file"
|
|
22623
22682
|
]);
|
|
22683
|
+
function stripTangyrFromMergeSurface(filePath, logger) {
|
|
22684
|
+
const parsed = readJsonObject2(filePath);
|
|
22685
|
+
if (!parsed) {
|
|
22686
|
+
return false;
|
|
22687
|
+
}
|
|
22688
|
+
const cleaned = { ...parsed };
|
|
22689
|
+
const hooks = removeManagedClaudeHookEntries(
|
|
22690
|
+
parsed.hooks,
|
|
22691
|
+
(entry) => entry._tangyr === true || entry._proteus === true
|
|
22692
|
+
);
|
|
22693
|
+
if (hooks === void 0) {
|
|
22694
|
+
delete cleaned.hooks;
|
|
22695
|
+
} else {
|
|
22696
|
+
cleaned.hooks = hooks;
|
|
22697
|
+
}
|
|
22698
|
+
if (isRecord(parsed.mcpServers)) {
|
|
22699
|
+
const kept = Object.fromEntries(
|
|
22700
|
+
Object.entries(parsed.mcpServers).filter(
|
|
22701
|
+
([, server]) => !isRecord(server) || !isRecord(server._tangyr) && !isRecord(server._proteus)
|
|
22702
|
+
)
|
|
22703
|
+
);
|
|
22704
|
+
if (Object.keys(kept).length === 0) {
|
|
22705
|
+
delete cleaned.mcpServers;
|
|
22706
|
+
} else {
|
|
22707
|
+
cleaned.mcpServers = kept;
|
|
22708
|
+
}
|
|
22709
|
+
}
|
|
22710
|
+
delete cleaned._tangyr;
|
|
22711
|
+
delete cleaned._proteus;
|
|
22712
|
+
if (JSON.stringify(cleaned) === JSON.stringify(parsed)) {
|
|
22713
|
+
return false;
|
|
22714
|
+
}
|
|
22715
|
+
writeJsonOrRemove(filePath, cleaned);
|
|
22716
|
+
logger.verbose(` stripped Tangyr's block from: ${filePath}`);
|
|
22717
|
+
return true;
|
|
22718
|
+
}
|
|
22719
|
+
function pruneEmptyAncestors(startDir, stopAt, logger) {
|
|
22720
|
+
const boundary = path24.resolve(stopAt);
|
|
22721
|
+
let current = path24.resolve(startDir);
|
|
22722
|
+
while (current !== boundary && current.startsWith(`${boundary}${path24.sep}`)) {
|
|
22723
|
+
try {
|
|
22724
|
+
if (fs27.readdirSync(current).length > 0) {
|
|
22725
|
+
return;
|
|
22726
|
+
}
|
|
22727
|
+
fs27.rmdirSync(current);
|
|
22728
|
+
logger.verbose(` removed empty directory: ${current}`);
|
|
22729
|
+
} catch {
|
|
22730
|
+
return;
|
|
22731
|
+
}
|
|
22732
|
+
current = path24.dirname(current);
|
|
22733
|
+
}
|
|
22734
|
+
}
|
|
22735
|
+
function pruneEmptyDirs(dir, logger) {
|
|
22736
|
+
let stat;
|
|
22737
|
+
try {
|
|
22738
|
+
stat = fs27.lstatSync(dir);
|
|
22739
|
+
} catch {
|
|
22740
|
+
return;
|
|
22741
|
+
}
|
|
22742
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
22743
|
+
return;
|
|
22744
|
+
}
|
|
22745
|
+
for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
|
|
22746
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
22747
|
+
pruneEmptyDirs(path24.join(dir, entry.name), logger);
|
|
22748
|
+
}
|
|
22749
|
+
}
|
|
22750
|
+
try {
|
|
22751
|
+
fs27.rmdirSync(dir);
|
|
22752
|
+
logger.verbose(` removed empty directory: ${dir}`);
|
|
22753
|
+
} catch {
|
|
22754
|
+
}
|
|
22755
|
+
}
|
|
22756
|
+
function stillMatchesRecord(artifactPath, recorded) {
|
|
22757
|
+
if (isLegacyArtifactState(recorded)) {
|
|
22758
|
+
return true;
|
|
22759
|
+
}
|
|
22760
|
+
const actual = describeArtifactState(artifactPath);
|
|
22761
|
+
return actual === null || actual === recorded;
|
|
22762
|
+
}
|
|
22624
22763
|
function isMergeSurfaceKey(relativePath) {
|
|
22625
22764
|
const [, ...rest] = relativePath.split("/");
|
|
22626
22765
|
return MERGE_SURFACE_SLOTS.has(rest.join("/"));
|
|
@@ -24952,6 +25091,7 @@ function toConflictPolicy2(value) {
|
|
|
24952
25091
|
}
|
|
24953
25092
|
|
|
24954
25093
|
// src/commands/install.ts
|
|
25094
|
+
import crypto11 from "crypto";
|
|
24955
25095
|
import fs43 from "fs";
|
|
24956
25096
|
import path43 from "path";
|
|
24957
25097
|
|
|
@@ -26363,6 +26503,12 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
|
|
|
26363
26503
|
const firstNonEmptyLine = existing.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
26364
26504
|
const marker = extractProvenanceMarker(firstNonEmptyLine);
|
|
26365
26505
|
if (marker !== null && marker.hash === expectedBodyHash) {
|
|
26506
|
+
addArtifactToManifest(manifest, {
|
|
26507
|
+
relativePath: SHIM_MANIFEST_KEY,
|
|
26508
|
+
path: shimPath,
|
|
26509
|
+
hash: expectedBodyHash,
|
|
26510
|
+
origin: "tangyr-managed"
|
|
26511
|
+
});
|
|
26366
26512
|
logger?.verbose(
|
|
26367
26513
|
`instructions-shim: already current, skipped -> ${shimPath}`
|
|
26368
26514
|
);
|
|
@@ -30689,20 +30835,25 @@ function parseCommandFile(commandFile) {
|
|
|
30689
30835
|
function isBackupablePath(targetPath) {
|
|
30690
30836
|
try {
|
|
30691
30837
|
const lstat = fs43.lstatSync(targetPath);
|
|
30692
|
-
|
|
30693
|
-
return false;
|
|
30694
|
-
}
|
|
30695
|
-
return lstat.isFile() || lstat.isDirectory();
|
|
30838
|
+
return lstat.isSymbolicLink() || lstat.isFile() || lstat.isDirectory();
|
|
30696
30839
|
} catch {
|
|
30697
30840
|
return false;
|
|
30698
30841
|
}
|
|
30699
30842
|
}
|
|
30700
30843
|
function backupConflictingTarget(targetPath, scopePath) {
|
|
30701
30844
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace("T", "T").replace(/\.\d+Z$/u, "Z");
|
|
30702
|
-
const
|
|
30845
|
+
const scopeRoot = path43.dirname(scopePath);
|
|
30846
|
+
let relativeToRoot = path43.relative(scopeRoot, targetPath);
|
|
30847
|
+
if (relativeToRoot.startsWith("..") || path43.isAbsolute(relativeToRoot)) {
|
|
30848
|
+
const digest = crypto11.createHash("sha256").update(path43.dirname(targetPath)).digest("hex").slice(0, 12);
|
|
30849
|
+
relativeToRoot = path43.join(`_outside-${digest}`, path43.basename(targetPath));
|
|
30850
|
+
}
|
|
30703
30851
|
const backupPath = path43.join(scopePath, "backups", ts2, relativeToRoot);
|
|
30704
30852
|
fs43.mkdirSync(path43.dirname(backupPath), { recursive: true });
|
|
30705
|
-
|
|
30853
|
+
const stat = fs43.lstatSync(targetPath);
|
|
30854
|
+
if (stat.isSymbolicLink()) {
|
|
30855
|
+
fs43.symlinkSync(fs43.readlinkSync(targetPath), backupPath);
|
|
30856
|
+
} else if (stat.isDirectory()) {
|
|
30706
30857
|
fs43.cpSync(targetPath, backupPath, {
|
|
30707
30858
|
recursive: true,
|
|
30708
30859
|
// Preserve links as links: following them would copy the kit into the
|
|
@@ -30918,14 +31069,14 @@ async function runInstallCommand(options, logger) {
|
|
|
30918
31069
|
return;
|
|
30919
31070
|
}
|
|
30920
31071
|
if (conflictingFindings.length > 0) {
|
|
31072
|
+
const toolsByPath = /* @__PURE__ */ new Map();
|
|
31073
|
+
for (const finding of conflictingFindings) {
|
|
31074
|
+
const tools2 = toolsByPath.get(finding.path) ?? [];
|
|
31075
|
+
tools2.push(finding.tool);
|
|
31076
|
+
toolsByPath.set(finding.path, tools2);
|
|
31077
|
+
}
|
|
30921
31078
|
if (options.yes) {
|
|
30922
31079
|
const decidedPaths = /* @__PURE__ */ new Set();
|
|
30923
|
-
const toolsByPath = /* @__PURE__ */ new Map();
|
|
30924
|
-
for (const finding of conflictingFindings) {
|
|
30925
|
-
const tools2 = toolsByPath.get(finding.path) ?? [];
|
|
30926
|
-
tools2.push(finding.tool);
|
|
30927
|
-
toolsByPath.set(finding.path, tools2);
|
|
30928
|
-
}
|
|
30929
31080
|
const posture = config.onConflict === "ask" ? "skip" : config.onConflict;
|
|
30930
31081
|
const replaceable = Array.from(toolsByPath.keys()).filter(
|
|
30931
31082
|
(p) => !foreignSkillsSlots.has(p)
|
|
@@ -31004,10 +31155,11 @@ ACCEPTED CONFLICTS (posture: ${posture}; ${replaceable.length} target(s)):`
|
|
|
31004
31155
|
} else {
|
|
31005
31156
|
logger.info(
|
|
31006
31157
|
`
|
|
31007
|
-
Found ${
|
|
31158
|
+
Found ${toolsByPath.size} conflicting target(s). Please choose an action for each:
|
|
31008
31159
|
`
|
|
31009
31160
|
);
|
|
31010
|
-
for (const
|
|
31161
|
+
for (const [conflictPath, claimingTools] of toolsByPath) {
|
|
31162
|
+
const finding = { path: conflictPath, tool: claimingTools.join(", ") };
|
|
31011
31163
|
const choice = await dist_default8({
|
|
31012
31164
|
message: `Conflict: ${finding.path} (${finding.tool})
|
|
31013
31165
|
Choose an action:`,
|
|
@@ -31058,6 +31210,9 @@ SKIPPED CONFLICTS (${skippedTargetPaths.size} target(s) left unchanged):`
|
|
|
31058
31210
|
for (const backup of backupsToAdd) {
|
|
31059
31211
|
manifest.backups.push(backup);
|
|
31060
31212
|
}
|
|
31213
|
+
if (backupsToAdd.length > 0) {
|
|
31214
|
+
writeManifest(scopePath, manifest);
|
|
31215
|
+
}
|
|
31061
31216
|
const mappings = loadMappings(kitInfo.path, config, configDir);
|
|
31062
31217
|
const installLossReport = createLossReport();
|
|
31063
31218
|
let installedCount = 0;
|
|
@@ -32320,7 +32475,7 @@ function commandProbeRows(target, key, command, args) {
|
|
|
32320
32475
|
}
|
|
32321
32476
|
|
|
32322
32477
|
// src/commands/status.ts
|
|
32323
|
-
import
|
|
32478
|
+
import crypto12 from "crypto";
|
|
32324
32479
|
import fs45 from "fs";
|
|
32325
32480
|
import path46 from "path";
|
|
32326
32481
|
function runStatusCommand(options, logger) {
|
|
@@ -33444,7 +33599,7 @@ function containsTangyrSource(value) {
|
|
|
33444
33599
|
return typeof value._tangyr_source === "string" || typeof value._proteus_source === "string" || Object.values(value).some((entry) => containsTangyrSource(entry));
|
|
33445
33600
|
}
|
|
33446
33601
|
function hashString6(value) {
|
|
33447
|
-
return `sha256:${
|
|
33602
|
+
return `sha256:${crypto12.createHash("sha256").update(value).digest("hex")}`;
|
|
33448
33603
|
}
|
|
33449
33604
|
|
|
33450
33605
|
// src/commands/sync.ts
|