@alphafox/cli 0.1.4 → 0.2.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/dist/auth/browser-login.d.ts +30 -0
- package/dist/auth/browser-login.js +193 -0
- package/dist/auth/loopback-callback.d.ts +32 -0
- package/dist/auth/loopback-callback.js +175 -0
- package/dist/auth/open-browser.d.ts +15 -0
- package/dist/auth/open-browser.js +54 -0
- package/dist/auth/refresh.d.ts +27 -2
- package/dist/auth/refresh.js +52 -15
- package/dist/catalog/allowlist.d.ts +20 -4
- package/dist/catalog/allowlist.js +126 -25
- package/dist/catalog/command-tree.d.ts +34 -0
- package/dist/catalog/command-tree.js +117 -0
- package/dist/catalog/compatibility.d.ts +23 -0
- package/dist/catalog/compatibility.js +58 -0
- package/dist/catalog/generated/registry.json +6137 -0
- package/dist/catalog/generated/schemas.json +31036 -0
- package/dist/catalog/operations.d.ts +76 -3
- package/dist/catalog/operations.js +87 -213
- package/dist/commands/run.js +171 -153
- package/dist/config/profiles.js +3 -3
- package/dist/envelope.d.ts +3 -0
- package/dist/envelope.js +43 -3
- package/dist/http/client.js +35 -19
- package/dist/index.d.ts +12 -5
- package/dist/index.js +32 -1
- package/dist/keychain/linux-secret-service.d.ts +11 -0
- package/dist/keychain/linux-secret-service.js +93 -0
- package/dist/keychain/store.d.ts +20 -1
- package/dist/keychain/store.js +94 -6
- package/dist/keychain/windows-credential.d.ts +13 -0
- package/dist/keychain/windows-credential.js +176 -0
- package/dist/safety/confirmation.d.ts +10 -3
- package/dist/safety/confirmation.js +27 -4
- package/dist/version.d.ts +2 -2
- package/dist/version.js +3 -2
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +156 -0
- package/docs/agents/triage-labels.md +18 -0
- package/docs/e2e-staging.md +76 -6
- package/docs/release-supply-chain.md +99 -26
- package/package.json +4 -2
- package/skills/account/SKILL.md +8 -6
- package/skills/admin/SKILL.md +9 -4
- package/skills/alphafox-shared/SKILL.md +23 -14
- package/skills/auth/SKILL.md +21 -6
- package/skills/exchange/SKILL.md +10 -3
- package/skills/market/SKILL.md +9 -4
- package/skills/notification/SKILL.md +8 -3
- package/skills/strategy/SKILL.md +15 -9
- package/skills/trading/SKILL.md +15 -5
package/dist/envelope.js
CHANGED
|
@@ -4,10 +4,12 @@ exports.newRequestId = newRequestId;
|
|
|
4
4
|
exports.successEnvelope = successEnvelope;
|
|
5
5
|
exports.errorEnvelope = errorEnvelope;
|
|
6
6
|
exports.writeSuccess = writeSuccess;
|
|
7
|
+
exports.applyJqFilter = applyJqFilter;
|
|
7
8
|
exports.writeError = writeError;
|
|
8
9
|
exports.mapErrorToExitCode = mapErrorToExitCode;
|
|
9
10
|
exports.parseJsonEnvelope = parseJsonEnvelope;
|
|
10
11
|
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
13
|
function newRequestId() {
|
|
12
14
|
return (0, node_crypto_1.randomUUID)();
|
|
13
15
|
}
|
|
@@ -28,11 +30,49 @@ function errorEnvelope(error, requestId) {
|
|
|
28
30
|
}
|
|
29
31
|
function writeSuccess(data, options = {}) {
|
|
30
32
|
const envelope = successEnvelope(data, options.meta, options.requestId);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
const rendered = options.format === "text" && data && typeof data === "object"
|
|
34
|
+
? `${JSON.stringify(envelope, null, 2)}\n`
|
|
35
|
+
: `${JSON.stringify(envelope)}\n`;
|
|
36
|
+
if (options.jq?.trim()) {
|
|
37
|
+
const filtered = applyJqFilter(envelope, options.jq.trim());
|
|
38
|
+
process.stdout.write(filtered.endsWith("\n") ? filtered : `${filtered}\n`);
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
|
-
process.stdout.write(
|
|
41
|
+
process.stdout.write(rendered);
|
|
42
|
+
}
|
|
43
|
+
function applyJqFilter(value, filter, spawn = node_child_process_1.spawnSync) {
|
|
44
|
+
if (!filter.trim()) {
|
|
45
|
+
throw Object.assign(new Error("--jq filter must be non-empty"), {
|
|
46
|
+
type: "usage",
|
|
47
|
+
subtype: "jq_empty",
|
|
48
|
+
status: 64,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const result = spawn(process.env.ALPHAFOX_JQ?.trim() || "jq", ["-c", filter], {
|
|
52
|
+
input: JSON.stringify(value),
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
timeout: 10_000,
|
|
55
|
+
});
|
|
56
|
+
if (result.error) {
|
|
57
|
+
const code = result.error.code;
|
|
58
|
+
if (code === "ENOENT") {
|
|
59
|
+
throw Object.assign(new Error("jq is not installed; --jq requires the jq binary on PATH"), { type: "usage", subtype: "jq_not_installed", status: 64 });
|
|
60
|
+
}
|
|
61
|
+
throw Object.assign(new Error(result.error.message), {
|
|
62
|
+
type: "usage",
|
|
63
|
+
subtype: "jq_failed",
|
|
64
|
+
status: 64,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (result.status !== 0) {
|
|
68
|
+
const errText = (result.stderr || "jq filter failed").trim();
|
|
69
|
+
throw Object.assign(new Error(errText), {
|
|
70
|
+
type: "usage",
|
|
71
|
+
subtype: "jq_failed",
|
|
72
|
+
status: 64,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return result.stdout;
|
|
36
76
|
}
|
|
37
77
|
function writeError(error, options = {}) {
|
|
38
78
|
const envelope = errorEnvelope(error, options.requestId);
|
package/dist/http/client.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.apiRequest = apiRequest;
|
|
4
4
|
const envelope_1 = require("../envelope");
|
|
5
|
+
const version_1 = require("../version");
|
|
5
6
|
const refresh_1 = require("../auth/refresh");
|
|
6
7
|
const store_1 = require("../keychain/store");
|
|
7
8
|
const allowlist_1 = require("../catalog/allowlist");
|
|
8
9
|
async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
9
|
-
|
|
10
|
+
// Keep query string for endpoints like /traders/performance?ids=...
|
|
11
|
+
const { path, query } = splitPathAndQuery(options.path);
|
|
10
12
|
if ((0, allowlist_1.isInternalDisallowedPath)(path)) {
|
|
11
13
|
throw Object.assign(new Error(`Path is internal and not allowed: ${path}`), {
|
|
12
14
|
status: 403,
|
|
@@ -16,15 +18,15 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
16
18
|
}
|
|
17
19
|
// Product raw API: /api/v1 only. Auth AS paths under /api/auth/oauth are allowed.
|
|
18
20
|
const isOAuthAsPath = path.startsWith("/api/auth/oauth");
|
|
19
|
-
if (!isOAuthAsPath &&
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
if (!isOAuthAsPath && path.startsWith("/api/")) {
|
|
22
|
+
// Unknown /api/v1/* is denied (finite facility/catalog allowlist).
|
|
23
|
+
if (!(0, allowlist_1.isFacadeAllowlistedPath)(path)) {
|
|
24
|
+
throw Object.assign(new Error(`Path is outside Public API facade: ${path}`), {
|
|
25
|
+
status: 403,
|
|
26
|
+
type: "authorization",
|
|
27
|
+
subtype: "facade_only",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
const requestId = options.requestId ?? (0, envelope_1.newRequestId)();
|
|
30
32
|
const base = options.profile.apiBaseUrl.replace(/\/$/, "");
|
|
@@ -32,30 +34,32 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
32
34
|
let url;
|
|
33
35
|
if (path.startsWith("/api/v1")) {
|
|
34
36
|
const origin = base.replace(/\/api\/v1$/, "");
|
|
35
|
-
url = `${origin}${path}`;
|
|
37
|
+
url = `${origin}${path}${query}`;
|
|
36
38
|
}
|
|
37
39
|
else if (path.startsWith("/api/auth")) {
|
|
38
40
|
const origin = base.replace(/\/api\/v1$/, "");
|
|
39
|
-
url = `${origin}${path}`;
|
|
41
|
+
url = `${origin}${path}${query}`;
|
|
40
42
|
}
|
|
41
43
|
else {
|
|
42
|
-
url = `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
44
|
+
url = `${base}${path.startsWith("/") ? path : `/${path}`}${query}`;
|
|
43
45
|
}
|
|
44
46
|
const headers = {
|
|
45
47
|
Accept: "application/json",
|
|
46
48
|
"X-Request-Id": requestId,
|
|
47
49
|
"X-Alphafox-Client": "alphafox-cli",
|
|
48
|
-
"X-Alphafox-Client-Version": env.ALPHAFOX_CLI_VERSION ??
|
|
50
|
+
"X-Alphafox-Client-Version": env.ALPHAFOX_CLI_VERSION ?? version_1.CLI_VERSION,
|
|
49
51
|
...(options.headers ?? {}),
|
|
50
52
|
};
|
|
51
53
|
if (!options.skipAuth) {
|
|
52
54
|
let tokens = (0, store_1.loadTokens)(options.profile.name, env);
|
|
53
55
|
// Proactive refresh before the access token expires (or once already expired).
|
|
54
56
|
if (tokens && (0, refresh_1.accessTokenNeedsRefresh)(tokens)) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
tokens =
|
|
57
|
+
const outcome = await (0, refresh_1.refreshStoredTokens)(options.profile, env, fetchImpl, { force: true });
|
|
58
|
+
if (outcome.status === "refreshed" || outcome.status === "unchanged") {
|
|
59
|
+
tokens = outcome.tokens;
|
|
58
60
|
}
|
|
61
|
+
// On failed refresh keep prior tokens only for the request; do not treat
|
|
62
|
+
// failure as a successful renewal.
|
|
59
63
|
}
|
|
60
64
|
if (tokens) {
|
|
61
65
|
// Never send tokens to a different site than the profile audience.
|
|
@@ -109,8 +113,8 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
109
113
|
!isOAuthAsPath) {
|
|
110
114
|
const tokens = (0, store_1.loadTokens)(options.profile.name, env);
|
|
111
115
|
if (tokens?.refreshToken?.trim()) {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
116
|
+
const outcome = await (0, refresh_1.refreshStoredTokens)(options.profile, env, fetchImpl, { force: true });
|
|
117
|
+
if (outcome.status === "refreshed") {
|
|
114
118
|
return apiRequest({ ...options, requestId, _refreshRetried: true }, env, fetchImpl);
|
|
115
119
|
}
|
|
116
120
|
}
|
|
@@ -190,3 +194,15 @@ function sameAuthSite(a, b) {
|
|
|
190
194
|
return false;
|
|
191
195
|
}
|
|
192
196
|
}
|
|
197
|
+
/** Split raw path so allowlist uses path-only while fetch keeps query. */
|
|
198
|
+
function splitPathAndQuery(raw) {
|
|
199
|
+
const trimmed = raw.trim();
|
|
200
|
+
const q = trimmed.indexOf("?");
|
|
201
|
+
if (q < 0) {
|
|
202
|
+
return { path: (0, allowlist_1.normalizeApiPath)(trimmed), query: "" };
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
path: (0, allowlist_1.normalizeApiPath)(trimmed.slice(0, q)),
|
|
206
|
+
query: trimmed.slice(q),
|
|
207
|
+
};
|
|
208
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export { runCli, parseGlobalFlags } from "./commands/run";
|
|
2
|
-
export { successEnvelope, errorEnvelope, parseJsonEnvelope, writeSuccess, writeError, } from "./envelope";
|
|
2
|
+
export { successEnvelope, errorEnvelope, parseJsonEnvelope, writeSuccess, writeError, applyJqFilter, } from "./envelope";
|
|
3
3
|
export { resolveProfile, loadConfigFile, saveConfigFile, assertNoTokenFields, } from "./config/profiles";
|
|
4
|
-
export { saveTokens, loadTokens, deleteTokens, tokenFingerprint, } from "./keychain/store";
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
4
|
+
export { saveTokens, loadTokens, deleteTokens, tokenFingerprint, getLastTokenSaveResult, probeOsKeychain, keychainPlatform, } from "./keychain/store";
|
|
5
|
+
export { linuxSecretServiceArgs, linuxSecretToolBin, } from "./keychain/linux-secret-service";
|
|
6
|
+
export { windowsCredentialTarget, WINDOWS_CRED_MAX_BYTES, } from "./keychain/windows-credential";
|
|
7
|
+
export { isFacadeAllowlistedPath, isInternalDisallowedPath, normalizeApiPath, pathTemplateMatches, } from "./catalog/allowlist";
|
|
8
|
+
export { checkCliCompatibility, type CompatibilityRange, type CompatibilityResult, } from "./catalog/compatibility";
|
|
9
|
+
export { resolveTypedCommand } from "./catalog/command-tree";
|
|
10
|
+
export { assertHighRiskConfirmation, inferRawApiRisk, requiresHighRiskConfirmation, } from "./safety/confirmation";
|
|
11
|
+
export { refreshStoredTokens, refreshStoredTokensOrNull, accessTokenNeedsRefresh, } from "./auth/refresh";
|
|
12
|
+
export { runBrowserPkceLogin } from "./auth/browser-login";
|
|
13
|
+
export { startLoopbackCallbackServer } from "./auth/loopback-callback";
|
|
14
|
+
export { CATALOG_OPERATIONS, CATALOG_SOURCE, CATALOG_VERSION, COMPATIBILITY_RANGE, findCatalogOperation, findCatalogOperationByRoute, getOperationSchemaDocument, buildCapabilityManifest, checkGeneratedCatalogCompatibility, } from "./catalog/operations";
|
|
8
15
|
export { CLI_VERSION, CLI_PACKAGE, CLI_CONTRACT_VERSION } from "./version";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CLI_CONTRACT_VERSION = exports.CLI_PACKAGE = exports.CLI_VERSION = exports.buildCapabilityManifest = exports.findCatalogOperation = exports.CATALOG_OPERATIONS = exports.assertHighRiskConfirmation = exports.normalizeApiPath = exports.isInternalDisallowedPath = exports.isFacadeAllowlistedPath = exports.tokenFingerprint = exports.deleteTokens = exports.loadTokens = exports.saveTokens = exports.assertNoTokenFields = exports.saveConfigFile = exports.loadConfigFile = exports.resolveProfile = exports.writeError = exports.writeSuccess = exports.parseJsonEnvelope = exports.errorEnvelope = exports.successEnvelope = exports.parseGlobalFlags = exports.runCli = void 0;
|
|
3
|
+
exports.CLI_CONTRACT_VERSION = exports.CLI_PACKAGE = exports.CLI_VERSION = exports.checkGeneratedCatalogCompatibility = exports.buildCapabilityManifest = exports.getOperationSchemaDocument = exports.findCatalogOperationByRoute = exports.findCatalogOperation = exports.COMPATIBILITY_RANGE = exports.CATALOG_VERSION = exports.CATALOG_SOURCE = exports.CATALOG_OPERATIONS = exports.startLoopbackCallbackServer = exports.runBrowserPkceLogin = exports.accessTokenNeedsRefresh = exports.refreshStoredTokensOrNull = exports.refreshStoredTokens = exports.requiresHighRiskConfirmation = exports.inferRawApiRisk = exports.assertHighRiskConfirmation = exports.resolveTypedCommand = exports.checkCliCompatibility = exports.pathTemplateMatches = exports.normalizeApiPath = exports.isInternalDisallowedPath = exports.isFacadeAllowlistedPath = exports.WINDOWS_CRED_MAX_BYTES = exports.windowsCredentialTarget = exports.linuxSecretToolBin = exports.linuxSecretServiceArgs = exports.keychainPlatform = exports.probeOsKeychain = exports.getLastTokenSaveResult = exports.tokenFingerprint = exports.deleteTokens = exports.loadTokens = exports.saveTokens = exports.assertNoTokenFields = exports.saveConfigFile = exports.loadConfigFile = exports.resolveProfile = exports.applyJqFilter = exports.writeError = exports.writeSuccess = exports.parseJsonEnvelope = exports.errorEnvelope = exports.successEnvelope = exports.parseGlobalFlags = exports.runCli = void 0;
|
|
4
4
|
var run_1 = require("./commands/run");
|
|
5
5
|
Object.defineProperty(exports, "runCli", { enumerable: true, get: function () { return run_1.runCli; } });
|
|
6
6
|
Object.defineProperty(exports, "parseGlobalFlags", { enumerable: true, get: function () { return run_1.parseGlobalFlags; } });
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "errorEnvelope", { enumerable: true, get: functio
|
|
|
10
10
|
Object.defineProperty(exports, "parseJsonEnvelope", { enumerable: true, get: function () { return envelope_1.parseJsonEnvelope; } });
|
|
11
11
|
Object.defineProperty(exports, "writeSuccess", { enumerable: true, get: function () { return envelope_1.writeSuccess; } });
|
|
12
12
|
Object.defineProperty(exports, "writeError", { enumerable: true, get: function () { return envelope_1.writeError; } });
|
|
13
|
+
Object.defineProperty(exports, "applyJqFilter", { enumerable: true, get: function () { return envelope_1.applyJqFilter; } });
|
|
13
14
|
var profiles_1 = require("./config/profiles");
|
|
14
15
|
Object.defineProperty(exports, "resolveProfile", { enumerable: true, get: function () { return profiles_1.resolveProfile; } });
|
|
15
16
|
Object.defineProperty(exports, "loadConfigFile", { enumerable: true, get: function () { return profiles_1.loadConfigFile; } });
|
|
@@ -20,16 +21,46 @@ Object.defineProperty(exports, "saveTokens", { enumerable: true, get: function (
|
|
|
20
21
|
Object.defineProperty(exports, "loadTokens", { enumerable: true, get: function () { return store_1.loadTokens; } });
|
|
21
22
|
Object.defineProperty(exports, "deleteTokens", { enumerable: true, get: function () { return store_1.deleteTokens; } });
|
|
22
23
|
Object.defineProperty(exports, "tokenFingerprint", { enumerable: true, get: function () { return store_1.tokenFingerprint; } });
|
|
24
|
+
Object.defineProperty(exports, "getLastTokenSaveResult", { enumerable: true, get: function () { return store_1.getLastTokenSaveResult; } });
|
|
25
|
+
Object.defineProperty(exports, "probeOsKeychain", { enumerable: true, get: function () { return store_1.probeOsKeychain; } });
|
|
26
|
+
Object.defineProperty(exports, "keychainPlatform", { enumerable: true, get: function () { return store_1.keychainPlatform; } });
|
|
27
|
+
var linux_secret_service_1 = require("./keychain/linux-secret-service");
|
|
28
|
+
Object.defineProperty(exports, "linuxSecretServiceArgs", { enumerable: true, get: function () { return linux_secret_service_1.linuxSecretServiceArgs; } });
|
|
29
|
+
Object.defineProperty(exports, "linuxSecretToolBin", { enumerable: true, get: function () { return linux_secret_service_1.linuxSecretToolBin; } });
|
|
30
|
+
var windows_credential_1 = require("./keychain/windows-credential");
|
|
31
|
+
Object.defineProperty(exports, "windowsCredentialTarget", { enumerable: true, get: function () { return windows_credential_1.windowsCredentialTarget; } });
|
|
32
|
+
Object.defineProperty(exports, "WINDOWS_CRED_MAX_BYTES", { enumerable: true, get: function () { return windows_credential_1.WINDOWS_CRED_MAX_BYTES; } });
|
|
23
33
|
var allowlist_1 = require("./catalog/allowlist");
|
|
24
34
|
Object.defineProperty(exports, "isFacadeAllowlistedPath", { enumerable: true, get: function () { return allowlist_1.isFacadeAllowlistedPath; } });
|
|
25
35
|
Object.defineProperty(exports, "isInternalDisallowedPath", { enumerable: true, get: function () { return allowlist_1.isInternalDisallowedPath; } });
|
|
26
36
|
Object.defineProperty(exports, "normalizeApiPath", { enumerable: true, get: function () { return allowlist_1.normalizeApiPath; } });
|
|
37
|
+
Object.defineProperty(exports, "pathTemplateMatches", { enumerable: true, get: function () { return allowlist_1.pathTemplateMatches; } });
|
|
38
|
+
var compatibility_1 = require("./catalog/compatibility");
|
|
39
|
+
Object.defineProperty(exports, "checkCliCompatibility", { enumerable: true, get: function () { return compatibility_1.checkCliCompatibility; } });
|
|
40
|
+
var command_tree_1 = require("./catalog/command-tree");
|
|
41
|
+
Object.defineProperty(exports, "resolveTypedCommand", { enumerable: true, get: function () { return command_tree_1.resolveTypedCommand; } });
|
|
27
42
|
var confirmation_1 = require("./safety/confirmation");
|
|
28
43
|
Object.defineProperty(exports, "assertHighRiskConfirmation", { enumerable: true, get: function () { return confirmation_1.assertHighRiskConfirmation; } });
|
|
44
|
+
Object.defineProperty(exports, "inferRawApiRisk", { enumerable: true, get: function () { return confirmation_1.inferRawApiRisk; } });
|
|
45
|
+
Object.defineProperty(exports, "requiresHighRiskConfirmation", { enumerable: true, get: function () { return confirmation_1.requiresHighRiskConfirmation; } });
|
|
46
|
+
var refresh_1 = require("./auth/refresh");
|
|
47
|
+
Object.defineProperty(exports, "refreshStoredTokens", { enumerable: true, get: function () { return refresh_1.refreshStoredTokens; } });
|
|
48
|
+
Object.defineProperty(exports, "refreshStoredTokensOrNull", { enumerable: true, get: function () { return refresh_1.refreshStoredTokensOrNull; } });
|
|
49
|
+
Object.defineProperty(exports, "accessTokenNeedsRefresh", { enumerable: true, get: function () { return refresh_1.accessTokenNeedsRefresh; } });
|
|
50
|
+
var browser_login_1 = require("./auth/browser-login");
|
|
51
|
+
Object.defineProperty(exports, "runBrowserPkceLogin", { enumerable: true, get: function () { return browser_login_1.runBrowserPkceLogin; } });
|
|
52
|
+
var loopback_callback_1 = require("./auth/loopback-callback");
|
|
53
|
+
Object.defineProperty(exports, "startLoopbackCallbackServer", { enumerable: true, get: function () { return loopback_callback_1.startLoopbackCallbackServer; } });
|
|
29
54
|
var operations_1 = require("./catalog/operations");
|
|
30
55
|
Object.defineProperty(exports, "CATALOG_OPERATIONS", { enumerable: true, get: function () { return operations_1.CATALOG_OPERATIONS; } });
|
|
56
|
+
Object.defineProperty(exports, "CATALOG_SOURCE", { enumerable: true, get: function () { return operations_1.CATALOG_SOURCE; } });
|
|
57
|
+
Object.defineProperty(exports, "CATALOG_VERSION", { enumerable: true, get: function () { return operations_1.CATALOG_VERSION; } });
|
|
58
|
+
Object.defineProperty(exports, "COMPATIBILITY_RANGE", { enumerable: true, get: function () { return operations_1.COMPATIBILITY_RANGE; } });
|
|
31
59
|
Object.defineProperty(exports, "findCatalogOperation", { enumerable: true, get: function () { return operations_1.findCatalogOperation; } });
|
|
60
|
+
Object.defineProperty(exports, "findCatalogOperationByRoute", { enumerable: true, get: function () { return operations_1.findCatalogOperationByRoute; } });
|
|
61
|
+
Object.defineProperty(exports, "getOperationSchemaDocument", { enumerable: true, get: function () { return operations_1.getOperationSchemaDocument; } });
|
|
32
62
|
Object.defineProperty(exports, "buildCapabilityManifest", { enumerable: true, get: function () { return operations_1.buildCapabilityManifest; } });
|
|
63
|
+
Object.defineProperty(exports, "checkGeneratedCatalogCompatibility", { enumerable: true, get: function () { return operations_1.checkGeneratedCatalogCompatibility; } });
|
|
33
64
|
var version_1 = require("./version");
|
|
34
65
|
Object.defineProperty(exports, "CLI_VERSION", { enumerable: true, get: function () { return version_1.CLI_VERSION; } });
|
|
35
66
|
Object.defineProperty(exports, "CLI_PACKAGE", { enumerable: true, get: function () { return version_1.CLI_PACKAGE; } });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linux Secret Service via `secret-tool` (libsecret).
|
|
3
|
+
* Tokens travel on stdin/stdout, never as argv.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LINUX_SECRET_LABEL_PREFIX = "alphafox-cli";
|
|
6
|
+
export declare function linuxSecretServiceArgs(action: "store" | "lookup" | "clear", service: string, account: string): readonly string[];
|
|
7
|
+
export declare function linuxSecretToolBin(env?: NodeJS.ProcessEnv): string;
|
|
8
|
+
export declare function linuxSecretServiceWrite(service: string, account: string, payload: string, env?: NodeJS.ProcessEnv): boolean;
|
|
9
|
+
export declare function linuxSecretServiceRead(service: string, account: string, env?: NodeJS.ProcessEnv): string | null;
|
|
10
|
+
export declare function linuxSecretServiceDelete(service: string, account: string, env?: NodeJS.ProcessEnv): void;
|
|
11
|
+
export declare function linuxSecretServiceAvailable(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Linux Secret Service via `secret-tool` (libsecret).
|
|
4
|
+
* Tokens travel on stdin/stdout, never as argv.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.LINUX_SECRET_LABEL_PREFIX = void 0;
|
|
8
|
+
exports.linuxSecretServiceArgs = linuxSecretServiceArgs;
|
|
9
|
+
exports.linuxSecretToolBin = linuxSecretToolBin;
|
|
10
|
+
exports.linuxSecretServiceWrite = linuxSecretServiceWrite;
|
|
11
|
+
exports.linuxSecretServiceRead = linuxSecretServiceRead;
|
|
12
|
+
exports.linuxSecretServiceDelete = linuxSecretServiceDelete;
|
|
13
|
+
exports.linuxSecretServiceAvailable = linuxSecretServiceAvailable;
|
|
14
|
+
const node_child_process_1 = require("node:child_process");
|
|
15
|
+
exports.LINUX_SECRET_LABEL_PREFIX = "alphafox-cli";
|
|
16
|
+
function linuxSecretServiceArgs(action, service, account) {
|
|
17
|
+
if (action === "store") {
|
|
18
|
+
return [
|
|
19
|
+
"store",
|
|
20
|
+
"--label",
|
|
21
|
+
`${exports.LINUX_SECRET_LABEL_PREFIX} ${service}`,
|
|
22
|
+
"service",
|
|
23
|
+
service,
|
|
24
|
+
"account",
|
|
25
|
+
account,
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
if (action === "lookup") {
|
|
29
|
+
return ["lookup", "service", service, "account", account];
|
|
30
|
+
}
|
|
31
|
+
return ["clear", "service", service, "account", account];
|
|
32
|
+
}
|
|
33
|
+
function linuxSecretToolBin(env = process.env) {
|
|
34
|
+
return env.ALPHAFOX_SECRET_TOOL?.trim() || "secret-tool";
|
|
35
|
+
}
|
|
36
|
+
function childEnv(env) {
|
|
37
|
+
return { ...process.env, ...env };
|
|
38
|
+
}
|
|
39
|
+
function linuxSecretServiceWrite(service, account, payload, env = process.env) {
|
|
40
|
+
try {
|
|
41
|
+
(0, node_child_process_1.execFileSync)(linuxSecretToolBin(env), [...linuxSecretServiceArgs("store", service, account)], {
|
|
42
|
+
input: payload,
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
45
|
+
timeout: 10_000,
|
|
46
|
+
env: childEnv(env),
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function linuxSecretServiceRead(service, account, env = process.env) {
|
|
55
|
+
try {
|
|
56
|
+
const out = (0, node_child_process_1.execFileSync)(linuxSecretToolBin(env), [...linuxSecretServiceArgs("lookup", service, account)], {
|
|
57
|
+
encoding: "utf8",
|
|
58
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
59
|
+
timeout: 10_000,
|
|
60
|
+
env: childEnv(env),
|
|
61
|
+
});
|
|
62
|
+
const text = out.replace(/\n$/, "");
|
|
63
|
+
return text.length > 0 ? text : null;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function linuxSecretServiceDelete(service, account, env = process.env) {
|
|
70
|
+
try {
|
|
71
|
+
(0, node_child_process_1.execFileSync)(linuxSecretToolBin(env), [...linuxSecretServiceArgs("clear", service, account)], {
|
|
72
|
+
stdio: "ignore",
|
|
73
|
+
timeout: 10_000,
|
|
74
|
+
env: childEnv(env),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// none
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function linuxSecretServiceAvailable(env = process.env) {
|
|
82
|
+
try {
|
|
83
|
+
(0, node_child_process_1.execFileSync)(linuxSecretToolBin(env), ["--help"], {
|
|
84
|
+
stdio: "ignore",
|
|
85
|
+
timeout: 5_000,
|
|
86
|
+
env: childEnv(env),
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/keychain/store.d.ts
CHANGED
|
@@ -13,7 +13,26 @@ export interface StoredTokens {
|
|
|
13
13
|
readonly clientId: string;
|
|
14
14
|
readonly scopes: readonly string[];
|
|
15
15
|
}
|
|
16
|
-
export
|
|
16
|
+
export type TokenStorageBackend = "keychain" | "file" | "test-injection";
|
|
17
|
+
export type OsKeychainKind = "macos-security" | "linux-secret-service" | "windows-credential-manager" | "none";
|
|
18
|
+
export interface TokenStorageResult {
|
|
19
|
+
readonly backend: TokenStorageBackend;
|
|
20
|
+
readonly kind?: OsKeychainKind;
|
|
21
|
+
/** Absolute path when backend is file. */
|
|
22
|
+
readonly path?: string;
|
|
23
|
+
/** True when OS keychain failed/unavailable and file fallback was used. */
|
|
24
|
+
readonly degraded: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function getLastTokenSaveResult(): TokenStorageResult | null;
|
|
27
|
+
export declare function keychainServiceName(profile: string): string;
|
|
28
|
+
export declare function keychainAccountName(): string;
|
|
29
|
+
/** Test-only override. Production code uses process.platform. */
|
|
30
|
+
export declare function keychainPlatform(env?: NodeJS.ProcessEnv): NodeJS.Platform;
|
|
31
|
+
export declare function probeOsKeychain(env?: NodeJS.ProcessEnv): {
|
|
32
|
+
readonly kind: OsKeychainKind;
|
|
33
|
+
readonly available: boolean;
|
|
34
|
+
};
|
|
35
|
+
export declare function saveTokens(profile: string, tokens: StoredTokens, env?: NodeJS.ProcessEnv): TokenStorageResult;
|
|
17
36
|
export declare function loadTokens(profile: string, env?: NodeJS.ProcessEnv): StoredTokens | null;
|
|
18
37
|
export declare function deleteTokens(profile: string, env?: NodeJS.ProcessEnv): void;
|
|
19
38
|
export declare function tokenFingerprint(token: string): string;
|
package/dist/keychain/store.js
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* (local unit tests only; not a production automation path — ADR 0004).
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getLastTokenSaveResult = getLastTokenSaveResult;
|
|
9
|
+
exports.keychainServiceName = keychainServiceName;
|
|
10
|
+
exports.keychainAccountName = keychainAccountName;
|
|
11
|
+
exports.keychainPlatform = keychainPlatform;
|
|
12
|
+
exports.probeOsKeychain = probeOsKeychain;
|
|
8
13
|
exports.saveTokens = saveTokens;
|
|
9
14
|
exports.loadTokens = loadTokens;
|
|
10
15
|
exports.deleteTokens = deleteTokens;
|
|
@@ -14,12 +19,52 @@ const node_fs_1 = require("node:fs");
|
|
|
14
19
|
const node_os_1 = require("node:os");
|
|
15
20
|
const node_path_1 = require("node:path");
|
|
16
21
|
const node_child_process_1 = require("node:child_process");
|
|
17
|
-
|
|
22
|
+
const linux_secret_service_1 = require("./linux-secret-service");
|
|
23
|
+
const windows_credential_1 = require("./windows-credential");
|
|
24
|
+
/** Last save outcome — callers/tests can observe silent-fallback without stderr parsing. */
|
|
25
|
+
let lastSaveResult = null;
|
|
26
|
+
function getLastTokenSaveResult() {
|
|
27
|
+
return lastSaveResult;
|
|
28
|
+
}
|
|
29
|
+
function keychainServiceName(profile) {
|
|
18
30
|
return `alphafox-cli.${profile}`;
|
|
19
31
|
}
|
|
20
|
-
function
|
|
32
|
+
function keychainAccountName() {
|
|
21
33
|
return "oauth-tokens";
|
|
22
34
|
}
|
|
35
|
+
function serviceName(profile) {
|
|
36
|
+
return keychainServiceName(profile);
|
|
37
|
+
}
|
|
38
|
+
function accountName() {
|
|
39
|
+
return keychainAccountName();
|
|
40
|
+
}
|
|
41
|
+
/** Test-only override. Production code uses process.platform. */
|
|
42
|
+
function keychainPlatform(env = process.env) {
|
|
43
|
+
const raw = env.ALPHAFOX_KEYCHAIN_PLATFORM?.trim();
|
|
44
|
+
if (raw === "darwin" || raw === "linux" || raw === "win32") {
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
return process.platform;
|
|
48
|
+
}
|
|
49
|
+
function probeOsKeychain(env = process.env) {
|
|
50
|
+
const platform = keychainPlatform(env);
|
|
51
|
+
if (platform === "darwin") {
|
|
52
|
+
return { kind: "macos-security", available: true };
|
|
53
|
+
}
|
|
54
|
+
if (platform === "linux") {
|
|
55
|
+
return {
|
|
56
|
+
kind: "linux-secret-service",
|
|
57
|
+
available: (0, linux_secret_service_1.linuxSecretServiceAvailable)(env),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (platform === "win32") {
|
|
61
|
+
return {
|
|
62
|
+
kind: "windows-credential-manager",
|
|
63
|
+
available: (0, windows_credential_1.windowsCredentialAvailable)(env),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { kind: "none", available: false };
|
|
67
|
+
}
|
|
23
68
|
/** File fallback under secure mode 0600 when OS keychain is unavailable (CI/Linux headless). */
|
|
24
69
|
function fileFallbackPath(profile, env) {
|
|
25
70
|
const base = env.ALPHAFOX_KEYCHAIN_DIR?.trim() ||
|
|
@@ -29,11 +74,31 @@ function fileFallbackPath(profile, env) {
|
|
|
29
74
|
function saveTokens(profile, tokens, env = process.env) {
|
|
30
75
|
const payload = JSON.stringify(tokens);
|
|
31
76
|
if (tryKeychainWrite(profile, payload, env)) {
|
|
32
|
-
|
|
77
|
+
lastSaveResult = {
|
|
78
|
+
backend: "keychain",
|
|
79
|
+
kind: probeOsKeychain(env).kind,
|
|
80
|
+
degraded: false,
|
|
81
|
+
};
|
|
82
|
+
return lastSaveResult;
|
|
33
83
|
}
|
|
34
84
|
const path = fileFallbackPath(profile, env);
|
|
35
85
|
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(path, ".."), { recursive: true });
|
|
36
86
|
(0, node_fs_1.writeFileSync)(path, payload, { mode: 0o600 });
|
|
87
|
+
const intentionalFile = env.ALPHAFOX_FORCE_FILE_KEYCHAIN === "1";
|
|
88
|
+
lastSaveResult = {
|
|
89
|
+
backend: "file",
|
|
90
|
+
path,
|
|
91
|
+
// Intentional file mode is not a silent degrade.
|
|
92
|
+
degraded: !intentionalFile,
|
|
93
|
+
};
|
|
94
|
+
// Observable signal when OS keychain failed unexpectedly (not force-file).
|
|
95
|
+
if (!intentionalFile) {
|
|
96
|
+
process.emitWarning(`OS keychain unavailable for profile "${profile}"; tokens stored in file ${path} (mode 0600). Set ALPHAFOX_FORCE_FILE_KEYCHAIN=1 when file storage is intentional.`, {
|
|
97
|
+
code: "ALPHAFOX_KEYCHAIN_FILE_FALLBACK",
|
|
98
|
+
detail: path,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return lastSaveResult;
|
|
37
102
|
}
|
|
38
103
|
function loadTokens(profile, env = process.env) {
|
|
39
104
|
// Controlled test injection — never document as prod automation.
|
|
@@ -77,7 +142,14 @@ function tryKeychainWrite(profile, payload, env) {
|
|
|
77
142
|
if (env.ALPHAFOX_FORCE_FILE_KEYCHAIN === "1") {
|
|
78
143
|
return false;
|
|
79
144
|
}
|
|
80
|
-
|
|
145
|
+
const platform = keychainPlatform(env);
|
|
146
|
+
if (platform === "linux") {
|
|
147
|
+
return (0, linux_secret_service_1.linuxSecretServiceWrite)(serviceName(profile), accountName(), payload, env);
|
|
148
|
+
}
|
|
149
|
+
if (platform === "win32") {
|
|
150
|
+
return (0, windows_credential_1.windowsCredentialWrite)(profile, payload, env);
|
|
151
|
+
}
|
|
152
|
+
if (platform === "darwin") {
|
|
81
153
|
try {
|
|
82
154
|
// delete existing silently
|
|
83
155
|
try {
|
|
@@ -114,7 +186,14 @@ function tryKeychainRead(profile, env) {
|
|
|
114
186
|
if (env.ALPHAFOX_FORCE_FILE_KEYCHAIN === "1") {
|
|
115
187
|
return null;
|
|
116
188
|
}
|
|
117
|
-
|
|
189
|
+
const platform = keychainPlatform(env);
|
|
190
|
+
if (platform === "linux") {
|
|
191
|
+
return (0, linux_secret_service_1.linuxSecretServiceRead)(serviceName(profile), accountName(), env);
|
|
192
|
+
}
|
|
193
|
+
if (platform === "win32") {
|
|
194
|
+
return (0, windows_credential_1.windowsCredentialRead)(profile, env);
|
|
195
|
+
}
|
|
196
|
+
if (platform === "darwin") {
|
|
118
197
|
try {
|
|
119
198
|
const out = (0, node_child_process_1.execFileSync)("security", [
|
|
120
199
|
"find-generic-password",
|
|
@@ -136,7 +215,16 @@ function tryKeychainDelete(profile, env) {
|
|
|
136
215
|
if (env.ALPHAFOX_FORCE_FILE_KEYCHAIN === "1") {
|
|
137
216
|
return;
|
|
138
217
|
}
|
|
139
|
-
|
|
218
|
+
const platform = keychainPlatform(env);
|
|
219
|
+
if (platform === "linux") {
|
|
220
|
+
(0, linux_secret_service_1.linuxSecretServiceDelete)(serviceName(profile), accountName(), env);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (platform === "win32") {
|
|
224
|
+
(0, windows_credential_1.windowsCredentialDelete)(profile, env);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (platform === "darwin") {
|
|
140
228
|
try {
|
|
141
229
|
(0, node_child_process_1.execFileSync)("security", [
|
|
142
230
|
"delete-generic-password",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows Credential Manager via advapi32 CredWrite/CredRead/CredDelete.
|
|
3
|
+
* Payload is passed on stdin to PowerShell — never as argv.
|
|
4
|
+
*/
|
|
5
|
+
/** CRED_MAX_CREDENTIAL_BLOB_SIZE is 5*512 = 2560. */
|
|
6
|
+
export declare const WINDOWS_CRED_MAX_BYTES = 2560;
|
|
7
|
+
export declare const WINDOWS_CRED_PS1 = "# Alphafox CLI \u2014 Windows Credential Manager helper (t101360)\nparam(\n [Parameter(Mandatory = $true)][ValidateSet('write','read','delete')][string]$Action,\n [Parameter(Mandatory = $true)][string]$Target\n)\n$ErrorActionPreference = 'Stop'\nAdd-Type -TypeDefinition @\"\nusing System;\nusing System.Runtime.InteropServices;\nnamespace AlphafoxCred {\n [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]\n public struct CREDENTIAL {\n public uint Flags;\n public uint Type;\n public string TargetName;\n public string Comment;\n public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;\n public uint CredentialBlobSize;\n public IntPtr CredentialBlob;\n public uint Persist;\n public uint AttributeCount;\n public IntPtr Attributes;\n public string TargetAlias;\n public string UserName;\n }\n public static class Native {\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredWrite(ref CREDENTIAL credential, uint flags);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredRead(string target, uint type, uint flags, out IntPtr credentialPtr);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredDelete(string target, uint type, uint flags);\n [DllImport(\"advapi32.dll\", SetLastError = true)]\n public static extern void CredFree(IntPtr credential);\n }\n}\n\"@\n$CredTypeGeneric = 1\n$PersistLocalMachine = 2\nswitch ($Action) {\n 'write' {\n $payload = [Console]::In.ReadToEnd()\n $bytes = [Text.Encoding]::UTF8.GetBytes($payload)\n if ($bytes.Length -gt 2560) { throw \"credential blob too large\" }\n $blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)\n try {\n [Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)\n $cred = New-Object AlphafoxCred.CREDENTIAL\n $cred.Type = $CredTypeGeneric\n $cred.TargetName = $Target\n $cred.UserName = \"alphafox-cli\"\n $cred.CredentialBlobSize = [uint32]$bytes.Length\n $cred.CredentialBlob = $blob\n $cred.Persist = $PersistLocalMachine\n $ok = [AlphafoxCred.Native]::CredWrite([ref]$cred, 0)\n if (-not $ok) {\n throw \"CredWrite failed Win32=$([Runtime.InteropServices.Marshal]::GetLastWin32Error())\"\n }\n } finally {\n [Runtime.InteropServices.Marshal]::FreeHGlobal($blob)\n }\n }\n 'read' {\n $ptr = [IntPtr]::Zero\n $ok = [AlphafoxCred.Native]::CredRead($Target, $CredTypeGeneric, 0, [ref]$ptr)\n if (-not $ok) { exit 2 }\n try {\n $cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][AlphafoxCred.CREDENTIAL])\n $size = [int]$cred.CredentialBlobSize\n $bytes = New-Object byte[] $size\n [Runtime.InteropServices.Marshal]::Copy($cred.CredentialBlob, $bytes, 0, $size)\n [Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))\n } finally {\n [AlphafoxCred.Native]::CredFree($ptr)\n }\n }\n 'delete' {\n [void][AlphafoxCred.Native]::CredDelete($Target, $CredTypeGeneric, 0)\n }\n}\n";
|
|
8
|
+
export declare function windowsCredentialTarget(profile: string): string;
|
|
9
|
+
export declare function windowsPowershellBin(env?: NodeJS.ProcessEnv): string;
|
|
10
|
+
export declare function windowsCredentialWrite(profile: string, payload: string, env?: NodeJS.ProcessEnv): boolean;
|
|
11
|
+
export declare function windowsCredentialRead(profile: string, env?: NodeJS.ProcessEnv): string | null;
|
|
12
|
+
export declare function windowsCredentialDelete(profile: string, env?: NodeJS.ProcessEnv): void;
|
|
13
|
+
export declare function windowsCredentialAvailable(env?: NodeJS.ProcessEnv): boolean;
|