@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 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
- // Next-step hints shared by the no-args home view and the installable
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
- // Next-step hints printed by the no-args home view. The two leading entries mirror the
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
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@knowttl/mg-axi",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
+ "license": "MIT",
4
5
  "description": "Agent-facing Microsoft Graph CLI, read-only by default",
5
6
  "type": "module",
6
7
  "bin": {