@alessandroraffa/tangyr 0.11.0 → 0.12.0
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/README.md +14 -0
- package/dist/index.js +676 -69
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ CLI for the Tangyr discipline — install and manage operating kits for AI codin
|
|
|
4
4
|
|
|
5
5
|
An operating kit is a self-contained package of agents, skills, commands, rules, and templates that implements the Tangyr discipline for a specific AI coding context. The CLI places kit artifacts into the environments consumed by supported tools.
|
|
6
6
|
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
Node.js `>=22`. Running `tangyr` on an older Node.js version prints a clear error to stderr and exits without attempting to run any command.
|
|
10
|
+
|
|
7
11
|
## Install / run
|
|
8
12
|
|
|
9
13
|
```sh
|
|
@@ -49,6 +53,16 @@ tangyr <command>
|
|
|
49
53
|
| `tangyr config` | Display or edit Tangyr configuration |
|
|
50
54
|
| `tangyr validate` | Validate the kit against kitFormat 1 conformity rules |
|
|
51
55
|
|
|
56
|
+
### Authentication
|
|
57
|
+
|
|
58
|
+
| Command | Description |
|
|
59
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
60
|
+
| `tangyr auth login` | Store a client ID/secret credential pair, validating by default |
|
|
61
|
+
| `tangyr auth status` | Show current credential validation state |
|
|
62
|
+
| `tangyr auth logout` | Remove the stored credential |
|
|
63
|
+
|
|
64
|
+
Resolved environment-first (`TANGYR_ACCESS_CLIENT_ID`/`TANGYR_ACCESS_CLIENT_SECRET`), then from the stored file, then interactively. A full quickstart (configuring a remote kit origin and installing against it) lands once remote kit resolution ships.
|
|
65
|
+
|
|
52
66
|
## Global flags
|
|
53
67
|
|
|
54
68
|
| Flag | Description |
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,11 @@ var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? requir
|
|
|
14
14
|
throw Error('Dynamic require of "' + x2 + '" is not supported');
|
|
15
15
|
});
|
|
16
16
|
var __commonJS = (cb, mod) => function __require2() {
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw mod = 0, e;
|
|
21
|
+
}
|
|
18
22
|
};
|
|
19
23
|
var __copyProps = (to, from, except, desc) => {
|
|
20
24
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -14761,6 +14765,60 @@ var dist_default6 = createPrompt((config, done) => {
|
|
|
14761
14765
|
];
|
|
14762
14766
|
});
|
|
14763
14767
|
|
|
14768
|
+
// node_modules/@inquirer/password/dist/index.js
|
|
14769
|
+
var passwordTheme = {
|
|
14770
|
+
style: {
|
|
14771
|
+
maskedText: "[input is masked]"
|
|
14772
|
+
}
|
|
14773
|
+
};
|
|
14774
|
+
var dist_default7 = createPrompt((config, done) => {
|
|
14775
|
+
const { validate = () => true } = config;
|
|
14776
|
+
const theme = makeTheme(passwordTheme, config.theme);
|
|
14777
|
+
const [status, setStatus] = useState("idle");
|
|
14778
|
+
const [errorMsg, setError] = useState();
|
|
14779
|
+
const [value, setValue] = useState("");
|
|
14780
|
+
const prefix = usePrefix({ status, theme });
|
|
14781
|
+
useKeypress(async (key, rl) => {
|
|
14782
|
+
if (status !== "idle") {
|
|
14783
|
+
return;
|
|
14784
|
+
}
|
|
14785
|
+
if (isEnterKey(key)) {
|
|
14786
|
+
const answer = value;
|
|
14787
|
+
setStatus("loading");
|
|
14788
|
+
const isValid = await validate(answer);
|
|
14789
|
+
if (isValid === true) {
|
|
14790
|
+
setValue(answer);
|
|
14791
|
+
setStatus("done");
|
|
14792
|
+
done(answer);
|
|
14793
|
+
} else {
|
|
14794
|
+
rl.write(value);
|
|
14795
|
+
setError(isValid || "You must provide a valid value");
|
|
14796
|
+
setStatus("idle");
|
|
14797
|
+
}
|
|
14798
|
+
} else {
|
|
14799
|
+
setValue(rl.line);
|
|
14800
|
+
setError(void 0);
|
|
14801
|
+
}
|
|
14802
|
+
});
|
|
14803
|
+
const message = theme.style.message(config.message, status);
|
|
14804
|
+
let formattedValue = "";
|
|
14805
|
+
let helpTip;
|
|
14806
|
+
if (config.mask) {
|
|
14807
|
+
const maskChar = typeof config.mask === "string" ? config.mask : "*";
|
|
14808
|
+
formattedValue = maskChar.repeat(value.length);
|
|
14809
|
+
} else if (status !== "done") {
|
|
14810
|
+
helpTip = `${theme.style.help(theme.style.maskedText)}${cursorHide}`;
|
|
14811
|
+
}
|
|
14812
|
+
if (status === "done") {
|
|
14813
|
+
formattedValue = theme.style.answer(formattedValue);
|
|
14814
|
+
}
|
|
14815
|
+
let error = "";
|
|
14816
|
+
if (errorMsg) {
|
|
14817
|
+
error = theme.style.error(errorMsg);
|
|
14818
|
+
}
|
|
14819
|
+
return [[prefix, message, config.mask ? formattedValue : helpTip].join(" "), error];
|
|
14820
|
+
});
|
|
14821
|
+
|
|
14764
14822
|
// node_modules/@inquirer/select/dist/index.js
|
|
14765
14823
|
import { styleText as styleText4 } from "util";
|
|
14766
14824
|
var selectTheme = {
|
|
@@ -14806,7 +14864,7 @@ function normalizeChoices2(choices) {
|
|
|
14806
14864
|
return normalizedChoice;
|
|
14807
14865
|
});
|
|
14808
14866
|
}
|
|
14809
|
-
var
|
|
14867
|
+
var dist_default8 = createPrompt((config, done) => {
|
|
14810
14868
|
const { loop = true, pageSize = 7 } = config;
|
|
14811
14869
|
const theme = makeTheme(selectTheme, config.theme);
|
|
14812
14870
|
const { keybindings } = theme;
|
|
@@ -15093,9 +15151,12 @@ function getPackageRoot(importMetaUrl) {
|
|
|
15093
15151
|
current = parent;
|
|
15094
15152
|
}
|
|
15095
15153
|
}
|
|
15154
|
+
function computeBytesHash(content) {
|
|
15155
|
+
return `sha256:${crypto.createHash("sha256").update(content).digest("hex")}`;
|
|
15156
|
+
}
|
|
15096
15157
|
function computeSourceHash(filePath) {
|
|
15097
15158
|
const content = fs2.readFileSync(filePath);
|
|
15098
|
-
return
|
|
15159
|
+
return computeBytesHash(content);
|
|
15099
15160
|
}
|
|
15100
15161
|
function formatProvenanceMarker(format, source, hash, target) {
|
|
15101
15162
|
if (format === "markdown") {
|
|
@@ -15404,6 +15465,11 @@ var EXIT_ERROR_SEVERITY_LOSS = 3;
|
|
|
15404
15465
|
var EXIT_TARGET_NOT_DETECTED = 4;
|
|
15405
15466
|
var EXIT_CONFIG_MISSING = 10;
|
|
15406
15467
|
var EXIT_USER_INTERRUPT = 130;
|
|
15468
|
+
var EXIT_AUTH_REQUIRED = 5;
|
|
15469
|
+
var EXIT_REMOTE_UNAVAILABLE = 6;
|
|
15470
|
+
var EXIT_INTEGRITY_FAILURE = 7;
|
|
15471
|
+
var EXIT_OFFLINE_CACHE_MISS = 8;
|
|
15472
|
+
var EXIT_INCOMPATIBLE_VERSION = 9;
|
|
15407
15473
|
var ConflictError = class extends Error {
|
|
15408
15474
|
constructor(message) {
|
|
15409
15475
|
super(message);
|
|
@@ -15416,6 +15482,44 @@ var TargetNotDetectedError = class extends Error {
|
|
|
15416
15482
|
this.name = "TargetNotDetectedError";
|
|
15417
15483
|
}
|
|
15418
15484
|
};
|
|
15485
|
+
var AuthenticationError = class extends Error {
|
|
15486
|
+
constructor(message) {
|
|
15487
|
+
super(message);
|
|
15488
|
+
this.name = "AuthenticationError";
|
|
15489
|
+
}
|
|
15490
|
+
};
|
|
15491
|
+
var RemoteUnavailableError = class extends Error {
|
|
15492
|
+
constructor(message) {
|
|
15493
|
+
super(message);
|
|
15494
|
+
this.name = "RemoteUnavailableError";
|
|
15495
|
+
}
|
|
15496
|
+
};
|
|
15497
|
+
var IntegrityError = class extends Error {
|
|
15498
|
+
constructor(message) {
|
|
15499
|
+
super(message);
|
|
15500
|
+
this.name = "IntegrityError";
|
|
15501
|
+
}
|
|
15502
|
+
};
|
|
15503
|
+
var OfflineCacheMissError = class extends Error {
|
|
15504
|
+
constructor(message) {
|
|
15505
|
+
super(message);
|
|
15506
|
+
this.name = "OfflineCacheMissError";
|
|
15507
|
+
}
|
|
15508
|
+
};
|
|
15509
|
+
var IncompatibleVersionError = class extends Error {
|
|
15510
|
+
constructor(message) {
|
|
15511
|
+
super(message);
|
|
15512
|
+
this.name = "IncompatibleVersionError";
|
|
15513
|
+
}
|
|
15514
|
+
};
|
|
15515
|
+
var CredentialsLegacyError = class extends Error {
|
|
15516
|
+
constructor() {
|
|
15517
|
+
super(
|
|
15518
|
+
"Legacy single-key credential file detected. The credential-store layout changed to store a client ID/secret pair (the credential-pair format). Run 'tangyr auth login' to re-authenticate and store the new format."
|
|
15519
|
+
);
|
|
15520
|
+
this.name = "CredentialsLegacyError";
|
|
15521
|
+
}
|
|
15522
|
+
};
|
|
15419
15523
|
function handleFatalError(err, logger) {
|
|
15420
15524
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
15421
15525
|
logger.error(error.message);
|
|
@@ -15428,6 +15532,24 @@ function handleFatalError(err, logger) {
|
|
|
15428
15532
|
if (error instanceof TargetNotDetectedError) {
|
|
15429
15533
|
process.exit(EXIT_TARGET_NOT_DETECTED);
|
|
15430
15534
|
}
|
|
15535
|
+
if (error instanceof AuthenticationError) {
|
|
15536
|
+
process.exit(EXIT_AUTH_REQUIRED);
|
|
15537
|
+
}
|
|
15538
|
+
if (error instanceof CredentialsLegacyError) {
|
|
15539
|
+
process.exit(EXIT_AUTH_REQUIRED);
|
|
15540
|
+
}
|
|
15541
|
+
if (error instanceof RemoteUnavailableError) {
|
|
15542
|
+
process.exit(EXIT_REMOTE_UNAVAILABLE);
|
|
15543
|
+
}
|
|
15544
|
+
if (error instanceof IntegrityError) {
|
|
15545
|
+
process.exit(EXIT_INTEGRITY_FAILURE);
|
|
15546
|
+
}
|
|
15547
|
+
if (error instanceof OfflineCacheMissError) {
|
|
15548
|
+
process.exit(EXIT_OFFLINE_CACHE_MISS);
|
|
15549
|
+
}
|
|
15550
|
+
if (error instanceof IncompatibleVersionError) {
|
|
15551
|
+
process.exit(EXIT_INCOMPATIBLE_VERSION);
|
|
15552
|
+
}
|
|
15431
15553
|
process.exit(EXIT_FAILURE);
|
|
15432
15554
|
}
|
|
15433
15555
|
function exitOnErrorSeverityLoss(report, logger, opts = {}) {
|
|
@@ -15444,6 +15566,28 @@ function exitOnErrorSeverityLoss(report, logger, opts = {}) {
|
|
|
15444
15566
|
}
|
|
15445
15567
|
}
|
|
15446
15568
|
|
|
15569
|
+
// src/core/runtime-guard.ts
|
|
15570
|
+
var MINIMUM_SUPPORTED_NODE_MAJOR = 22;
|
|
15571
|
+
function checkNodeRuntime(nodeVersion) {
|
|
15572
|
+
const match = /^v?(\d+)\./.exec(nodeVersion);
|
|
15573
|
+
const major = match ? Number.parseInt(match[1], 10) : Number.NaN;
|
|
15574
|
+
if (Number.isNaN(major) || major < MINIMUM_SUPPORTED_NODE_MAJOR) {
|
|
15575
|
+
return {
|
|
15576
|
+
supported: false,
|
|
15577
|
+
message: `tangyr requires Node.js >=${MINIMUM_SUPPORTED_NODE_MAJOR}. Detected ${nodeVersion}. Install Node.js ${MINIMUM_SUPPORTED_NODE_MAJOR} or later (e.g. via nvm: "nvm install ${MINIMUM_SUPPORTED_NODE_MAJOR}") and retry.`
|
|
15578
|
+
};
|
|
15579
|
+
}
|
|
15580
|
+
return { supported: true };
|
|
15581
|
+
}
|
|
15582
|
+
function assertSupportedNodeRuntime(nodeVersion = process.version) {
|
|
15583
|
+
const result = checkNodeRuntime(nodeVersion);
|
|
15584
|
+
if (!result.supported) {
|
|
15585
|
+
process.stderr.write(`${result.message}
|
|
15586
|
+
`);
|
|
15587
|
+
process.exit(EXIT_FAILURE);
|
|
15588
|
+
}
|
|
15589
|
+
}
|
|
15590
|
+
|
|
15447
15591
|
// src/commands/assess.ts
|
|
15448
15592
|
import fs11 from "fs";
|
|
15449
15593
|
import path12 from "path";
|
|
@@ -16926,63 +17070,364 @@ function resolveKitPath(config, configDir) {
|
|
|
16926
17070
|
import fs12 from "fs";
|
|
16927
17071
|
import os3 from "os";
|
|
16928
17072
|
import path13 from "path";
|
|
17073
|
+
|
|
17074
|
+
// src/core/transport.ts
|
|
17075
|
+
var KIT_CONTRACT_VERSION = "1";
|
|
17076
|
+
var CONTRACT_VERSION_HEADER = "X-Tangyr-Contract-Version";
|
|
17077
|
+
var ACCESS_CLIENT_ID_HEADER = "CF-Access-Client-Id";
|
|
17078
|
+
var ACCESS_CLIENT_SECRET_HEADER = "CF-Access-Client-Secret";
|
|
17079
|
+
var REDACTED = "[redacted]";
|
|
17080
|
+
function assertHttps(originUrl) {
|
|
17081
|
+
const parsed = new URL(originUrl);
|
|
17082
|
+
if (parsed.protocol !== "https:") {
|
|
17083
|
+
throw new IntegrityError(
|
|
17084
|
+
`KitTransport rejected a non-HTTPS origin "${originUrl}" \u2014 only https:// origins are permitted (REQ-INT-006).`
|
|
17085
|
+
);
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
17088
|
+
async function performRequest(originUrl, requestPath, credentials, logger) {
|
|
17089
|
+
assertHttps(originUrl);
|
|
17090
|
+
const headers = {
|
|
17091
|
+
[ACCESS_CLIENT_ID_HEADER]: credentials.clientId,
|
|
17092
|
+
[ACCESS_CLIENT_SECRET_HEADER]: credentials.clientSecret,
|
|
17093
|
+
[CONTRACT_VERSION_HEADER]: KIT_CONTRACT_VERSION
|
|
17094
|
+
};
|
|
17095
|
+
logger?.verbose(
|
|
17096
|
+
`KitTransport GET ${originUrl}${requestPath} \u2014 headers: ${ACCESS_CLIENT_ID_HEADER}=${REDACTED}, ${ACCESS_CLIENT_SECRET_HEADER}=${REDACTED}, ${CONTRACT_VERSION_HEADER}=${KIT_CONTRACT_VERSION}`
|
|
17097
|
+
);
|
|
17098
|
+
const response = await fetch(`${originUrl}${requestPath}`, {
|
|
17099
|
+
method: "GET",
|
|
17100
|
+
redirect: "manual",
|
|
17101
|
+
headers
|
|
17102
|
+
});
|
|
17103
|
+
if (response.status >= 300 && response.status < 400) {
|
|
17104
|
+
throw new IntegrityError(
|
|
17105
|
+
`KitTransport rejected a redirect response (HTTP ${response.status}) from ${originUrl}${requestPath} \u2014 redirects are never followed; every redirect is rejected fail-closed (REQ-INT-006, resolved reject-all-redirects policy).`
|
|
17106
|
+
);
|
|
17107
|
+
}
|
|
17108
|
+
if (response.status === 426) {
|
|
17109
|
+
throw new IncompatibleVersionError(
|
|
17110
|
+
`The origin rejected this CLI's contract version (HTTP 426) at ${originUrl}${requestPath}. Upgrade tangyr to the latest release and retry.`
|
|
17111
|
+
);
|
|
17112
|
+
}
|
|
17113
|
+
return response;
|
|
17114
|
+
}
|
|
17115
|
+
function createFetchKitTransport(options = {}) {
|
|
17116
|
+
return {
|
|
17117
|
+
async probe(originUrl, credentials) {
|
|
17118
|
+
const response = await performRequest(
|
|
17119
|
+
originUrl,
|
|
17120
|
+
"/v1/probe",
|
|
17121
|
+
credentials,
|
|
17122
|
+
options.logger
|
|
17123
|
+
);
|
|
17124
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
17125
|
+
const metadata = contentType.includes("application/json") ? await response.json() : {};
|
|
17126
|
+
return { status: response.status, metadata };
|
|
17127
|
+
},
|
|
17128
|
+
async fetchObject(originUrl, objectPath, credentials) {
|
|
17129
|
+
const response = await performRequest(
|
|
17130
|
+
originUrl,
|
|
17131
|
+
objectPath,
|
|
17132
|
+
credentials,
|
|
17133
|
+
options.logger
|
|
17134
|
+
);
|
|
17135
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
17136
|
+
return { status: response.status, body };
|
|
17137
|
+
}
|
|
17138
|
+
};
|
|
17139
|
+
}
|
|
17140
|
+
|
|
17141
|
+
// src/commands/auth.ts
|
|
16929
17142
|
var CREDENTIALS_DIR = path13.join(os3.homedir(), ".tangyr");
|
|
16930
17143
|
var CREDENTIALS_PATH = path13.join(CREDENTIALS_DIR, "credentials.json");
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
key = await dist_default6({ message: "Enter your Tangyr license key:" });
|
|
17144
|
+
function isCurrentCredentialRecord(value) {
|
|
17145
|
+
if (typeof value !== "object" || value === null) {
|
|
17146
|
+
return false;
|
|
16935
17147
|
}
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
17148
|
+
const record = value;
|
|
17149
|
+
return record.schema === 2 && typeof record.clientId === "string" && record.clientId.length > 0 && typeof record.clientSecret === "string" && record.clientSecret.length > 0 && (record.validation === "validated" || record.validation === "unvalidated") && typeof record.storedAt === "string";
|
|
17150
|
+
}
|
|
17151
|
+
function readStoredCredentials() {
|
|
17152
|
+
if (!fs12.existsSync(CREDENTIALS_PATH)) {
|
|
17153
|
+
return null;
|
|
16939
17154
|
}
|
|
16940
|
-
fs12.
|
|
16941
|
-
const
|
|
16942
|
-
|
|
16943
|
-
|
|
16944
|
-
}
|
|
16945
|
-
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
`,
|
|
16949
|
-
{ mode: 384 }
|
|
16950
|
-
);
|
|
17155
|
+
const raw = fs12.readFileSync(CREDENTIALS_PATH, "utf8");
|
|
17156
|
+
const parsed = JSON.parse(raw);
|
|
17157
|
+
if (!isCurrentCredentialRecord(parsed)) {
|
|
17158
|
+
throw new CredentialsLegacyError();
|
|
17159
|
+
}
|
|
17160
|
+
return parsed;
|
|
17161
|
+
}
|
|
17162
|
+
function ensureCredentialsDir(logger) {
|
|
16951
17163
|
try {
|
|
16952
|
-
fs12.
|
|
17164
|
+
fs12.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
17165
|
+
} catch (err) {
|
|
17166
|
+
if (err.code !== "EEXIST") {
|
|
17167
|
+
throw err;
|
|
17168
|
+
}
|
|
17169
|
+
}
|
|
17170
|
+
if (fs12.lstatSync(CREDENTIALS_DIR).isSymbolicLink()) {
|
|
17171
|
+
throw new Error(
|
|
17172
|
+
`Refusing to write credentials: "${CREDENTIALS_DIR}" already exists as a symbolic link, not a directory. Remove it and run 'tangyr auth login' again.`
|
|
17173
|
+
);
|
|
17174
|
+
}
|
|
17175
|
+
try {
|
|
17176
|
+
fs12.chmodSync(CREDENTIALS_DIR, 448);
|
|
16953
17177
|
} catch {
|
|
17178
|
+
logger.warn(
|
|
17179
|
+
"Could not set owner-only (0700) permissions on the credentials directory on this platform; at-rest protection is reduced (REQ-AUT-007)."
|
|
17180
|
+
);
|
|
16954
17181
|
}
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
logger
|
|
16958
|
-
|
|
17182
|
+
}
|
|
17183
|
+
function writeStoredCredentials(record, logger) {
|
|
17184
|
+
ensureCredentialsDir(logger);
|
|
17185
|
+
const content = `${JSON.stringify(record, null, 2)}
|
|
17186
|
+
`;
|
|
17187
|
+
const tmpPath = `${CREDENTIALS_PATH}.tmp`;
|
|
17188
|
+
try {
|
|
17189
|
+
fs12.writeFileSync(tmpPath, content, { mode: 384, flag: "wx" });
|
|
17190
|
+
} catch (err) {
|
|
17191
|
+
if (err.code === "EEXIST") {
|
|
17192
|
+
fs12.unlinkSync(tmpPath);
|
|
17193
|
+
fs12.writeFileSync(tmpPath, content, { mode: 384, flag: "wx" });
|
|
17194
|
+
} else {
|
|
17195
|
+
throw err;
|
|
17196
|
+
}
|
|
17197
|
+
}
|
|
17198
|
+
try {
|
|
17199
|
+
fs12.renameSync(tmpPath, CREDENTIALS_PATH);
|
|
17200
|
+
} catch (renameErr) {
|
|
17201
|
+
try {
|
|
17202
|
+
try {
|
|
17203
|
+
const destStat = fs12.lstatSync(CREDENTIALS_PATH);
|
|
17204
|
+
if (destStat.isSymbolicLink()) {
|
|
17205
|
+
fs12.unlinkSync(CREDENTIALS_PATH);
|
|
17206
|
+
}
|
|
17207
|
+
} catch {
|
|
17208
|
+
}
|
|
17209
|
+
fs12.copyFileSync(tmpPath, CREDENTIALS_PATH);
|
|
17210
|
+
try {
|
|
17211
|
+
fs12.chmodSync(CREDENTIALS_PATH, 384);
|
|
17212
|
+
} catch {
|
|
17213
|
+
logger.warn(
|
|
17214
|
+
"Could not set owner-only (0600) permissions on the credentials file on this platform; at-rest protection is reduced (REQ-AUT-007)."
|
|
17215
|
+
);
|
|
17216
|
+
}
|
|
17217
|
+
try {
|
|
17218
|
+
fs12.unlinkSync(tmpPath);
|
|
17219
|
+
} catch {
|
|
17220
|
+
}
|
|
17221
|
+
} catch (copyErr) {
|
|
17222
|
+
try {
|
|
17223
|
+
fs12.unlinkSync(tmpPath);
|
|
17224
|
+
} catch {
|
|
17225
|
+
}
|
|
17226
|
+
throw new Error(
|
|
17227
|
+
`writeStoredCredentials: failed to write ${CREDENTIALS_PATH} \u2014 ${String(renameErr)} / fallback copy failed \u2014 ${String(copyErr)}`
|
|
17228
|
+
);
|
|
17229
|
+
}
|
|
17230
|
+
}
|
|
17231
|
+
}
|
|
17232
|
+
var ADVANCE_EXPIRY_WARNING_DAYS = 30;
|
|
17233
|
+
var DEFINITIVE_DENIAL_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
17234
|
+
async function resolveCredentialForLogin(options, logger) {
|
|
17235
|
+
const envClientId = process.env.TANGYR_ACCESS_CLIENT_ID?.trim();
|
|
17236
|
+
const envClientSecret = process.env.TANGYR_ACCESS_CLIENT_SECRET?.trim();
|
|
17237
|
+
if (envClientId && envClientSecret) {
|
|
17238
|
+
logger.verbose(
|
|
17239
|
+
"Resolved credential from TANGYR_ACCESS_CLIENT_ID / TANGYR_ACCESS_CLIENT_SECRET."
|
|
17240
|
+
);
|
|
17241
|
+
return {
|
|
17242
|
+
pair: { clientId: envClientId, clientSecret: envClientSecret },
|
|
17243
|
+
source: "env"
|
|
17244
|
+
};
|
|
17245
|
+
}
|
|
17246
|
+
if (envClientId || envClientSecret) {
|
|
17247
|
+
throw new AuthenticationError(
|
|
17248
|
+
"Both TANGYR_ACCESS_CLIENT_ID and TANGYR_ACCESS_CLIENT_SECRET must be set together. Set both environment variables, or unset both and run 'tangyr auth login' interactively."
|
|
17249
|
+
);
|
|
17250
|
+
}
|
|
17251
|
+
const stored = readStoredCredentials();
|
|
17252
|
+
if (stored) {
|
|
17253
|
+
logger.verbose("Resolved credential from the stored credential file.");
|
|
17254
|
+
return {
|
|
17255
|
+
pair: { clientId: stored.clientId, clientSecret: stored.clientSecret },
|
|
17256
|
+
source: "stored"
|
|
17257
|
+
};
|
|
17258
|
+
}
|
|
17259
|
+
if (options.nonInteractive) {
|
|
17260
|
+
throw new AuthenticationError(
|
|
17261
|
+
"No credential available under --non-interactive. Set TANGYR_ACCESS_CLIENT_ID and TANGYR_ACCESS_CLIENT_SECRET, or drop --non-interactive and run 'tangyr auth login' again from an interactive terminal."
|
|
17262
|
+
);
|
|
17263
|
+
}
|
|
17264
|
+
const clientId = await dist_default6({
|
|
17265
|
+
message: "Enter your Tangyr access client ID:"
|
|
17266
|
+
});
|
|
17267
|
+
const clientSecret = await dist_default7({
|
|
17268
|
+
message: "Enter your Tangyr access client secret:"
|
|
17269
|
+
});
|
|
17270
|
+
if (clientId.trim().length === 0 || clientSecret.trim().length === 0) {
|
|
17271
|
+
throw new AuthenticationError("No credential provided.");
|
|
17272
|
+
}
|
|
17273
|
+
return {
|
|
17274
|
+
pair: { clientId: clientId.trim(), clientSecret: clientSecret.trim() },
|
|
17275
|
+
source: "interactive"
|
|
17276
|
+
};
|
|
17277
|
+
}
|
|
17278
|
+
function resolveOriginUrl(configPath) {
|
|
17279
|
+
let config;
|
|
17280
|
+
try {
|
|
17281
|
+
({ config } = resolveConfig(configPath));
|
|
17282
|
+
} catch (err) {
|
|
17283
|
+
if (err instanceof ConfigNotFoundError) {
|
|
17284
|
+
return void 0;
|
|
17285
|
+
}
|
|
17286
|
+
throw err;
|
|
17287
|
+
}
|
|
17288
|
+
const source = config.source;
|
|
17289
|
+
return typeof source.url === "string" && source.url.length > 0 ? source.url : void 0;
|
|
17290
|
+
}
|
|
17291
|
+
async function validateAgainstOrigin(originUrl, pair, transport, logger) {
|
|
17292
|
+
if (!originUrl) {
|
|
17293
|
+
logger.verbose(
|
|
17294
|
+
"No remote origin configured (source.url) \u2014 treating as unreachable for validation purposes."
|
|
17295
|
+
);
|
|
17296
|
+
return { outcome: "unreachable" };
|
|
17297
|
+
}
|
|
17298
|
+
try {
|
|
17299
|
+
const result = await transport.probe(originUrl, pair);
|
|
17300
|
+
if (DEFINITIVE_DENIAL_STATUSES.has(result.status)) {
|
|
17301
|
+
return { outcome: "denied" };
|
|
17302
|
+
}
|
|
17303
|
+
if (result.status >= 200 && result.status < 300) {
|
|
17304
|
+
return { outcome: "success", metadata: result.metadata };
|
|
17305
|
+
}
|
|
17306
|
+
return { outcome: "unreachable" };
|
|
17307
|
+
} catch (err) {
|
|
17308
|
+
if (err instanceof IntegrityError || err instanceof IncompatibleVersionError) {
|
|
17309
|
+
throw err;
|
|
17310
|
+
}
|
|
17311
|
+
logger.verbose(
|
|
17312
|
+
`Origin probe failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17313
|
+
);
|
|
17314
|
+
return { outcome: "unreachable" };
|
|
17315
|
+
}
|
|
17316
|
+
}
|
|
17317
|
+
async function runAuthLoginCommand(options, logger, transport = createFetchKitTransport({ logger })) {
|
|
17318
|
+
const resolved = await resolveCredentialForLogin(options, logger);
|
|
17319
|
+
if (options.skipValidation) {
|
|
17320
|
+
writeStoredCredentials(
|
|
17321
|
+
{
|
|
17322
|
+
schema: 2,
|
|
17323
|
+
clientId: resolved.pair.clientId,
|
|
17324
|
+
clientSecret: resolved.pair.clientSecret,
|
|
17325
|
+
validation: "unvalidated",
|
|
17326
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17327
|
+
},
|
|
17328
|
+
logger
|
|
17329
|
+
);
|
|
17330
|
+
logger.info(
|
|
17331
|
+
"Credential stored (validation skipped via --skip-validation)."
|
|
17332
|
+
);
|
|
17333
|
+
logger.warn(
|
|
17334
|
+
"Validation state: stored, unvalidated. It will be revalidated on the next command that reaches the configured origin."
|
|
17335
|
+
);
|
|
17336
|
+
return;
|
|
17337
|
+
}
|
|
17338
|
+
const originUrl = resolveOriginUrl(options.config);
|
|
17339
|
+
const outcome = await validateAgainstOrigin(
|
|
17340
|
+
originUrl,
|
|
17341
|
+
resolved.pair,
|
|
17342
|
+
transport,
|
|
17343
|
+
logger
|
|
17344
|
+
);
|
|
17345
|
+
if (outcome.outcome === "denied") {
|
|
17346
|
+
throw new AuthenticationError(
|
|
17347
|
+
"The configured origin denied this credential. The credential was not stored. Obtain a valid client ID/secret pair and run 'tangyr auth login' again."
|
|
17348
|
+
);
|
|
17349
|
+
}
|
|
17350
|
+
writeStoredCredentials(
|
|
17351
|
+
{
|
|
17352
|
+
schema: 2,
|
|
17353
|
+
clientId: resolved.pair.clientId,
|
|
17354
|
+
clientSecret: resolved.pair.clientSecret,
|
|
17355
|
+
validation: outcome.outcome === "success" ? "validated" : "unvalidated",
|
|
17356
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17357
|
+
},
|
|
17358
|
+
logger
|
|
16959
17359
|
);
|
|
17360
|
+
if (outcome.outcome === "success") {
|
|
17361
|
+
logger.info("Credential validated and stored (stored, validated).");
|
|
17362
|
+
} else {
|
|
17363
|
+
logger.warn(
|
|
17364
|
+
"Could not reach the configured origin to validate this credential. Credential stored as stored, unvalidated \u2014 it will be revalidated on the next command that reaches the origin."
|
|
17365
|
+
);
|
|
17366
|
+
}
|
|
16960
17367
|
}
|
|
16961
|
-
function runAuthStatusCommand(
|
|
16962
|
-
|
|
17368
|
+
async function runAuthStatusCommand(options, logger, transport = createFetchKitTransport({ logger })) {
|
|
17369
|
+
const stored = readStoredCredentials();
|
|
17370
|
+
if (!stored) {
|
|
16963
17371
|
logger.info(
|
|
16964
|
-
"Not authenticated. Run 'tangyr auth login' to configure a
|
|
17372
|
+
"Not authenticated. Run 'tangyr auth login' to configure a credential."
|
|
16965
17373
|
);
|
|
16966
17374
|
return;
|
|
16967
17375
|
}
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
logger
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
17376
|
+
const originUrl = resolveOriginUrl(options.config);
|
|
17377
|
+
const outcome = await validateAgainstOrigin(
|
|
17378
|
+
originUrl,
|
|
17379
|
+
{ clientId: stored.clientId, clientSecret: stored.clientSecret },
|
|
17380
|
+
transport,
|
|
17381
|
+
logger
|
|
17382
|
+
);
|
|
17383
|
+
if (outcome.outcome === "denied") {
|
|
17384
|
+
throw new AuthenticationError(
|
|
17385
|
+
"The configured origin denied the stored credential \u2014 it has likely been revoked. Run 'tangyr auth login' with a new credential."
|
|
17386
|
+
);
|
|
17387
|
+
}
|
|
17388
|
+
if (outcome.outcome === "unreachable") {
|
|
17389
|
+
logger.info(`Credential status: stored, ${stored.validation}.`);
|
|
17390
|
+
logger.info(
|
|
17391
|
+
"The configured origin was not reached \u2014 label and expiry are not reported."
|
|
17392
|
+
);
|
|
17393
|
+
return;
|
|
17394
|
+
}
|
|
17395
|
+
if (stored.validation === "unvalidated") {
|
|
17396
|
+
writeStoredCredentials(
|
|
17397
|
+
{
|
|
17398
|
+
...stored,
|
|
17399
|
+
validation: "validated",
|
|
17400
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17401
|
+
},
|
|
17402
|
+
logger
|
|
17403
|
+
);
|
|
17404
|
+
}
|
|
17405
|
+
logger.info("Credential status: stored, validated.");
|
|
17406
|
+
const label = outcome.metadata.label;
|
|
17407
|
+
if (typeof label === "string" && label.length > 0) {
|
|
17408
|
+
logger.info(`Label: ${label}`);
|
|
17409
|
+
}
|
|
17410
|
+
const expiresAtRaw = outcome.metadata.expiresAt;
|
|
17411
|
+
if (typeof expiresAtRaw === "string") {
|
|
17412
|
+
const expiresAt = new Date(expiresAtRaw);
|
|
17413
|
+
if (!Number.isNaN(expiresAt.getTime())) {
|
|
17414
|
+
logger.info(`Expires: ${expiresAt.toISOString()}`);
|
|
17415
|
+
const daysUntilExpiry = (expiresAt.getTime() - Date.now()) / (24 * 60 * 60 * 1e3);
|
|
17416
|
+
if (daysUntilExpiry <= ADVANCE_EXPIRY_WARNING_DAYS) {
|
|
17417
|
+
logger.warn(
|
|
17418
|
+
`Credential expires in ${Math.max(0, Math.ceil(daysUntilExpiry))} day(s) (${expiresAt.toISOString()}) \u2014 renew it soon.`
|
|
17419
|
+
);
|
|
17420
|
+
}
|
|
17421
|
+
}
|
|
16977
17422
|
}
|
|
16978
17423
|
}
|
|
16979
17424
|
function runAuthLogoutCommand(_options, logger) {
|
|
16980
17425
|
if (!fs12.existsSync(CREDENTIALS_PATH)) {
|
|
16981
|
-
logger.info("No
|
|
17426
|
+
logger.info("No credential found. Nothing to remove.");
|
|
16982
17427
|
return;
|
|
16983
17428
|
}
|
|
16984
17429
|
fs12.unlinkSync(CREDENTIALS_PATH);
|
|
16985
|
-
logger.info("
|
|
17430
|
+
logger.info("Credential removed.");
|
|
16986
17431
|
}
|
|
16987
17432
|
|
|
16988
17433
|
// src/commands/cleanup.ts
|
|
@@ -20139,7 +20584,7 @@ import path17 from "path";
|
|
|
20139
20584
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
20140
20585
|
import fs16 from "fs";
|
|
20141
20586
|
import path16 from "path";
|
|
20142
|
-
function
|
|
20587
|
+
function resolveKitComponentNames(sourceRoot, category) {
|
|
20143
20588
|
const manifestPath = path16.resolve(sourceRoot, "tangyr-kit.yaml");
|
|
20144
20589
|
if (!fs16.existsSync(manifestPath)) {
|
|
20145
20590
|
return void 0;
|
|
@@ -20149,10 +20594,10 @@ function resolveKitHookNames(sourceRoot) {
|
|
|
20149
20594
|
const doc = (0, import_yaml5.parse)(raw);
|
|
20150
20595
|
if (doc && typeof doc === "object" && !Array.isArray(doc) && "components" in doc) {
|
|
20151
20596
|
const components = doc.components;
|
|
20152
|
-
if (components && typeof components === "object" && !Array.isArray(components) &&
|
|
20153
|
-
const
|
|
20154
|
-
if (Array.isArray(
|
|
20155
|
-
return
|
|
20597
|
+
if (components && typeof components === "object" && !Array.isArray(components) && category in components) {
|
|
20598
|
+
const names = components[category];
|
|
20599
|
+
if (Array.isArray(names) && names.every((n7) => typeof n7 === "string")) {
|
|
20600
|
+
return names;
|
|
20156
20601
|
}
|
|
20157
20602
|
}
|
|
20158
20603
|
}
|
|
@@ -20160,6 +20605,12 @@ function resolveKitHookNames(sourceRoot) {
|
|
|
20160
20605
|
}
|
|
20161
20606
|
return void 0;
|
|
20162
20607
|
}
|
|
20608
|
+
function resolveKitHookNames(sourceRoot) {
|
|
20609
|
+
return resolveKitComponentNames(sourceRoot, "hooks");
|
|
20610
|
+
}
|
|
20611
|
+
function resolveKitSkillNames(sourceRoot) {
|
|
20612
|
+
return resolveKitComponentNames(sourceRoot, "skills");
|
|
20613
|
+
}
|
|
20163
20614
|
function resolveHooksSourceFile(sourceRoot, config) {
|
|
20164
20615
|
return path16.resolve(sourceRoot, config.source.hooks, "hooks.json");
|
|
20165
20616
|
}
|
|
@@ -21692,14 +22143,14 @@ ${JSON.stringify(config, null, 2)}`);
|
|
|
21692
22143
|
checked: enabled
|
|
21693
22144
|
}))
|
|
21694
22145
|
});
|
|
21695
|
-
const selectedConflict = await
|
|
22146
|
+
const selectedConflict = await dist_default8({
|
|
21696
22147
|
message: "Conflict policy",
|
|
21697
22148
|
choices: ["ask", "skip", "backup", "overwrite"].map((value) => ({
|
|
21698
22149
|
name: value,
|
|
21699
22150
|
value
|
|
21700
22151
|
}))
|
|
21701
22152
|
});
|
|
21702
|
-
const selectedVerbosity = await
|
|
22153
|
+
const selectedVerbosity = await dist_default8({
|
|
21703
22154
|
message: "Verbosity",
|
|
21704
22155
|
choices: ["quiet", "normal", "verbose"].map((value) => ({
|
|
21705
22156
|
name: value,
|
|
@@ -21771,9 +22222,17 @@ var bundledCopilotManifestPathsByPlatform = {
|
|
|
21771
22222
|
};
|
|
21772
22223
|
function detectTargets(logger) {
|
|
21773
22224
|
const result = {
|
|
21774
|
-
"claude-code": commandExists(
|
|
22225
|
+
"claude-code": commandExists(
|
|
22226
|
+
process.platform === "win32" ? "where.exe" : "which",
|
|
22227
|
+
["claude"],
|
|
22228
|
+
logger
|
|
22229
|
+
),
|
|
21775
22230
|
copilot: detectsCopilot(logger),
|
|
21776
|
-
codex: commandExists(
|
|
22231
|
+
codex: commandExists(
|
|
22232
|
+
process.platform === "win32" ? "where.exe" : "which",
|
|
22233
|
+
["codex"],
|
|
22234
|
+
logger
|
|
22235
|
+
)
|
|
21777
22236
|
};
|
|
21778
22237
|
return result;
|
|
21779
22238
|
}
|
|
@@ -21803,9 +22262,16 @@ function detectCopilot(logger, dependencies = {}) {
|
|
|
21803
22262
|
...bundledCopilotEditorCommandsByPlatform[platform] ?? []
|
|
21804
22263
|
];
|
|
21805
22264
|
for (const command of commandCandidates) {
|
|
21806
|
-
const result = spawnSyncImpl(command, [
|
|
22265
|
+
const result = spawnSyncImpl(command, [
|
|
22266
|
+
"--list-extensions",
|
|
22267
|
+
"--show-versions"
|
|
22268
|
+
]);
|
|
21807
22269
|
if (result.error) {
|
|
21808
|
-
logDetectionError(
|
|
22270
|
+
logDetectionError(
|
|
22271
|
+
`${command} --list-extensions --show-versions`,
|
|
22272
|
+
result.error,
|
|
22273
|
+
logger
|
|
22274
|
+
);
|
|
21809
22275
|
continue;
|
|
21810
22276
|
}
|
|
21811
22277
|
if (result.status !== 0) {
|
|
@@ -21886,10 +22352,14 @@ function commandExists(command, args, logger) {
|
|
|
21886
22352
|
}
|
|
21887
22353
|
function logDetectionError(attemptedCommand, error, logger) {
|
|
21888
22354
|
if (error.code === "ENOENT") {
|
|
21889
|
-
logger?.verbose(
|
|
22355
|
+
logger?.verbose(
|
|
22356
|
+
`${attemptedCommand}: executable not found while detecting target`
|
|
22357
|
+
);
|
|
21890
22358
|
return;
|
|
21891
22359
|
}
|
|
21892
|
-
logger?.verbose(
|
|
22360
|
+
logger?.verbose(
|
|
22361
|
+
`${attemptedCommand} failed: ${error.message ?? "unknown spawn error"}`
|
|
22362
|
+
);
|
|
21893
22363
|
}
|
|
21894
22364
|
|
|
21895
22365
|
// src/commands/doctor.ts
|
|
@@ -22204,7 +22674,7 @@ async function runInitCommand(_options, logger) {
|
|
|
22204
22674
|
checked: detected
|
|
22205
22675
|
}))
|
|
22206
22676
|
});
|
|
22207
|
-
const onConflict = await
|
|
22677
|
+
const onConflict = await dist_default8({
|
|
22208
22678
|
message: "Conflict policy",
|
|
22209
22679
|
choices: ["ask", "skip", "backup", "overwrite"].map((value) => ({
|
|
22210
22680
|
name: value,
|
|
@@ -26201,7 +26671,7 @@ async function runInstallCommand(options, logger) {
|
|
|
26201
26671
|
);
|
|
26202
26672
|
process.exit(1);
|
|
26203
26673
|
} else {
|
|
26204
|
-
action = await
|
|
26674
|
+
action = await dist_default8({
|
|
26205
26675
|
message: "Existing installation found. Choose an action:",
|
|
26206
26676
|
choices: [
|
|
26207
26677
|
{
|
|
@@ -26330,7 +26800,7 @@ Found ${conflictingFindings.length} conflicting target(s). Please choose an acti
|
|
|
26330
26800
|
`
|
|
26331
26801
|
);
|
|
26332
26802
|
for (const finding of conflictingFindings) {
|
|
26333
|
-
const choice = await
|
|
26803
|
+
const choice = await dist_default8({
|
|
26334
26804
|
message: `Conflict: ${finding.path} (${finding.tool})
|
|
26335
26805
|
Choose an action:`,
|
|
26336
26806
|
choices: [
|
|
@@ -28419,12 +28889,6 @@ function buildDirectComponents(profile) {
|
|
|
28419
28889
|
destination: profile.agents,
|
|
28420
28890
|
type: "dir"
|
|
28421
28891
|
},
|
|
28422
|
-
{
|
|
28423
|
-
component: "skills",
|
|
28424
|
-
sourceKey: "skills",
|
|
28425
|
-
destination: profile.skills,
|
|
28426
|
-
type: "dir"
|
|
28427
|
-
},
|
|
28428
28892
|
{
|
|
28429
28893
|
component: "commands",
|
|
28430
28894
|
sourceKey: "commands",
|
|
@@ -28490,6 +28954,19 @@ function syncClaudeCode(config, sourceRoot, options, logger, mappings, manifest,
|
|
|
28490
28954
|
)
|
|
28491
28955
|
);
|
|
28492
28956
|
}
|
|
28957
|
+
if (!shouldSkipComponent6(options, "skills")) {
|
|
28958
|
+
outcomes.push(
|
|
28959
|
+
...syncClaudeCodeSkills(
|
|
28960
|
+
config,
|
|
28961
|
+
sourceRoot,
|
|
28962
|
+
options,
|
|
28963
|
+
logger,
|
|
28964
|
+
profile.skills,
|
|
28965
|
+
manifest,
|
|
28966
|
+
scopePath
|
|
28967
|
+
)
|
|
28968
|
+
);
|
|
28969
|
+
}
|
|
28493
28970
|
if (!shouldSkipComponent6(options, "instructions") && manifest && scopePath) {
|
|
28494
28971
|
const shimPath = profile.instructions_shim;
|
|
28495
28972
|
const agentsMdPath = path36.resolve(sourceRoot, config.source.instructions);
|
|
@@ -28662,6 +29139,128 @@ function syncDirectComponent(config, sourceRoot, options, logger, directComponen
|
|
|
28662
29139
|
path: destination
|
|
28663
29140
|
};
|
|
28664
29141
|
}
|
|
29142
|
+
function syncClaudeCodeSkills(config, sourceRoot, options, logger, destination, manifest, scopePath) {
|
|
29143
|
+
const source = path36.resolve(sourceRoot, config.source.skills);
|
|
29144
|
+
if (!fs34.existsSync(source)) {
|
|
29145
|
+
return [skippedMissingSource6("skills", source)];
|
|
29146
|
+
}
|
|
29147
|
+
const declaredNames = resolveKitSkillNames(sourceRoot);
|
|
29148
|
+
const isLegacyWholesaleSymlink = isSymlinkPath(destination);
|
|
29149
|
+
if (options.dryRun) {
|
|
29150
|
+
const outcomes2 = [];
|
|
29151
|
+
if (isLegacyWholesaleSymlink) {
|
|
29152
|
+
for (const name of foreignSkillNames(source, declaredNames)) {
|
|
29153
|
+
logger.info(
|
|
29154
|
+
`[dry-run] skills: migrate foreign skill ${path36.join(source, name)} -> ${path36.join(destination, name)}`
|
|
29155
|
+
);
|
|
29156
|
+
}
|
|
29157
|
+
logger.info(
|
|
29158
|
+
`[dry-run] skills: replace wholesale symlink with real directory ${destination}`
|
|
29159
|
+
);
|
|
29160
|
+
}
|
|
29161
|
+
for (const name of declaredNames ?? listSkillDirNames(source)) {
|
|
29162
|
+
const skillDestination = path36.join(destination, name);
|
|
29163
|
+
logger.info(
|
|
29164
|
+
`[dry-run] skills: symlink ${path36.join(source, name)} -> ${skillDestination}`
|
|
29165
|
+
);
|
|
29166
|
+
outcomes2.push({
|
|
29167
|
+
component: "skills",
|
|
29168
|
+
status: "dry-run",
|
|
29169
|
+
path: skillDestination
|
|
29170
|
+
});
|
|
29171
|
+
}
|
|
29172
|
+
return outcomes2;
|
|
29173
|
+
}
|
|
29174
|
+
if (isLegacyWholesaleSymlink) {
|
|
29175
|
+
migrateWholesaleSkillsSymlink(source, destination, declaredNames, logger);
|
|
29176
|
+
}
|
|
29177
|
+
ensureDir(destination);
|
|
29178
|
+
const outcomes = [];
|
|
29179
|
+
for (const name of declaredNames ?? listSkillDirNames(source)) {
|
|
29180
|
+
outcomes.push(
|
|
29181
|
+
syncSingleSkill(
|
|
29182
|
+
config,
|
|
29183
|
+
sourceRoot,
|
|
29184
|
+
options,
|
|
29185
|
+
logger,
|
|
29186
|
+
source,
|
|
29187
|
+
destination,
|
|
29188
|
+
name,
|
|
29189
|
+
manifest,
|
|
29190
|
+
scopePath
|
|
29191
|
+
)
|
|
29192
|
+
);
|
|
29193
|
+
}
|
|
29194
|
+
return outcomes;
|
|
29195
|
+
}
|
|
29196
|
+
function syncSingleSkill(config, sourceRoot, options, logger, source, destination, name, manifest, scopePath) {
|
|
29197
|
+
const skillSource = path36.join(source, name);
|
|
29198
|
+
const skillDestination = path36.join(destination, name);
|
|
29199
|
+
if (!fs34.existsSync(skillSource)) {
|
|
29200
|
+
return skippedMissingSource6("skills", skillSource);
|
|
29201
|
+
}
|
|
29202
|
+
const artifactClass = classifyArtifact(skillDestination, config, sourceRoot);
|
|
29203
|
+
if (artifactClass === "managed-symlink" && isSymlinkTo6(skillDestination, skillSource)) {
|
|
29204
|
+
return {
|
|
29205
|
+
component: "skills",
|
|
29206
|
+
status: "skipped-current",
|
|
29207
|
+
path: skillDestination
|
|
29208
|
+
};
|
|
29209
|
+
}
|
|
29210
|
+
if (artifactClass === "managed-stale" && isOurStaleSymlink(skillDestination, /* @__PURE__ */ new Set([skillDestination]), config)) {
|
|
29211
|
+
fs34.unlinkSync(skillDestination);
|
|
29212
|
+
} else if (artifactClass === "unmanaged-conflict" && !handleConflict6(
|
|
29213
|
+
skillDestination,
|
|
29214
|
+
config,
|
|
29215
|
+
options,
|
|
29216
|
+
logger,
|
|
29217
|
+
manifest,
|
|
29218
|
+
scopePath
|
|
29219
|
+
)) {
|
|
29220
|
+
return { component: "skills", status: "conflict", path: skillDestination };
|
|
29221
|
+
} else if (artifactClass === "managed-symlink") {
|
|
29222
|
+
fs34.unlinkSync(skillDestination);
|
|
29223
|
+
}
|
|
29224
|
+
createRelativeSymlink(skillSource, skillDestination, "dir");
|
|
29225
|
+
return { component: "skills", status: "success", path: skillDestination };
|
|
29226
|
+
}
|
|
29227
|
+
function migrateWholesaleSkillsSymlink(source, destination, declaredNames, logger) {
|
|
29228
|
+
const foreignNames = foreignSkillNames(source, declaredNames);
|
|
29229
|
+
fs34.unlinkSync(destination);
|
|
29230
|
+
ensureDir(destination);
|
|
29231
|
+
for (const name of foreignNames) {
|
|
29232
|
+
const from = path36.join(source, name);
|
|
29233
|
+
const to = path36.join(destination, name);
|
|
29234
|
+
if (fs34.existsSync(to)) {
|
|
29235
|
+
logger.warn(
|
|
29236
|
+
`skills migration: ${to} already exists \u2014 skipping move of ${from} to avoid overwrite`
|
|
29237
|
+
);
|
|
29238
|
+
continue;
|
|
29239
|
+
}
|
|
29240
|
+
fs34.renameSync(from, to);
|
|
29241
|
+
logger.info(`skills migration: moved ${from} -> ${to}`);
|
|
29242
|
+
}
|
|
29243
|
+
}
|
|
29244
|
+
function foreignSkillNames(source, declaredNames) {
|
|
29245
|
+
if (!declaredNames) {
|
|
29246
|
+
return [];
|
|
29247
|
+
}
|
|
29248
|
+
const declaredSet = new Set(declaredNames);
|
|
29249
|
+
return listSkillDirNames(source).filter((name) => !declaredSet.has(name));
|
|
29250
|
+
}
|
|
29251
|
+
function listSkillDirNames(dir) {
|
|
29252
|
+
if (!fs34.existsSync(dir)) {
|
|
29253
|
+
return [];
|
|
29254
|
+
}
|
|
29255
|
+
return fs34.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
29256
|
+
}
|
|
29257
|
+
function isSymlinkPath(filePath) {
|
|
29258
|
+
try {
|
|
29259
|
+
return fs34.lstatSync(filePath).isSymbolicLink();
|
|
29260
|
+
} catch {
|
|
29261
|
+
return false;
|
|
29262
|
+
}
|
|
29263
|
+
}
|
|
28665
29264
|
function syncHookScriptsBridge(config, sourceRoot, options, logger, destination, manifest, scopePath) {
|
|
28666
29265
|
const source = path36.resolve(sourceRoot, config.source.hooks);
|
|
28667
29266
|
if (!fs34.existsSync(source)) {
|
|
@@ -29946,6 +30545,7 @@ var commandNames = [
|
|
|
29946
30545
|
"probe"
|
|
29947
30546
|
];
|
|
29948
30547
|
async function main() {
|
|
30548
|
+
assertSupportedNodeRuntime();
|
|
29949
30549
|
process.on("SIGINT", () => {
|
|
29950
30550
|
process.exit(EXIT_USER_INTERRUPT);
|
|
29951
30551
|
});
|
|
@@ -29976,6 +30576,9 @@ function buildProgram() {
|
|
|
29976
30576
|
).option(
|
|
29977
30577
|
"-y, --yes",
|
|
29978
30578
|
"Skip interactive confirmation and apply non-interactive conflict handling"
|
|
30579
|
+
).option(
|
|
30580
|
+
"--non-interactive",
|
|
30581
|
+
"Never prompt; fail closed when a required input is missing (auth login)"
|
|
29979
30582
|
).option("-v, --verbose", "Emit detailed output").option("-q, --quiet", "Emit errors only").option("--no-color", "Disable colorized output").option(
|
|
29980
30583
|
"--claude-config-dir <path>",
|
|
29981
30584
|
"Claude Code global root directory (repeatable; overrides claudeCodeGlobalPaths in config)",
|
|
@@ -30002,19 +30605,23 @@ function buildProgram() {
|
|
|
30002
30605
|
);
|
|
30003
30606
|
}
|
|
30004
30607
|
);
|
|
30005
|
-
const authCmd = program2.command("auth").description("
|
|
30006
|
-
authCmd.command("login").description("Authenticate with a
|
|
30608
|
+
const authCmd = program2.command("auth").description("Credential management");
|
|
30609
|
+
authCmd.command("login").description("Authenticate with a client ID/secret credential pair").option(
|
|
30610
|
+
"--skip-validation",
|
|
30611
|
+
"Store the credential without contacting the configured origin"
|
|
30612
|
+
).action(async (options) => {
|
|
30007
30613
|
const context = commandContext(program2);
|
|
30614
|
+
const nonInteractive = context.options.nonInteractive === true || !process.stdin.isTTY;
|
|
30008
30615
|
await runAuthLoginCommand(
|
|
30009
|
-
{ ...context.options, ...options },
|
|
30616
|
+
{ ...context.options, ...options, nonInteractive },
|
|
30010
30617
|
context.logger
|
|
30011
30618
|
);
|
|
30012
30619
|
});
|
|
30013
|
-
authCmd.command("status").description("Show current authentication status").action(() => {
|
|
30620
|
+
authCmd.command("status").description("Show current authentication status").action(async () => {
|
|
30014
30621
|
const context = commandContext(program2);
|
|
30015
|
-
runAuthStatusCommand({ ...context.options }, context.logger);
|
|
30622
|
+
await runAuthStatusCommand({ ...context.options }, context.logger);
|
|
30016
30623
|
});
|
|
30017
|
-
authCmd.command("logout").description("Remove stored
|
|
30624
|
+
authCmd.command("logout").description("Remove stored credential").action(() => {
|
|
30018
30625
|
const context = commandContext(program2);
|
|
30019
30626
|
runAuthLogoutCommand({ ...context.options }, context.logger);
|
|
30020
30627
|
});
|
|
@@ -30103,7 +30710,7 @@ function buildProgram() {
|
|
|
30103
30710
|
return program2;
|
|
30104
30711
|
}
|
|
30105
30712
|
async function runInteractiveMenu(program2, logger) {
|
|
30106
|
-
const choice = await
|
|
30713
|
+
const choice = await dist_default8({
|
|
30107
30714
|
message: "Select a Tangyr command",
|
|
30108
30715
|
choices: [
|
|
30109
30716
|
{ name: "Assess", value: "assess" },
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alessandroraffa/tangyr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
6
9
|
"type": "module",
|
|
7
10
|
"bin": {
|
|
8
11
|
"tangyr": "./dist/index.js"
|
|
@@ -33,9 +36,13 @@
|
|
|
33
36
|
"bugs": {
|
|
34
37
|
"url": "https://github.com/alessandroraffa/tangyr-cli/issues"
|
|
35
38
|
},
|
|
39
|
+
"overrides": {
|
|
40
|
+
"esbuild": "^0.28.1"
|
|
41
|
+
},
|
|
36
42
|
"devDependencies": {
|
|
37
43
|
"@iarna/toml": "^2.2.5",
|
|
38
44
|
"@inquirer/prompts": "^8.4.1",
|
|
45
|
+
"@types/node": "^22.20.1",
|
|
39
46
|
"chalk": "^5.6.2",
|
|
40
47
|
"commander": "^14.0.3",
|
|
41
48
|
"execa": "^9.0.0",
|
|
@@ -46,6 +53,5 @@
|
|
|
46
53
|
"typescript": "^5.0.0",
|
|
47
54
|
"vitest": "^3.0.0",
|
|
48
55
|
"yaml": "^2.8.3"
|
|
49
|
-
}
|
|
50
|
-
"dependencies": {}
|
|
56
|
+
}
|
|
51
57
|
}
|