@knowttl/mg-axi 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -2
- package/dist/catalogue.js +3 -2
- package/dist/cli.js +5 -0
- package/dist/docs.js +3 -1
- package/dist/skill-hint.js +138 -0
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 knowttl and mg-axi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -294,7 +294,7 @@ Only explicit login persists credentials; silent refresh keeps refreshed credent
|
|
|
294
294
|
Access tokens are excluded from the persisted MSAL cache.
|
|
295
295
|
`auth logout` deletes the selected profile's cache and is idempotent; MSAL exposes no upstream token revocation API, so organizational revocation remains separate.
|
|
296
296
|
`auth status` reports the selected identity, Graph endpoint, secret source names or paths, and local availability without signing in or making a live request.
|
|
297
|
-
The old top-level `login` remains an alias for one minor release.
|
|
297
|
+
The old top-level `login` remains an alias for one minor release; see [Versioning and releases](#versioning-and-releases).
|
|
298
298
|
Tokens remain opaque and never appear in profile views, stdout or authentication diagnostics.
|
|
299
299
|
Credential acquisition is bound to the configured tenant/client and refreshes at a 60-second expiry margin.
|
|
300
300
|
|
|
@@ -336,7 +336,7 @@ Prefer certificates or federation over bearer client secrets, and rotate secrets
|
|
|
336
336
|
|
|
337
337
|
Federation reads the assertion file on every acquisition so projected tokens can rotate.
|
|
338
338
|
Use `--federated-token-file <absolute-path>` for a projected assertion file, or set the default `MG_AXI_TOKEN` assertion value through the environment.
|
|
339
|
-
When the token reference is the default `MG_AXI_TOKEN` env holder and that variable is unset, `AZURE_FEDERATED_TOKEN_FILE` stays accepted as a deprecated alias for one minor release with a stderr notice; `MG_AXI_TOKEN` takes precedence and the alias must name an absolute file path.
|
|
339
|
+
When the token reference is the default `MG_AXI_TOKEN` env holder and that variable is unset, `AZURE_FEDERATED_TOKEN_FILE` stays accepted as a deprecated alias for one minor release with a stderr notice; see [Versioning and releases](#versioning-and-releases). `MG_AXI_TOKEN` takes precedence and the alias must name an absolute file path.
|
|
340
340
|
References, rather than values, are stored in configuration; rotation updates the environment or file without changing the profile.
|
|
341
341
|
Application credentials remain in process memory and are reacquired at a 60-second expiry margin.
|
|
342
342
|
See [headless credential guidance](docs/headless-credentials.md) for holder examples.
|
|
@@ -358,9 +358,22 @@ corepack pnpm run docs:check
|
|
|
358
358
|
|
|
359
359
|
Tests use fixture credentials and transports without signing in or contacting real tenants.
|
|
360
360
|
The test script limits concurrency to four files.
|
|
361
|
+
Pull requests rely on local validation; there is no GitHub test CI.
|
|
362
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for isolated dependency installation, focused tests and contribution checks.
|
|
361
363
|
To use the built checkout binary on PATH, run `npm link --ignore-scripts` from the checkout root.
|
|
362
364
|
Otherwise substitute `node dist/bin/mg-axi.js` for `mg-axi` in examples.
|
|
363
365
|
|
|
364
366
|
The agent skill and capability report are generated from the command catalogue, documentation template and discovery inventory.
|
|
365
367
|
After changing their sources, run `corepack pnpm run docs:generate`, then `corepack pnpm run docs:check`.
|
|
366
368
|
The [implementation plan](PLAN.md), [operation inventory](docs/inventory.md) and [execution contract](docs/execution.md) describe design and coverage boundaries.
|
|
369
|
+
|
|
370
|
+
## Versioning and releases
|
|
371
|
+
|
|
372
|
+
During 0.x development, feature changes continue to produce patch releases under the existing release configuration.
|
|
373
|
+
A minor version is cut only intentionally.
|
|
374
|
+
The deprecated top-level `login` and `AZURE_FEDERATED_TOKEN_FILE` aliases remain supported through the next intentionally cut minor release and become eligible for removal after that release.
|
|
375
|
+
Patch releases do not expire either alias.
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT. See [LICENSE](LICENSE).
|
package/dist/catalogue.js
CHANGED
|
@@ -2875,9 +2875,10 @@ export function operationFor(leaf, version) {
|
|
|
2875
2875
|
const inventory = JSON.parse(readFileSync(new URL("../inventory/operations.json", import.meta.url), "utf8"));
|
|
2876
2876
|
return inventory.operations.find((row) => row.id === `${version}:${leaf.operation}`);
|
|
2877
2877
|
}
|
|
2878
|
-
//
|
|
2878
|
+
// Base next-step hints shared by the no-args home view and the installable
|
|
2879
2879
|
// skill, so the two cannot drift apart. home() prints them alongside live
|
|
2880
|
-
// profile status; skillDocument() in docs.ts reprints them.
|
|
2880
|
+
// profile status; skillDocument() in docs.ts reprints them. localHome() in
|
|
2881
|
+
// cli.ts may append one conditional skill install hint (see skill-hint.ts).
|
|
2881
2882
|
export const HOME_HELP = ["npx -y @knowttl/mg-axi setup", "npx -y @knowttl/mg-axi doctor", "npx -y @knowttl/mg-axi entra user list --help", "npx -y @knowttl/mg-axi entra user show --help", "npx -y @knowttl/mg-axi entra group list --help", "npx -y @knowttl/mg-axi entra group member list --help", "npx -y @knowttl/mg-axi entra application list --help", "npx -y @knowttl/mg-axi entra service-principal list --help", "npx -y @knowttl/mg-axi entra conditional-access policy list --help", "npx -y @knowttl/mg-axi api get --help"];
|
|
2882
2883
|
// A useful local status, without treating unavailable tenant summaries as zeros.
|
|
2883
2884
|
export function home() {
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { AxiError, installSessionStartHooks, runAxiCli } from "axi-sdk-js";
|
|
|
6
6
|
import { LEAVES, home, leafHelp, operationFor, resolveCommand, DESCRIPTION, TOP_LEVEL_HELP } from "./catalogue.js";
|
|
7
7
|
import { VERSION } from "./version.js";
|
|
8
8
|
import { Profiles } from "./profiles.js";
|
|
9
|
+
import { skillHint } from "./skill-hint.js";
|
|
9
10
|
import { authStatus } from "./auth-status.js";
|
|
10
11
|
import { redactSecrets } from "./redact.js";
|
|
11
12
|
import { GraphSession, MAX_CURSOR_BYTES } from "./graph-session.js";
|
|
@@ -66,6 +67,10 @@ function localHome(name, config) {
|
|
|
66
67
|
output.profile = "unavailable: no default profile selected";
|
|
67
68
|
}
|
|
68
69
|
output.help.unshift("npx -y @knowttl/mg-axi profile list", "npx -y @knowttl/mg-axi auth login --help");
|
|
70
|
+
// Point agents at the installable skill until it is installed for their host.
|
|
71
|
+
const hint = skillHint();
|
|
72
|
+
if (hint !== undefined)
|
|
73
|
+
output.help.push(hint);
|
|
69
74
|
return output;
|
|
70
75
|
}
|
|
71
76
|
// One dispatch for the hook installer: resolve the sibling hook entry point
|
package/dist/docs.js
CHANGED
|
@@ -135,9 +135,11 @@ export function skillDescription() {
|
|
|
135
135
|
const writes = skillWriteLabels();
|
|
136
136
|
return `Inspect Microsoft Entra ${areas.join(", ")} and run ${writes.length} gated writes (${writes.join(", ")}) through token-efficient TOON output.`;
|
|
137
137
|
}
|
|
138
|
-
//
|
|
138
|
+
// Base next-step hints printed by the no-args home view. The two leading entries mirror the
|
|
139
139
|
// profile/login hints localHome() in cli.ts prepends to HOME_HELP; the
|
|
140
140
|
// rest is HOME_HELP verbatim, so skill and home view cannot drift apart.
|
|
141
|
+
// localHome() may append one conditional skill install hint from skill-hint.ts
|
|
142
|
+
// when the skill is missing for the current host; the installed skill omits it.
|
|
141
143
|
export function skillHomeHints() {
|
|
142
144
|
return ["npx -y @knowttl/mg-axi profile list", "npx -y @knowttl/mg-axi auth login --help", ...HOME_HELP];
|
|
143
145
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// One-line skill install hint for the no-args home view. Detects the agent
|
|
2
|
+
// host from its documented environment marker and checks whether the mg-axi
|
|
3
|
+
// skill is already installed for that host, at user, project (working
|
|
4
|
+
// directory upwards) and system level. File-existence checks only: this never
|
|
5
|
+
// installs, writes, fails or touches the network.
|
|
6
|
+
//
|
|
7
|
+
// The check looks only at the detected host's row, so one host's copy never
|
|
8
|
+
// hides another host's hint. Each row lists every documented skill folder
|
|
9
|
+
// that host loads, verified against the real hosts:
|
|
10
|
+
// - Claude Code reads its config directory ($CLAUDE_CONFIG_DIR or ~/.claude,
|
|
11
|
+
// per its binary) plus ~/.agents/skills and .agents/skills (per its
|
|
12
|
+
// binary); project .claude/skills loads from the working directory up to
|
|
13
|
+
// the repository root (per https://code.claude.com/docs/en/skills). When
|
|
14
|
+
// the working directory is inside a linked git worktree whose root has no
|
|
15
|
+
// .claude/skills directory, Claude Code (v2.1.277+) falls back to the main
|
|
16
|
+
// checkout's project skills (per https://code.claude.com/docs/en/worktrees).
|
|
17
|
+
// - Codex reads $HOME/.agents/skills and .agents/skills (cwd to repo root)
|
|
18
|
+
// per https://learn.chatgpt.com/docs/build-skills.md, plus
|
|
19
|
+
// $CODEX_HOME/skills (default ~/.codex/skills, per its binary and the
|
|
20
|
+
// skills-CLI registry) and the documented /etc/codex/skills admin folder.
|
|
21
|
+
// - Pi reads its user/project skills directories plus the Agent Skills
|
|
22
|
+
// locations ~/.agents/skills/ and .agents/skills/ (ancestors to the repo
|
|
23
|
+
// root) per its skills.md.
|
|
24
|
+
// - OpenCode reads its config directory ($OPENCODE_CONFIG_DIR,
|
|
25
|
+
// $XDG_CONFIG_HOME or ~/.config, per its binary and CLI docs) plus
|
|
26
|
+
// ~/.claude/skills, ~/.agents/skills, .opencode/skills, .claude/skills and
|
|
27
|
+
// .agents/skills (project paths walk up to the git worktree) per
|
|
28
|
+
// https://opencode.ai/docs/skills/.
|
|
29
|
+
//
|
|
30
|
+
// Only a direct-child <folder>/mg-axi/SKILL.md counts as installed. Folders
|
|
31
|
+
// listed in host settings files (for example Pi settings.json) and recursive
|
|
32
|
+
// nested layouts are a documented limitation and are deliberately not
|
|
33
|
+
// implemented.
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { homedir } from "node:os";
|
|
36
|
+
import { dirname, join, resolve } from "node:path";
|
|
37
|
+
export const SKILL_NAME = "mg-axi";
|
|
38
|
+
export const SKILL_INSTALL_COMMAND = `npx skills add knowttl/${SKILL_NAME} --skill ${SKILL_NAME}`;
|
|
39
|
+
function skillFile(dir) {
|
|
40
|
+
return join(dir, SKILL_NAME, "SKILL.md");
|
|
41
|
+
}
|
|
42
|
+
function resolveHome(env) {
|
|
43
|
+
return env.HOME ?? env.USERPROFILE ?? homedir();
|
|
44
|
+
}
|
|
45
|
+
export function skillHostRows(env, home) {
|
|
46
|
+
const agentsUser = join(home, ".agents", "skills");
|
|
47
|
+
const agentsProject = join(".agents", "skills");
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
name: "claude-code",
|
|
51
|
+
userDirs: [join(env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), "skills"), agentsUser],
|
|
52
|
+
projectDirs: [join(".claude", "skills"), agentsProject],
|
|
53
|
+
systemDirs: [],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "codex",
|
|
57
|
+
userDirs: [join(env.CODEX_HOME ?? join(home, ".codex"), "skills"), agentsUser],
|
|
58
|
+
projectDirs: [agentsProject],
|
|
59
|
+
systemDirs: ["/etc/codex/skills"],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "pi",
|
|
63
|
+
userDirs: [join(home, ".pi", "agent", "skills"), agentsUser],
|
|
64
|
+
projectDirs: [join(".pi", "skills"), agentsProject],
|
|
65
|
+
systemDirs: [],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "opencode",
|
|
69
|
+
userDirs: [
|
|
70
|
+
join(env.OPENCODE_CONFIG_DIR ?? env.XDG_CONFIG_HOME ?? join(home, ".config"), "opencode", "skills"),
|
|
71
|
+
join(home, ".claude", "skills"),
|
|
72
|
+
agentsUser,
|
|
73
|
+
],
|
|
74
|
+
projectDirs: [join(".opencode", "skills"), join(".claude", "skills"), agentsProject],
|
|
75
|
+
systemDirs: [],
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
// Marker variables each host sets for its own child processes, verified
|
|
80
|
+
// against the real hosts: CLAUDECODE (Claude Code docs), CODEX_THREAD_ID /
|
|
81
|
+
// CODEX_SESSION_ID (codex shell_environment.rs), PI_CODING_AGENT (pi docs),
|
|
82
|
+
// OPENCODE (opencode startup middleware, inherited by its shell children).
|
|
83
|
+
export function detectSkillHost(env, home) {
|
|
84
|
+
const rows = skillHostRows(env, home);
|
|
85
|
+
if (env.CLAUDECODE !== undefined)
|
|
86
|
+
return rows.find(row => row.name === "claude-code");
|
|
87
|
+
if (env.CODEX_THREAD_ID !== undefined || env.CODEX_SESSION_ID !== undefined)
|
|
88
|
+
return rows.find(row => row.name === "codex");
|
|
89
|
+
if (env.PI_CODING_AGENT !== undefined)
|
|
90
|
+
return rows.find(row => row.name === "pi");
|
|
91
|
+
if (env.OPENCODE !== undefined)
|
|
92
|
+
return rows.find(row => row.name === "opencode");
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
// Main-checkout fallback for a linked git worktree: <worktree>/.git is a file
|
|
96
|
+
// reading "gitdir: <main>/.git/worktrees/<name>". Applies only when the
|
|
97
|
+
// worktree root has no .claude/skills directory of its own.
|
|
98
|
+
function claudeWorktreeSkill(fromDir) {
|
|
99
|
+
for (let dir = resolve(fromDir);; dir = dirname(dir)) {
|
|
100
|
+
let gitdir;
|
|
101
|
+
try {
|
|
102
|
+
const text = readFileSync(join(dir, ".git"), "utf8");
|
|
103
|
+
const prefix = "gitdir: ";
|
|
104
|
+
if (text.startsWith(prefix))
|
|
105
|
+
gitdir = resolve(dir, text.slice(prefix.length).trim());
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
gitdir = undefined;
|
|
109
|
+
}
|
|
110
|
+
if (gitdir !== undefined) {
|
|
111
|
+
const splitAt = gitdir.replace(/\\/g, "/").indexOf("/.git/worktrees/");
|
|
112
|
+
if (splitAt === -1 || existsSync(join(dir, ".claude", "skills")))
|
|
113
|
+
return false;
|
|
114
|
+
return existsSync(skillFile(join(gitdir.slice(0, splitAt), ".claude", "skills")));
|
|
115
|
+
}
|
|
116
|
+
if (existsSync(join(dir, ".git")) || dirname(dir) === dir)
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function skillInstalled(row, fromDir) {
|
|
121
|
+
if ([...row.userDirs, ...row.systemDirs].some(dir => existsSync(skillFile(dir))))
|
|
122
|
+
return true;
|
|
123
|
+
for (let dir = resolve(fromDir);; dir = dirname(dir)) {
|
|
124
|
+
if (row.projectDirs.some(projectDir => existsSync(skillFile(join(dir, projectDir)))))
|
|
125
|
+
return true;
|
|
126
|
+
if (dirname(dir) === dir)
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (row.name === "claude-code")
|
|
130
|
+
return claudeWorktreeSkill(fromDir);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
export function skillHint(env = process.env, fromDir = process.cwd()) {
|
|
134
|
+
const host = detectSkillHost(env, resolveHome(env));
|
|
135
|
+
if (host === undefined || skillInstalled(host, fromDir))
|
|
136
|
+
return undefined;
|
|
137
|
+
return SKILL_INSTALL_COMMAND;
|
|
138
|
+
}
|