@knowttl/artium-axi 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -3
- package/bin/artium-axi.mjs +308 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -652,7 +652,7 @@ See the [API contract](src/shared/api.ts) and [retention and export runbook](doc
|
|
|
652
652
|
| --- | --- |
|
|
653
653
|
| `ARTIUM_AXI_URL` | Required HTTPS base URL without credentials, query or fragment. |
|
|
654
654
|
| `ARTIUM_AXI_CA_FILE` | Optional readable PEM CA file added to Node's certificate trust. |
|
|
655
|
-
| `ARTIUM_AXI_PASSWORD` |
|
|
655
|
+
| `ARTIUM_AXI_PASSWORD` | Ordinary login password source when `--password-stdin` is absent, before the terminal prompt; refused for [managed sign-in](#externally-managed-client-sessions). |
|
|
656
656
|
| `ARTIUM_AXI_CURRENT_PASSWORD` | Current password for an ordinary password change, before the terminal prompt. |
|
|
657
657
|
| `XDG_CONFIG_HOME` | Session-file base on Linux/macOS; otherwise `~/.config`. |
|
|
658
658
|
| `APPDATA` | Session-file base on Windows, whose execution remains unverified. |
|
|
@@ -660,11 +660,12 @@ See the [API contract](src/shared/api.ts) and [retention and export runbook](doc
|
|
|
660
660
|
|
|
661
661
|
Use `login --password-stdin` or `passwd --password-stdin` to supply the login or new password through stdin.
|
|
662
662
|
Stdin removes one final LF or CRLF and preserves all other spaces and Unicode.
|
|
663
|
-
Password arguments are refused; tokens are never printed.
|
|
663
|
+
Password arguments are refused; tokens are never printed to stdout or stderr.
|
|
664
664
|
Ordinary password changes also require the current password through the terminal prompt or `ARTIUM_AXI_CURRENT_PASSWORD`.
|
|
665
665
|
Clear password environment values after use.
|
|
666
666
|
|
|
667
|
-
|
|
667
|
+
By default, sessions live in `artium-axi/session.json` under the applicable base directory and are bound to the normalized server URL.
|
|
668
|
+
Callers that manage their own tokens can instead use [externally managed client sessions](#externally-managed-client-sessions).
|
|
668
669
|
POSIX directories use mode 0700 and files mode 0600; Windows uses a restricted ACL and refuses storage if it cannot establish it.
|
|
669
670
|
Do not export or share the session file.
|
|
670
671
|
CLI processes serialize refreshes through `session.lock`; after `SESSION_BUSY`, remove that lock directory only when every CLI process has exited.
|
|
@@ -675,6 +676,58 @@ Certificate verification remains enforced even with `NODE_TLS_REJECT_UNAUTHORIZE
|
|
|
675
676
|
Only an explicit pre-execution `TOKEN_EXPIRED` response permits one serialized refresh and retry.
|
|
676
677
|
A network failure or uncertain command outcome is never retried automatically.
|
|
677
678
|
|
|
679
|
+
### Externally managed client sessions
|
|
680
|
+
|
|
681
|
+
Callers that keep credentials outside the CLI can opt in with one leading flag: `artium-axi --managed-session <command> <args>`.
|
|
682
|
+
The caller supplies inherited pipes or sockets: fd 3 for session input on authenticated calls and fd 4 for private new-session and rotation output.
|
|
683
|
+
Stdin remains available for tool input; stdout and stderr retain their normal command formats.
|
|
684
|
+
Do not pass tokens in arguments or environment variables, connect fd 4 to a log or public output, or redirect either private channel to a disk file.
|
|
685
|
+
The CLI refuses regular files as private channels and never reads, creates, locks, updates or deletes `session.json` in this mode.
|
|
686
|
+
|
|
687
|
+
For authenticated calls, send one UTF-8 JSON object of at most 64 KiB to fd 3, then close the input pipe:
|
|
688
|
+
|
|
689
|
+
```json
|
|
690
|
+
{"serverUrl":"https://artium.example.test","accessToken":"CANARY-access","refreshToken":"CANARY-refresh"}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
`serverUrl` must match the normalized `ARTIUM_AXI_URL`, allowing a final slash.
|
|
694
|
+
`accessToken` is required and nonempty; `refreshToken` is optional and nonempty when supplied.
|
|
695
|
+
The optional fields `status: "authenticated"`, `accessExpiresAt` and `familyExpiresAt` (integer Unix milliseconds) allow a rotation record to be reused as input; other fields are refused.
|
|
696
|
+
The caller owns expiry policy, refresh serialization across processes and secure persistence.
|
|
697
|
+
Managed mode supports `login`, the home view, `whoami`, `tools`, `tools show`, `doctor` and tool commands.
|
|
698
|
+
`passwd`, `logout` and `setup` are refused before any network call; ordinary sign-in and its server-version warning remain unchanged without the flag.
|
|
699
|
+
Help is available without private channels through `artium-axi --help`.
|
|
700
|
+
|
|
701
|
+
To obtain the first session, run `artium-axi --managed-session login --username <username> --password-stdin` with the password on protected inherited stdin, or `artium-axi --managed-session login --sso` for browser approval.
|
|
702
|
+
Login does not read or require fd 3; it requires a writable private pipe or socket on fd 4 before contacting the server.
|
|
703
|
+
Password arguments and `ARTIUM_AXI_PASSWORD` are refused, including when `--password-stdin` is present; unset that variable before managed sign-in.
|
|
704
|
+
Password input follows the stdin rules in [client settings](#client-settings).
|
|
705
|
+
SSO displays the approval URL and user code immediately so the user can approve while the CLI waits.
|
|
706
|
+
Both sign-in methods return the new session as one newline-terminated JSON object on fd 4, using the same envelope shown below for rotations, and create nothing under the config or session paths.
|
|
707
|
+
The login server-version warning still appears on stderr when versions differ.
|
|
708
|
+
If a local account requires a password change, fd 4 instead receives `{serverUrl, status: "password-change-required", passwordChangeToken, passwordChangeExpiresAt}` and the command returns `PASSWORD_CHANGE_REQUIRED` (exit 1).
|
|
709
|
+
That restricted record is not valid managed-session input; the caller must complete the password change through the server API and sign in again.
|
|
710
|
+
|
|
711
|
+
Only an explicit `TOKEN_EXPIRED` response permits a single refresh and retry of that request.
|
|
712
|
+
Without a refresh token, or if refresh is refused, the CLI returns the authentication error for the caller to handle.
|
|
713
|
+
Network failures and uncertain outcomes are never automatically retried.
|
|
714
|
+
After a successful refresh, fd 4 receives one newline-terminated JSON object before the request is retried:
|
|
715
|
+
|
|
716
|
+
```json
|
|
717
|
+
{"serverUrl":"https://artium.example.test","status":"authenticated","accessToken":"CANARY-new-access","refreshToken":"CANARY-new-refresh","accessExpiresAt":1800000000000,"familyExpiresAt":1800003600000}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Every rotation is delivered even if the subsequent request fails; an authenticated invocation without rotation writes no record.
|
|
721
|
+
Consume fd 4 concurrently with stdout and stderr, and retain new sessions and rotations regardless of the command's exit status.
|
|
722
|
+
If new-session or rotation delivery fails, the CLI returns `INVALID_INPUT` (exit 2); the caller must recover the session.
|
|
723
|
+
After failed rotation delivery, the CLI refuses to retry the request; do not reuse the consumed refresh token.
|
|
724
|
+
Managed public output is buffered and scanned against supplied, newly issued and rotated tokens and their canonical encodings before printing; a match withholds both buffered streams and returns `SECRET_IN_OUTPUT`.
|
|
725
|
+
SSO approval instructions are displayed before session issuance; managed password input and the private SSO device code are also checked against buffered output.
|
|
726
|
+
|
|
727
|
+
Exit codes are 0 for success, 2 for invalid input, client configuration or private channels and refused commands, and 1 for authentication, server-binding, transport or output-scanning failures.
|
|
728
|
+
Tool exit codes pass through as usual.
|
|
729
|
+
Missing or closed descriptors, invalid JSON and mismatched server bindings fail before network access; no failure falls back to a session file.
|
|
730
|
+
|
|
678
731
|
### Server settings and custody
|
|
679
732
|
|
|
680
733
|
| Variable | Meaning |
|
package/bin/artium-axi.mjs
CHANGED
|
@@ -4498,9 +4498,9 @@ var $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) =>
|
|
|
4498
4498
|
}
|
|
4499
4499
|
const _out = def.transform(payload.value, payload);
|
|
4500
4500
|
if (ctx.async) {
|
|
4501
|
-
const
|
|
4502
|
-
return
|
|
4503
|
-
payload.value =
|
|
4501
|
+
const output3 = _out instanceof Promise ? _out : Promise.resolve(_out);
|
|
4502
|
+
return output3.then((output4) => {
|
|
4503
|
+
payload.value = output4;
|
|
4504
4504
|
return payload;
|
|
4505
4505
|
});
|
|
4506
4506
|
}
|
|
@@ -4950,12 +4950,12 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
|
|
|
4950
4950
|
output: inst._def.output
|
|
4951
4951
|
});
|
|
4952
4952
|
};
|
|
4953
|
-
inst.output = (
|
|
4953
|
+
inst.output = (output3) => {
|
|
4954
4954
|
const F = inst.constructor;
|
|
4955
4955
|
return new F({
|
|
4956
4956
|
type: "function",
|
|
4957
4957
|
input: inst._def.input,
|
|
4958
|
-
output:
|
|
4958
|
+
output: output3
|
|
4959
4959
|
});
|
|
4960
4960
|
};
|
|
4961
4961
|
return inst;
|
|
@@ -18364,14 +18364,14 @@ var ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
|
|
|
18364
18364
|
payload.issues.push(util_exports.issue(_issue));
|
|
18365
18365
|
}
|
|
18366
18366
|
};
|
|
18367
|
-
const
|
|
18368
|
-
if (
|
|
18369
|
-
return
|
|
18370
|
-
payload.value =
|
|
18367
|
+
const output3 = def.transform(payload.value, payload);
|
|
18368
|
+
if (output3 instanceof Promise) {
|
|
18369
|
+
return output3.then((output4) => {
|
|
18370
|
+
payload.value = output4;
|
|
18371
18371
|
return payload;
|
|
18372
18372
|
});
|
|
18373
18373
|
}
|
|
18374
|
-
payload.value =
|
|
18374
|
+
payload.value = output3;
|
|
18375
18375
|
return payload;
|
|
18376
18376
|
};
|
|
18377
18377
|
});
|
|
@@ -20452,7 +20452,7 @@ import { rootCertificates } from "node:tls";
|
|
|
20452
20452
|
// package.json
|
|
20453
20453
|
var package_default = {
|
|
20454
20454
|
name: "artium-axi",
|
|
20455
|
-
version: "0.0.
|
|
20455
|
+
version: "0.0.4",
|
|
20456
20456
|
private: true,
|
|
20457
20457
|
license: "MIT",
|
|
20458
20458
|
type: "module",
|
|
@@ -20636,20 +20636,27 @@ var SessionStore = class {
|
|
|
20636
20636
|
// src/cli/http.ts
|
|
20637
20637
|
var DEFAULT_REQUEST_DEADLINE_MS = 9e4;
|
|
20638
20638
|
var Client = class {
|
|
20639
|
-
constructor(environment = process.env) {
|
|
20639
|
+
constructor(environment = process.env, managed2) {
|
|
20640
20640
|
this.environment = environment;
|
|
20641
|
+
this.managed = managed2;
|
|
20641
20642
|
try {
|
|
20642
20643
|
const url2 = new URL(environment.ARTIUM_AXI_URL);
|
|
20643
20644
|
if (url2.username || url2.password || url2.href.includes("?") || url2.href.includes("#") || url2.protocol !== "https:" && !(url2.protocol === "http:" && environment.ARTIUM_AXI_TEST_HTTP === "1" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) throw new Error();
|
|
20644
20645
|
this.serverUrl = url2.href.replace(/\/$/, "");
|
|
20645
|
-
this.
|
|
20646
|
+
if (!managed2) this.storedSessions = new SessionStore(this.serverUrl, sessionPath(process.platform, environment));
|
|
20646
20647
|
} catch {
|
|
20647
20648
|
throw new ArtiumError("CLIENT_CONFIG_INVALID");
|
|
20648
20649
|
}
|
|
20650
|
+
managed2?.checkServer(this.serverUrl);
|
|
20649
20651
|
}
|
|
20650
20652
|
environment;
|
|
20653
|
+
managed;
|
|
20651
20654
|
serverUrl;
|
|
20652
|
-
|
|
20655
|
+
storedSessions;
|
|
20656
|
+
get sessions() {
|
|
20657
|
+
if (!this.storedSessions) throw new ArtiumError("INVALID_INPUT", "Session-file operations are unavailable in managed mode.");
|
|
20658
|
+
return this.storedSessions;
|
|
20659
|
+
}
|
|
20653
20660
|
certificateVerified = false;
|
|
20654
20661
|
async request(path, method, input2, token) {
|
|
20655
20662
|
this.certificateVerified = false;
|
|
@@ -20719,12 +20726,18 @@ var Client = class {
|
|
|
20719
20726
|
if (result.status !== 200 && result.status !== 204) throw new ArtiumError("INVALID_RESPONSE");
|
|
20720
20727
|
if ((path === "/v1/auth/login" || path === "/v1/auth/device") && result.serverVersion && result.serverVersion !== cliVersion) {
|
|
20721
20728
|
const version2 = result.serverVersion.replace(/[\x00-\x1f\x7f]/g, "");
|
|
20722
|
-
|
|
20723
|
-
|
|
20729
|
+
const warning = `warning: CLI ${cliVersion} differs from server ${version2}; download the matching client from ${this.serverUrl}/cli/artium-axi.mjs and verify its .sha256, or run npm install -g @knowttl/artium-axi@${version2}.
|
|
20730
|
+
`;
|
|
20731
|
+
if (this.managed) this.managed.write("stderr", warning);
|
|
20732
|
+
else process.stderr.write(warning);
|
|
20724
20733
|
}
|
|
20725
20734
|
return result.body;
|
|
20726
20735
|
}
|
|
20727
20736
|
async authenticated(path, method, input2, previous) {
|
|
20737
|
+
if (this.managed) return this.managed.authenticated(
|
|
20738
|
+
(token) => this.request(path, method, input2, token),
|
|
20739
|
+
(refreshToken) => this.request("/v1/auth/refresh", "POST", { refreshToken })
|
|
20740
|
+
);
|
|
20728
20741
|
const session = previous ? this.reload(previous) : this.sessions.read();
|
|
20729
20742
|
if (!session) throw new ArtiumError("TOKEN_INVALID", void 0, "Run artium-axi login --username <username> --password-stdin.");
|
|
20730
20743
|
if (session.status === "password-change-required") throw new ArtiumError("PASSWORD_CHANGE_REQUIRED");
|
|
@@ -20808,16 +20821,18 @@ async function readPassword(stdin, environmentVariable) {
|
|
|
20808
20821
|
}
|
|
20809
20822
|
|
|
20810
20823
|
// src/cli/doctor.ts
|
|
20811
|
-
async function doctor() {
|
|
20824
|
+
async function doctor(createClient = () => new Client(), write = (text2) => {
|
|
20825
|
+
process.stdout.write(text2);
|
|
20826
|
+
}) {
|
|
20812
20827
|
let failure2;
|
|
20813
20828
|
const line = (name, pass, detail, error62) => {
|
|
20814
20829
|
if (!pass) failure2 ??= error62 ?? new ArtiumError("INVALID_RESPONSE");
|
|
20815
|
-
|
|
20830
|
+
write(`${pass ? "pass" : "fail"}: ${name}${detail ? ` (${detail})` : ""}
|
|
20816
20831
|
`);
|
|
20817
20832
|
};
|
|
20818
20833
|
let client;
|
|
20819
20834
|
try {
|
|
20820
|
-
client =
|
|
20835
|
+
client = createClient();
|
|
20821
20836
|
} catch (error62) {
|
|
20822
20837
|
for (const name of ["server address", "certificate", "token", "CLI version"]) line(name, false, "CLIENT_CONFIG_INVALID");
|
|
20823
20838
|
throw error62;
|
|
@@ -22878,7 +22893,14 @@ var commands = {
|
|
|
22878
22893
|
};
|
|
22879
22894
|
function commandHelp(command) {
|
|
22880
22895
|
const entry = commands[command];
|
|
22881
|
-
const flags = [
|
|
22896
|
+
const flags = [
|
|
22897
|
+
command === "falcon-axi" || command === "vectra-axi" || command === "ldap-axi" ? "--help before the tool name (default false)" : "--help (default false)",
|
|
22898
|
+
...entry.flags,
|
|
22899
|
+
"--managed-session before the command (default false; login and authenticated calls; session JSON on inherited pipe fd 3, new sessions and rotations as JSON lines on private pipe fd 4; no session file)",
|
|
22900
|
+
"Managed input: {serverUrl, accessToken, refreshToken?}; close fd 3 after JSON (64 KiB maximum). Output: {serverUrl, status, accessToken, refreshToken, accessExpiresAt, familyExpiresAt} per rotation, before retry, including on later failure.",
|
|
22901
|
+
"Managed login: fd 3 is unused; fd 4 must be a writable private pipe/socket. Local sign-in requires --username <username> --password-stdin with protected inherited stdin; password argv and ARTIUM_AXI_PASSWORD are refused. SSO uses login --sso and displays browser approval instructions immediately. New sessions use the rotation envelope; password-change-required returns {serverUrl, status, passwordChangeToken, passwordChangeExpiresAt} on fd 4 and exits 1.",
|
|
22902
|
+
"Managed exits: 0 success; 2 invalid JSON/descriptors or refused passwd/logout/setup; 1 authentication/transport failure; tool exit codes pass through. Only TOKEN_EXPIRED permits one refresh/retry; without refreshToken or on refusal, return control to the caller."
|
|
22903
|
+
];
|
|
22882
22904
|
return `usage: ${entry.usage}
|
|
22883
22905
|
purpose: ${entry.purpose}
|
|
22884
22906
|
flags[${flags.length}]:
|
|
@@ -33713,25 +33735,25 @@ function rewriteDiscoveryHelp(help, tool) {
|
|
|
33713
33735
|
examples: help.examples.map((example) => hostedHelp(example, tool, flags, true))
|
|
33714
33736
|
});
|
|
33715
33737
|
}
|
|
33716
|
-
function rewriteSuggestions(
|
|
33717
|
-
if (truncated) return
|
|
33738
|
+
function rewriteSuggestions(output3, tool, truncated = false) {
|
|
33739
|
+
if (truncated) return output3;
|
|
33718
33740
|
const flags = managedFlags[tool];
|
|
33719
|
-
if (!flags) return
|
|
33741
|
+
if (!flags) return output3;
|
|
33720
33742
|
const executable = new RegExp(`^(?:artium-axi |npx -y @knowttl/)?${tool}(?=\\s|$)`);
|
|
33721
|
-
if (executable.test(
|
|
33743
|
+
if (executable.test(output3.replace(/^[ \t]*(?:(?:Command|Native operation|Examples): )?/, "")) || tool === "ldap-axi" && ldapHelp.test(output3)) return hostedHelp(output3, tool, flags, true);
|
|
33722
33744
|
let spans;
|
|
33723
33745
|
let unknownOutcome = false;
|
|
33724
33746
|
try {
|
|
33725
|
-
const json2 =
|
|
33726
|
-
let parsed = json2 ? jsonHelp(
|
|
33747
|
+
const json2 = output3.trimStart().startsWith("{");
|
|
33748
|
+
let parsed = json2 ? jsonHelp(output3) : toonHelp(output3);
|
|
33727
33749
|
if (tool === "ldap-axi" && parsed.document.description === "Read Active Directory and LDAPv3 directories safely with ldap-axi (read-only)" && Array.isArray(parsed.document.commands) && parsed.spans.length) {
|
|
33728
33750
|
const home = {
|
|
33729
33751
|
...parsed.document,
|
|
33730
33752
|
commands: parsed.document.commands.filter((command) => ldapCommands.includes(command)),
|
|
33731
33753
|
help: [...parsed.spans.map((span) => span.value).filter((value) => !/^(?:Authentication auto selects|Set LDAP_AXI_URL)/.test(value) && value !== ldapCredentialGuidance), ldapCredentialGuidance]
|
|
33732
33754
|
};
|
|
33733
|
-
|
|
33734
|
-
parsed = json2 ? jsonHelp(
|
|
33755
|
+
output3 = (json2 ? JSON.stringify(home) : encode3(home)) + "\n";
|
|
33756
|
+
parsed = json2 ? jsonHelp(output3) : toonHelp(output3);
|
|
33735
33757
|
}
|
|
33736
33758
|
const { spans: suggestions, document } = parsed;
|
|
33737
33759
|
spans = suggestions;
|
|
@@ -33739,15 +33761,15 @@ function rewriteSuggestions(output2, tool, truncated = false) {
|
|
|
33739
33761
|
unknownOutcome = document?.code === "UNKNOWN_OUTCOME" || document?.error?.code === "UNKNOWN_OUTCOME";
|
|
33740
33762
|
}
|
|
33741
33763
|
} catch {
|
|
33742
|
-
return
|
|
33764
|
+
return output3;
|
|
33743
33765
|
}
|
|
33744
33766
|
for (const span of [...spans].reverse()) {
|
|
33745
33767
|
const value = hostedHelp(span.value, tool, flags, /^[ \t]*(?:Command|Native operation|Examples):/m.test(span.value));
|
|
33746
33768
|
if (value === span.value) continue;
|
|
33747
|
-
|
|
33769
|
+
output3 = output3.slice(0, span.start) + (span.quoted || tool === "ldap-axi" && /[,\r\n]/.test(value) ? JSON.stringify(value) : value) + output3.slice(span.start + span.raw.length);
|
|
33748
33770
|
}
|
|
33749
|
-
spans = (
|
|
33750
|
-
if (spans.some((span) => /\\[`'"]/.test(span.value))) return
|
|
33771
|
+
spans = (output3.trimStart().startsWith("{") ? jsonHelp(output3) : toonHelp(output3)).spans;
|
|
33772
|
+
if (spans.some((span) => /\\[`'"]/.test(span.value))) return output3;
|
|
33751
33773
|
const insertions = /* @__PURE__ */ new Map();
|
|
33752
33774
|
const removals = /* @__PURE__ */ new Map();
|
|
33753
33775
|
for (const span of spans) {
|
|
@@ -33781,28 +33803,28 @@ function rewriteSuggestions(output2, tool, truncated = false) {
|
|
|
33781
33803
|
if (!quote2 && /^(?:artium-axi |npx -y @knowttl\/)?\S+-axi(?:\s|$)/.test(command)) commandRanges.push([match.index, match.index + match[0].length]);
|
|
33782
33804
|
const wrapped = command.startsWith(`artium-axi ${tool}`);
|
|
33783
33805
|
if (!executable.test(quote2 ? command.split(quote2)[0] : command)) continue;
|
|
33784
|
-
if (quote2 && !command.includes(quote2)) return
|
|
33806
|
+
if (quote2 && !command.includes(quote2)) return output3;
|
|
33785
33807
|
const start = match.index + match[0].length - command.length;
|
|
33786
33808
|
commandRanges.push([match.index, quote2 ? start + command.indexOf(quote2) + 1 : match.index + match[0].length]);
|
|
33787
33809
|
commandEdit2(start, quote2 ? start + command.indexOf(quote2) : start + command.length, wrapped);
|
|
33788
33810
|
}
|
|
33789
33811
|
for (const match of span.value.matchAll(/(?<![^\s(])([`'"])([^\n]*?)(\1|(?=\n|$))/g)) {
|
|
33790
33812
|
if (commandRanges.some(([start, end]) => match.index >= start && match.index < end)) continue;
|
|
33791
|
-
if (!/^(?:artium-axi |npx -y @knowttl\/)?[^\s`'"]+-axi(?:\s|$)/.test(match[2])) return
|
|
33813
|
+
if (!/^(?:artium-axi |npx -y @knowttl\/)?[^\s`'"]+-axi(?:\s|$)/.test(match[2])) return output3;
|
|
33792
33814
|
const preceding = span.value.slice(0, match.index);
|
|
33793
33815
|
if (/(?:^|\n)[ \t]*Valid flags for $/.test(preceding) && match[1] === "`" && match[3] === "`") continue;
|
|
33794
|
-
if (!/(?:^|\n|\S[`'"])[ \t]*(?:(?:Run|Use|run|use|Verify with|If you're unsure, run|Check current state before trying again:|or(?:, if you're unsure, run)?)[ \t]+)?$/.test(preceding)) return
|
|
33816
|
+
if (!/(?:^|\n|\S[`'"])[ \t]*(?:(?:Run|Use|run|use|Verify with|If you're unsure, run|Check current state before trying again:|or(?:, if you're unsure, run)?)[ \t]+)?$/.test(preceding)) return output3;
|
|
33795
33817
|
const wrapped = match[2].startsWith(`artium-axi ${tool}`);
|
|
33796
33818
|
if (executable.test(match[2])) {
|
|
33797
|
-
if (!match[3]) return
|
|
33819
|
+
if (!match[3]) return output3;
|
|
33798
33820
|
commandEdit2(match.index + 1, match.index + 1 + match[2].length, wrapped);
|
|
33799
33821
|
}
|
|
33800
33822
|
}
|
|
33801
33823
|
}
|
|
33802
33824
|
for (const offset of [.../* @__PURE__ */ new Set([...insertions.keys(), ...removals.keys()])].sort((a, b) => b - a)) {
|
|
33803
|
-
|
|
33825
|
+
output3 = output3.slice(0, offset) + (insertions.get(offset) ?? "") + output3.slice(removals.get(offset) ?? offset);
|
|
33804
33826
|
}
|
|
33805
|
-
return
|
|
33827
|
+
return output3;
|
|
33806
33828
|
}
|
|
33807
33829
|
function managedFlagRanges(command, tool) {
|
|
33808
33830
|
const tokens = [...command.matchAll(/(?:[^\s;'"`]+|'[^']*'|"(?:\\.|[^"\\])*")+|;/g)];
|
|
@@ -33848,10 +33870,10 @@ function hostedHelp(help, tool, flags, commands2) {
|
|
|
33848
33870
|
(match, separator) => separator ? `; artium-axi ${tool}` : match
|
|
33849
33871
|
)).replace(new RegExp("`(?:npx -y @knowttl/)?" + tool + "(?=\\s|`)([^\\r\\n`]*`)", "g"), "`artium-axi " + tool + "$1").replace(new RegExp(`^([ ]*cat [^\\r\\n|]+ \\| )(?:npx -y @knowttl/)?${tool}(?=\\s|$)`, "gm"), `$1artium-axi ${tool}`).replace(new RegExp(`\\buse ${tool} entry search\\b`, "g"), `use artium-axi ${tool} entry search`);
|
|
33850
33872
|
}
|
|
33851
|
-
function jsonHelp(
|
|
33852
|
-
const parsed = JSON.parse(
|
|
33873
|
+
function jsonHelp(output3) {
|
|
33874
|
+
const parsed = JSON.parse(output3);
|
|
33853
33875
|
if (typeof parsed.help !== "string" && !(Array.isArray(parsed.help) && parsed.help.every((item) => typeof item === "string"))) return { spans: [], document: parsed };
|
|
33854
|
-
const tokens = [...
|
|
33876
|
+
const tokens = [...output3.matchAll(/"(?:\\[\s\S]|[^"\\])*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null|[{}\[\]:,]/g)];
|
|
33855
33877
|
const spans = [];
|
|
33856
33878
|
let index = 0;
|
|
33857
33879
|
let helpFields = 0;
|
|
@@ -33879,8 +33901,8 @@ function jsonHelp(output2) {
|
|
|
33879
33901
|
value(false, true);
|
|
33880
33902
|
return { spans: helpFields === 1 ? spans : [], document: parsed };
|
|
33881
33903
|
}
|
|
33882
|
-
function toonHelp(
|
|
33883
|
-
const lines = [...
|
|
33904
|
+
function toonHelp(output3) {
|
|
33905
|
+
const lines = [...output3.matchAll(/[^\r\n]*(?:\r\n|\n|$)/g)].filter((line) => line[0]);
|
|
33884
33906
|
const spans = [];
|
|
33885
33907
|
const validation = lines.map((line) => line[0].replace(/\r?\n$/, ""));
|
|
33886
33908
|
let helpFields = 0;
|
|
@@ -34059,15 +34081,15 @@ function claudeSettings(path, executable) {
|
|
|
34059
34081
|
if (!Array.isArray(sessions) || sessions.some((entry) => !object2(entry) || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !object2(hook)))) {
|
|
34060
34082
|
throw new ArtiumError("INVALID_INPUT", "Claude SessionStart hooks have an unsupported shape; no installation was written.");
|
|
34061
34083
|
}
|
|
34062
|
-
const
|
|
34084
|
+
const managed2 = { type: "command", command: process.execPath, args: [executable, "tools"], statusMessage: hookName };
|
|
34063
34085
|
let found = false;
|
|
34064
34086
|
const merged = sessions.map((entry) => ({ ...entry, hooks: entry.hooks.flatMap((hook) => {
|
|
34065
34087
|
if (hook.statusMessage !== hookName) return [hook];
|
|
34066
34088
|
if (found) return [];
|
|
34067
34089
|
found = true;
|
|
34068
|
-
return [
|
|
34090
|
+
return [managed2];
|
|
34069
34091
|
}) }));
|
|
34070
|
-
if (!found) merged.push({ hooks: [
|
|
34092
|
+
if (!found) merged.push({ hooks: [managed2] });
|
|
34071
34093
|
settings.hooks = { ...hooks, SessionStart: merged };
|
|
34072
34094
|
return JSON.stringify(settings, null, 2) + "\n";
|
|
34073
34095
|
}
|
|
@@ -34194,10 +34216,11 @@ async function toolInput(tool, original) {
|
|
|
34194
34216
|
|
|
34195
34217
|
// src/cli/device.ts
|
|
34196
34218
|
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
34197
|
-
async function ssoLogin(client) {
|
|
34219
|
+
async function ssoLogin(client, managed2) {
|
|
34198
34220
|
const parsed = deviceStartResponseSchema.safeParse(await client.request("/v1/auth/device", "POST", {}));
|
|
34199
34221
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
34200
34222
|
const device = parsed.data;
|
|
34223
|
+
managed2?.protect(device.deviceCode);
|
|
34201
34224
|
if (device.verificationUri !== client.serverUrl + "/device") throw new ArtiumError("INVALID_RESPONSE");
|
|
34202
34225
|
process.stdout.write(`status: approval-required
|
|
34203
34226
|
open: ${JSON.stringify(device.verificationUri)}
|
|
@@ -34211,13 +34234,16 @@ expires: ${new Date(device.expiresAt).toISOString()}
|
|
|
34211
34234
|
if (!response.success || response.data.status === "password-change-required") throw new ArtiumError("INVALID_RESPONSE");
|
|
34212
34235
|
if (response.data.status === "authenticated") {
|
|
34213
34236
|
const session = response.data;
|
|
34214
|
-
await client.
|
|
34237
|
+
if (managed2) await managed2.emit(client.serverUrl, session);
|
|
34238
|
+
else await client.sessions.locked(async () => {
|
|
34215
34239
|
client.sessions.write(session);
|
|
34216
34240
|
});
|
|
34217
|
-
|
|
34241
|
+
const message = `status: authenticated
|
|
34218
34242
|
session-ends: ${new Date(session.familyExpiresAt).toISOString()}
|
|
34219
34243
|
next: artium-axi tools
|
|
34220
|
-
|
|
34244
|
+
`;
|
|
34245
|
+
if (managed2) managed2.write("stdout", message);
|
|
34246
|
+
else process.stdout.write(message);
|
|
34221
34247
|
return;
|
|
34222
34248
|
}
|
|
34223
34249
|
} catch (error62) {
|
|
@@ -34228,10 +34254,190 @@ next: artium-axi tools
|
|
|
34228
34254
|
throw new ArtiumError("DEVICE_CODE_EXPIRED");
|
|
34229
34255
|
}
|
|
34230
34256
|
|
|
34257
|
+
// src/cli/managed-session.ts
|
|
34258
|
+
import { fstatSync, writeSync } from "node:fs";
|
|
34259
|
+
import { Socket } from "node:net";
|
|
34260
|
+
|
|
34261
|
+
// src/server/runner/scan.ts
|
|
34262
|
+
function prefixLengths(bytes) {
|
|
34263
|
+
const lengths = new Uint32Array(bytes.length);
|
|
34264
|
+
let length = 0;
|
|
34265
|
+
for (let index = 1; index < bytes.length; index++) {
|
|
34266
|
+
while (length > 0 && bytes[index] !== bytes[length]) length = lengths[length - 1];
|
|
34267
|
+
if (bytes[index] === bytes[length]) length++;
|
|
34268
|
+
lengths[index] = length;
|
|
34269
|
+
}
|
|
34270
|
+
return lengths;
|
|
34271
|
+
}
|
|
34272
|
+
var SecretScanner = class {
|
|
34273
|
+
#patterns;
|
|
34274
|
+
#positions;
|
|
34275
|
+
#matched = false;
|
|
34276
|
+
constructor(secrets) {
|
|
34277
|
+
const forms = /* @__PURE__ */ new Set();
|
|
34278
|
+
for (const secret of secrets) {
|
|
34279
|
+
if (secret.length === 0) throw new Error("Secret values must be nonempty");
|
|
34280
|
+
const bytes = Buffer.from(secret, "utf8");
|
|
34281
|
+
forms.add(secret);
|
|
34282
|
+
forms.add(bytes.toString("base64"));
|
|
34283
|
+
forms.add(bytes.toString("base64url"));
|
|
34284
|
+
forms.add(encodeURIComponent(secret));
|
|
34285
|
+
forms.add(JSON.stringify(secret).slice(1, -1));
|
|
34286
|
+
}
|
|
34287
|
+
this.#patterns = [...forms].map((form) => {
|
|
34288
|
+
const bytes = Buffer.from(form, "utf8");
|
|
34289
|
+
return { bytes, prefixes: prefixLengths(bytes) };
|
|
34290
|
+
});
|
|
34291
|
+
this.#positions = {
|
|
34292
|
+
stdout: new Uint32Array(this.#patterns.length),
|
|
34293
|
+
stderr: new Uint32Array(this.#patterns.length)
|
|
34294
|
+
};
|
|
34295
|
+
}
|
|
34296
|
+
scan(stream, chunk) {
|
|
34297
|
+
if (this.#matched) return true;
|
|
34298
|
+
const positions = this.#positions[stream];
|
|
34299
|
+
for (let index = 0; index < this.#patterns.length; index++) {
|
|
34300
|
+
const { bytes, prefixes } = this.#patterns[index];
|
|
34301
|
+
let position = positions[index];
|
|
34302
|
+
for (const byte of chunk) {
|
|
34303
|
+
while (position > 0 && byte !== bytes[position]) position = prefixes[position - 1];
|
|
34304
|
+
if (byte === bytes[position]) position++;
|
|
34305
|
+
if (position === bytes.length) {
|
|
34306
|
+
this.#matched = true;
|
|
34307
|
+
return true;
|
|
34308
|
+
}
|
|
34309
|
+
}
|
|
34310
|
+
positions[index] = position;
|
|
34311
|
+
}
|
|
34312
|
+
return false;
|
|
34313
|
+
}
|
|
34314
|
+
};
|
|
34315
|
+
|
|
34316
|
+
// src/cli/managed-session.ts
|
|
34317
|
+
var sessionSchema = external_exports.strictObject({
|
|
34318
|
+
serverUrl: external_exports.string().url(),
|
|
34319
|
+
accessToken: external_exports.string().min(1),
|
|
34320
|
+
refreshToken: external_exports.string().min(1).optional(),
|
|
34321
|
+
status: external_exports.literal("authenticated").optional(),
|
|
34322
|
+
accessExpiresAt: external_exports.number().int().optional(),
|
|
34323
|
+
familyExpiresAt: external_exports.number().int().optional()
|
|
34324
|
+
});
|
|
34325
|
+
var ManagedSession = class _ManagedSession {
|
|
34326
|
+
constructor(channel, session) {
|
|
34327
|
+
this.channel = channel;
|
|
34328
|
+
this.session = session;
|
|
34329
|
+
if (session) this.protect(session.accessToken, ...session.refreshToken ? [session.refreshToken] : []);
|
|
34330
|
+
}
|
|
34331
|
+
channel;
|
|
34332
|
+
session;
|
|
34333
|
+
secrets = [];
|
|
34334
|
+
output = { stdout: "", stderr: "" };
|
|
34335
|
+
static async read(signIn = false) {
|
|
34336
|
+
try {
|
|
34337
|
+
for (const fd of signIn ? [4] : [3, 4]) {
|
|
34338
|
+
const stat = fstatSync(fd);
|
|
34339
|
+
if (!stat.isFIFO() && !stat.isSocket()) throw new Error();
|
|
34340
|
+
}
|
|
34341
|
+
writeSync(4, Buffer.alloc(0));
|
|
34342
|
+
let session;
|
|
34343
|
+
if (!signIn) {
|
|
34344
|
+
const input2 = new Socket({ fd: 3, readable: true, writable: false });
|
|
34345
|
+
const chunks = [];
|
|
34346
|
+
let bytes = 0;
|
|
34347
|
+
for await (const chunk of input2) {
|
|
34348
|
+
bytes += chunk.length;
|
|
34349
|
+
if (bytes > 64 * 1024) {
|
|
34350
|
+
input2.destroy();
|
|
34351
|
+
throw new Error();
|
|
34352
|
+
}
|
|
34353
|
+
chunks.push(chunk);
|
|
34354
|
+
}
|
|
34355
|
+
session = sessionSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
34356
|
+
}
|
|
34357
|
+
const channel = new Socket({ fd: 4, readable: false, writable: true });
|
|
34358
|
+
channel.on("error", () => {
|
|
34359
|
+
});
|
|
34360
|
+
return new _ManagedSession(channel, session);
|
|
34361
|
+
} catch {
|
|
34362
|
+
throw new ArtiumError(
|
|
34363
|
+
"INVALID_INPUT",
|
|
34364
|
+
"Managed session input or private descriptors are invalid.",
|
|
34365
|
+
signIn ? "Supply a writable private pipe on fd 4 for the new session." : "Supply session JSON on inherited pipe fd 3 and a writable private pipe on fd 4; close fd 3 after sending JSON."
|
|
34366
|
+
);
|
|
34367
|
+
}
|
|
34368
|
+
}
|
|
34369
|
+
checkServer(serverUrl) {
|
|
34370
|
+
if (this.session && this.session.serverUrl.replace(/\/$/, "") !== serverUrl) throw new ArtiumError("SESSION_SERVER_MISMATCH");
|
|
34371
|
+
}
|
|
34372
|
+
async authenticated(request, refresh) {
|
|
34373
|
+
if (!this.session) throw new ArtiumError("TOKEN_INVALID");
|
|
34374
|
+
try {
|
|
34375
|
+
return await request(this.session.accessToken);
|
|
34376
|
+
} catch (error62) {
|
|
34377
|
+
if (!(error62 instanceof ArtiumError) || error62.code !== "TOKEN_EXPIRED" || !this.session.refreshToken) throw error62;
|
|
34378
|
+
}
|
|
34379
|
+
const parsed = loginResponseSchema.safeParse(await refresh(this.session.refreshToken));
|
|
34380
|
+
if (!parsed.success || parsed.data.status !== "authenticated" || !parsed.data.accessToken || !parsed.data.refreshToken) {
|
|
34381
|
+
throw new ArtiumError("INVALID_RESPONSE");
|
|
34382
|
+
}
|
|
34383
|
+
await this.emit(this.session.serverUrl, parsed.data);
|
|
34384
|
+
this.session = { serverUrl: this.session.serverUrl, ...parsed.data };
|
|
34385
|
+
return request(this.session.accessToken);
|
|
34386
|
+
}
|
|
34387
|
+
protect(...secrets) {
|
|
34388
|
+
this.secrets.push(...secrets.filter((secret) => secret.length > 0));
|
|
34389
|
+
}
|
|
34390
|
+
async emit(serverUrl, session) {
|
|
34391
|
+
if (session.status === "authenticated") this.protect(session.accessToken, session.refreshToken);
|
|
34392
|
+
else this.protect(session.passwordChangeToken);
|
|
34393
|
+
try {
|
|
34394
|
+
await new Promise((resolve3, reject) => {
|
|
34395
|
+
this.channel.write(`${JSON.stringify({ serverUrl, ...session })}
|
|
34396
|
+
`, (error62) => error62 ? reject(error62) : resolve3());
|
|
34397
|
+
});
|
|
34398
|
+
} catch {
|
|
34399
|
+
throw new ArtiumError(
|
|
34400
|
+
"INVALID_INPUT",
|
|
34401
|
+
"Managed session could not be returned.",
|
|
34402
|
+
"Recover the session through the caller; do not reuse a consumed refresh token."
|
|
34403
|
+
);
|
|
34404
|
+
}
|
|
34405
|
+
}
|
|
34406
|
+
write(stream, text2) {
|
|
34407
|
+
this.output[stream] += text2;
|
|
34408
|
+
}
|
|
34409
|
+
flush() {
|
|
34410
|
+
this.channel.destroy();
|
|
34411
|
+
const scanner = new SecretScanner(this.secrets);
|
|
34412
|
+
if (scanner.scan("stdout", Buffer.from(this.output.stdout)) || scanner.scan("stderr", Buffer.from(this.output.stderr))) {
|
|
34413
|
+
throw new ArtiumError("SECRET_IN_OUTPUT");
|
|
34414
|
+
}
|
|
34415
|
+
process.stdout.write(this.output.stdout);
|
|
34416
|
+
process.stderr.write(this.output.stderr);
|
|
34417
|
+
}
|
|
34418
|
+
};
|
|
34419
|
+
|
|
34231
34420
|
// src/cli/main.ts
|
|
34421
|
+
var managed;
|
|
34422
|
+
function output2(stream, text2) {
|
|
34423
|
+
if (managed) managed.write(stream, text2);
|
|
34424
|
+
else process[stream].write(text2);
|
|
34425
|
+
}
|
|
34232
34426
|
async function main(args) {
|
|
34427
|
+
const managedMode = args[0] === "--managed-session";
|
|
34428
|
+
if (managedMode) {
|
|
34429
|
+
args = args.slice(1);
|
|
34430
|
+
if (["passwd", "logout", "setup"].includes(args[0])) {
|
|
34431
|
+
throw new ArtiumError(
|
|
34432
|
+
"INVALID_INPUT",
|
|
34433
|
+
"This command is unavailable in managed session mode.",
|
|
34434
|
+
"The caller manages session persistence, password changes and revocation."
|
|
34435
|
+
);
|
|
34436
|
+
}
|
|
34437
|
+
managed = await ManagedSession.read(args[0] === "login");
|
|
34438
|
+
}
|
|
34233
34439
|
if (args.length === 1 && args[0] === "--version") {
|
|
34234
|
-
|
|
34440
|
+
output2("stdout", `${cliVersion}
|
|
34235
34441
|
`);
|
|
34236
34442
|
return;
|
|
34237
34443
|
}
|
|
@@ -34241,22 +34447,30 @@ async function main(args) {
|
|
|
34241
34447
|
const help = helpCommand(prefixHelp ? [...args, "--help"] : args, prefixHelp);
|
|
34242
34448
|
if (prefixHelp && !help) throw new ArtiumError("INVALID_INPUT", void 0, "Use artium-axi --help for commands.");
|
|
34243
34449
|
if (help) {
|
|
34244
|
-
|
|
34450
|
+
output2("stdout", commandHelp(help));
|
|
34245
34451
|
return;
|
|
34246
34452
|
}
|
|
34247
34453
|
if (command === "setup") {
|
|
34248
|
-
|
|
34454
|
+
output2("stdout", await setup(args.slice(1)));
|
|
34249
34455
|
return;
|
|
34250
34456
|
}
|
|
34251
34457
|
if (command === "doctor") {
|
|
34252
34458
|
if (args.length !== 1) throw new ArtiumError("INVALID_INPUT", void 0, "Use artium-axi doctor.");
|
|
34253
|
-
await doctor();
|
|
34459
|
+
if (managed) await doctor(() => new Client(process.env, managed), (text2) => output2("stdout", text2));
|
|
34460
|
+
else await doctor();
|
|
34254
34461
|
return;
|
|
34255
34462
|
}
|
|
34256
34463
|
if (command === "login" || command === "passwd") {
|
|
34257
34464
|
const remaining = args.slice(1);
|
|
34465
|
+
if (managed && process.env.ARTIUM_AXI_PASSWORD !== void 0) {
|
|
34466
|
+
throw new ArtiumError(
|
|
34467
|
+
"INVALID_INPUT",
|
|
34468
|
+
"Password environment values are refused in managed mode.",
|
|
34469
|
+
"Unset ARTIUM_AXI_PASSWORD; use --password-stdin for local sign-in or --sso."
|
|
34470
|
+
);
|
|
34471
|
+
}
|
|
34258
34472
|
if (command === "login" && remaining.length === 1 && remaining[0] === "--sso") {
|
|
34259
|
-
await ssoLogin(new Client());
|
|
34473
|
+
await ssoLogin(new Client(process.env, managed), managed);
|
|
34260
34474
|
return;
|
|
34261
34475
|
}
|
|
34262
34476
|
let stdin = false;
|
|
@@ -34275,24 +34489,34 @@ async function main(args) {
|
|
|
34275
34489
|
command === "login" ? "Use artium-axi login --sso or artium-axi login --username <username> --password-stdin." : "Use artium-axi passwd --password-stdin."
|
|
34276
34490
|
);
|
|
34277
34491
|
}
|
|
34278
|
-
const client2 = new Client();
|
|
34492
|
+
const client2 = new Client(process.env, managed);
|
|
34279
34493
|
if (command === "login") {
|
|
34494
|
+
if (managed && !stdin) {
|
|
34495
|
+
throw new ArtiumError(
|
|
34496
|
+
"INVALID_INPUT",
|
|
34497
|
+
"Managed login requires protected password input.",
|
|
34498
|
+
"Use --password-stdin with inherited stdin; unset ARTIUM_AXI_PASSWORD. Password arguments and environment values are refused."
|
|
34499
|
+
);
|
|
34500
|
+
}
|
|
34280
34501
|
const password = await readPassword(stdin, "ARTIUM_AXI_PASSWORD");
|
|
34281
|
-
|
|
34502
|
+
managed?.protect(password);
|
|
34503
|
+
const signIn = async () => {
|
|
34282
34504
|
const parsed = loginResponseSchema.safeParse(await client2.request("/v1/auth/login", "POST", { username, password }));
|
|
34283
34505
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
34284
|
-
client2.
|
|
34506
|
+
if (managed) await managed.emit(client2.serverUrl, parsed.data);
|
|
34507
|
+
else client2.sessions.write(parsed.data);
|
|
34285
34508
|
return parsed.data;
|
|
34286
|
-
}
|
|
34509
|
+
};
|
|
34510
|
+
const session = await (managed ? signIn() : client2.sessions.locked(signIn));
|
|
34287
34511
|
if (session.status === "password-change-required") throw new ArtiumError("PASSWORD_CHANGE_REQUIRED");
|
|
34288
|
-
|
|
34512
|
+
output2("stdout", "status: authenticated\nnext: artium-axi tools\n");
|
|
34289
34513
|
} else {
|
|
34290
34514
|
await client2.changePassword(async (session) => {
|
|
34291
34515
|
const newPassword = await readPassword(stdin);
|
|
34292
34516
|
const currentPassword = session.status === "authenticated" ? await readPassword(false, "ARTIUM_AXI_CURRENT_PASSWORD") : void 0;
|
|
34293
34517
|
return { newPassword, ...currentPassword === void 0 ? {} : { currentPassword } };
|
|
34294
34518
|
});
|
|
34295
|
-
|
|
34519
|
+
output2("stdout", "status: password-changed\nnext: artium-axi login --username <username> --password-stdin\n");
|
|
34296
34520
|
}
|
|
34297
34521
|
return;
|
|
34298
34522
|
}
|
|
@@ -34317,10 +34541,10 @@ async function main(args) {
|
|
|
34317
34541
|
return false;
|
|
34318
34542
|
}
|
|
34319
34543
|
});
|
|
34320
|
-
|
|
34544
|
+
output2("stdout", `status: logged-out
|
|
34321
34545
|
remoteRevocation: ${verified ? "verified" : "unverified"}
|
|
34322
34546
|
`);
|
|
34323
|
-
if (!verified)
|
|
34547
|
+
if (!verified) output2("stdout", "next: Remote revocation is unverified. The local tokens were removed.\n");
|
|
34324
34548
|
return;
|
|
34325
34549
|
}
|
|
34326
34550
|
const home = args.length === 0;
|
|
@@ -34331,25 +34555,25 @@ remoteRevocation: ${verified ? "verified" : "unverified"}
|
|
|
34331
34555
|
throw new ArtiumError("INVALID_INPUT", "Unknown command, argument or flag.", "Use artium-axi --help for commands or artium-axi <command> --help for valid flags.");
|
|
34332
34556
|
}
|
|
34333
34557
|
const input2 = home || listing || whoami ? void 0 : await toolInput(command, args.slice(1));
|
|
34334
|
-
const client = new Client();
|
|
34335
|
-
const homeSession = home ? client.sessions.read() : void 0;
|
|
34336
|
-
if (home && !homeSession) throw new ArtiumError("TOKEN_INVALID", void 0, "Run artium-axi login --username <username> --password-stdin.");
|
|
34558
|
+
const client = new Client(process.env, managed);
|
|
34559
|
+
const homeSession = home && !managed ? client.sessions.read() : void 0;
|
|
34560
|
+
if (home && !managed && !homeSession) throw new ArtiumError("TOKEN_INVALID", void 0, "Run artium-axi login --username <username> --password-stdin.");
|
|
34337
34561
|
const result = await client.authenticated(home || whoami ? "/v1/me" : `/v1/tools${listing ? "" : `/${command}/run`}`, home || listing || whoami ? "GET" : "POST", input2, homeSession);
|
|
34338
34562
|
if (home || whoami) {
|
|
34339
34563
|
const parsed = meResponseSchema.safeParse(result);
|
|
34340
34564
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
34341
|
-
let
|
|
34565
|
+
let view = identityView(parsed.data, client.serverUrl);
|
|
34342
34566
|
if (home) {
|
|
34343
34567
|
const tools = toolsResponseSchema.safeParse(await client.authenticated("/v1/tools", "GET", void 0, homeSession));
|
|
34344
34568
|
if (!tools.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
34345
34569
|
const executable = resolve2(process.argv[1]);
|
|
34346
34570
|
const local = relative(homedir3(), executable);
|
|
34347
34571
|
const bin = local && !isAbsolute(local) && !local.startsWith(`..${sep}`) && local !== ".." ? `~${sep}${local}` : executable;
|
|
34348
|
-
|
|
34572
|
+
view = `bin: ${JSON.stringify(bin)}
|
|
34349
34573
|
description: Run managed upstream tools through one local sign-in.
|
|
34350
|
-
${
|
|
34574
|
+
${view}${toolsView(tools.data.tools)}`;
|
|
34351
34575
|
}
|
|
34352
|
-
|
|
34576
|
+
output2("stdout", view);
|
|
34353
34577
|
} else if (listing) {
|
|
34354
34578
|
const parsed = toolsResponseSchema.safeParse(result);
|
|
34355
34579
|
if (!parsed.success) throw new ArtiumError("INVALID_RESPONSE");
|
|
@@ -34357,23 +34581,30 @@ ${output2}${toolsView(tools.data.tools)}`;
|
|
|
34357
34581
|
if (detail) {
|
|
34358
34582
|
const tool = tools.find((tool2) => tool2.name === args[2]);
|
|
34359
34583
|
if (!tool) throw new ArtiumError("TOOL_NOT_GRANTED", "The tool is not available to your account.", "Ask an administrator to check your role assignments and enabled tools.");
|
|
34360
|
-
|
|
34361
|
-
} else
|
|
34584
|
+
output2("stdout", toolView(tool));
|
|
34585
|
+
} else output2("stdout", toolsView(tools));
|
|
34362
34586
|
} else {
|
|
34363
34587
|
const parsed = runSuccessSchema.safeParse(result);
|
|
34364
34588
|
if (!parsed.success || parsed.data.source !== command) throw new ArtiumError("INVALID_RESPONSE");
|
|
34365
34589
|
const { stdout, stderr, exitCode, truncated } = parsed.data;
|
|
34366
|
-
if (truncated.stdout || truncated.stderr)
|
|
34590
|
+
if (truncated.stdout || truncated.stderr) output2("stderr", `artium-axi: output truncated (${["stdout", "stderr"].filter((stream) => truncated[stream]).join(", ")}).
|
|
34367
34591
|
`);
|
|
34368
|
-
|
|
34369
|
-
|
|
34592
|
+
output2("stdout", rewriteSuggestions(stdout, command, truncated.stdout));
|
|
34593
|
+
output2("stderr", rewriteSuggestions(stderr, command, truncated.stderr));
|
|
34370
34594
|
process.exitCode = exitCode;
|
|
34371
34595
|
}
|
|
34372
34596
|
}
|
|
34373
34597
|
function printError(response) {
|
|
34374
|
-
|
|
34598
|
+
output2("stdout", `error:
|
|
34375
34599
|
${Object.entries(response.error).map(([key, value]) => ` ${key}: ${JSON.stringify(value)}`).join("\n")}
|
|
34376
34600
|
`);
|
|
34377
34601
|
process.exitCode = errors[response.error.code].exitCode;
|
|
34378
34602
|
}
|
|
34379
|
-
main(process.argv.slice(2)).catch((error62) => printError(errorResponse(error62 instanceof ArtiumError ? error62 : new ArtiumError("INTERNAL_ERROR"), randomUUID3())))
|
|
34603
|
+
main(process.argv.slice(2)).catch((error62) => printError(errorResponse(error62 instanceof ArtiumError ? error62 : new ArtiumError("INTERNAL_ERROR"), randomUUID3()))).then(() => {
|
|
34604
|
+
try {
|
|
34605
|
+
managed?.flush();
|
|
34606
|
+
} catch {
|
|
34607
|
+
managed = void 0;
|
|
34608
|
+
printError(errorResponse(new ArtiumError("SECRET_IN_OUTPUT"), randomUUID3()));
|
|
34609
|
+
}
|
|
34610
|
+
});
|