@habitat-ai/cli 0.3.1 → 0.4.1

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.
@@ -9,6 +9,6 @@ export type ExecuteHabitatOptions = Readonly<{
9
9
  * Runs one native Oclif invocation over the app-selected Habitat client.
10
10
  *
11
11
  * Oclif owns command discovery and dispatch; this boundary supplies only the
12
- * ready workspace client required by the Habitat command plugin.
12
+ * ready workspace client required by the Habitat commands.
13
13
  */
14
14
  export declare function executeHabitat({ appRoot, workspaceRoot, args, development, }: ExecuteHabitatOptions): Promise<unknown>;
@@ -1,11 +1,11 @@
1
- import { bindHabitatClient } from "@habitat-ai/plugin-cli/binding";
1
+ import { createHabitatClientForWorkspace } from "@habitat-ai/sdk";
2
2
  import { execute, settings } from "@oclif/core";
3
- import { createHabitatClientForWorkspace } from "./composition.js";
3
+ import { bindHabitatClient } from "./lib/binding.js";
4
4
  /**
5
5
  * Runs one native Oclif invocation over the app-selected Habitat client.
6
6
  *
7
7
  * Oclif owns command discovery and dispatch; this boundary supplies only the
8
- * ready workspace client required by the Habitat command plugin.
8
+ * ready workspace client required by the Habitat commands.
9
9
  */
10
10
  export async function executeHabitat({ appRoot, workspaceRoot, args, development, }) {
11
11
  settings.enableAutoTranspile = development === true;
@@ -0,0 +1,15 @@
1
+ import type { HabitatClient } from "@habitat-ai/sdk";
2
+ import { Command } from "@oclif/core";
3
+ type CheckResult = Awaited<ReturnType<HabitatClient["catalog"]["check"]>>;
4
+ /** Projects one selected Habitat catalog check into Oclif. */
5
+ export default class Check extends Command {
6
+ static description: string;
7
+ static flags: {
8
+ readonly instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ readonly owner: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ readonly rule: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ readonly runner: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<CheckResult>;
14
+ }
15
+ export {};
@@ -0,0 +1,32 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+ import { habitatClientFrom } from "../lib/binding.js";
3
+ import { writeJsonResult } from "../lib/output.js";
4
+ /** Projects one selected Habitat catalog check into Oclif. */
5
+ export default class Check extends Command {
6
+ static description = "Check resolved Habitat applications";
7
+ static flags = {
8
+ instance: Flags.string({ description: "Exact Habitat instance identity" }),
9
+ owner: Flags.string({ description: "Repository project whose applications enter the check" }),
10
+ rule: Flags.string({
11
+ description: "Habitat rule identity; repeat to select a rule set",
12
+ multiple: true,
13
+ }),
14
+ runner: Flags.string({ description: "Mechanical runner identity" }),
15
+ };
16
+ async run() {
17
+ const { flags } = await this.parse(Check);
18
+ const rules = Array.isArray(flags.rule) ? flags.rule : flags.rule ? [flags.rule] : [];
19
+ const selectors = {
20
+ ...(flags.instance !== undefined ? { instance: flags.instance } : {}),
21
+ ...(flags.owner !== undefined ? { owner: flags.owner } : {}),
22
+ ...(rules.length === 1 ? { rule: rules[0] } : {}),
23
+ ...(rules.length > 1 ? { rules } : {}),
24
+ ...(flags.runner !== undefined ? { runner: flags.runner } : {}),
25
+ };
26
+ const result = await habitatClientFrom(this.config).catalog.check(Object.keys(selectors).length === 0 ? {} : { selectors });
27
+ await writeJsonResult(result);
28
+ if (result._tag !== "Completed" || !result.ok)
29
+ this.exit(1);
30
+ return result;
31
+ }
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { HabitatClient } from "@habitat-ai/sdk";
2
+ import { Command } from "@oclif/core";
3
+ type HookResult = Awaited<ReturnType<HabitatClient["catalog"]["check"]>>;
4
+ /** Runs one bounded Habitat operation for a repository-owned local hook. */
5
+ export default class Hook extends Command {
6
+ static description: string;
7
+ static args: {
8
+ readonly name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
9
+ };
10
+ run(): Promise<HookResult>;
11
+ }
12
+ export {};
@@ -0,0 +1,25 @@
1
+ import { Args, Command } from "@oclif/core";
2
+ import { habitatClientFrom } from "../lib/binding.js";
3
+ import { writeJsonResult } from "../lib/output.js";
4
+ /** Runs one bounded Habitat operation for a repository-owned local hook. */
5
+ export default class Hook extends Command {
6
+ static description = "Run a Habitat local-hook entrypoint";
7
+ static args = {
8
+ name: Args.string({
9
+ required: true,
10
+ options: ["agent-stop"],
11
+ description: "Named Habitat hook operation",
12
+ }),
13
+ };
14
+ async run() {
15
+ await this.parse(Hook);
16
+ const result = await habitatClientFrom(this.config).catalog.check({
17
+ selectors: { runner: "habitat" },
18
+ });
19
+ if (result._tag !== "Completed" || !result.ok) {
20
+ await writeJsonResult(result);
21
+ this.exit(1);
22
+ }
23
+ return result;
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ import type { HabitatClient } from "@habitat-ai/sdk";
2
+ import { Command } from "@oclif/core";
3
+ type ResolveResult = Awaited<ReturnType<HabitatClient["catalog"]["resolve"]>>;
4
+ /** Projects current-workspace Habitat catalog resolution into Oclif. */
5
+ export default class Resolve extends Command {
6
+ static description: string;
7
+ run(): Promise<ResolveResult>;
8
+ }
9
+ export {};
@@ -0,0 +1,15 @@
1
+ import { Command } from "@oclif/core";
2
+ import { habitatClientFrom } from "../lib/binding.js";
3
+ import { writeJsonResult } from "../lib/output.js";
4
+ /** Projects current-workspace Habitat catalog resolution into Oclif. */
5
+ export default class Resolve extends Command {
6
+ static description = "Resolve the current Habitat authority catalog";
7
+ async run() {
8
+ await this.parse(Resolve);
9
+ const result = await habitatClientFrom(this.config).catalog.resolve({});
10
+ await writeJsonResult(result);
11
+ if (result._tag === "Rejected")
12
+ this.exit(1);
13
+ return result;
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ import { type GeneratorCallback, type Tree } from "@nx/devkit";
2
+ /** Initializes the installed Habitat package inside one Nx consumer. */
3
+ export default function initializeHabitat(tree: Tree): GeneratorCallback | void;
@@ -0,0 +1,10 @@
1
+ import { installPackagesTask } from "@nx/devkit";
2
+ import { initializeHabitatConsumer } from "../nx/initialization.js";
3
+ import { habitatConsumerBinding } from "../nx-generators.js";
4
+ /** Initializes the installed Habitat package inside one Nx consumer. */
5
+ export default function initializeHabitat(tree) {
6
+ const result = initializeHabitatConsumer(tree, habitatConsumerBinding);
7
+ if (!result.packageChanged)
8
+ return;
9
+ return () => installPackagesTask(tree);
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { Tree } from "@nx/devkit";
2
+ /** Removes only Habitat's named Codex hook contribution from one Nx consumer. */
3
+ export default function removeHook(tree: Tree): void;
@@ -0,0 +1,6 @@
1
+ import { removeHabitatHook } from "../nx/initialization.js";
2
+ import { habitatConsumerBinding } from "../nx-generators.js";
3
+ /** Removes only Habitat's named Codex hook contribution from one Nx consumer. */
4
+ export default function removeHook(tree) {
5
+ removeHabitatHook(tree, habitatConsumerBinding);
6
+ }
@@ -0,0 +1,10 @@
1
+ import type { HabitatClient } from "@habitat-ai/sdk";
2
+ import { type Config } from "@oclif/core";
3
+ /** Oclif load options carrying the ready Habitat client selected by the app. */
4
+ export type HabitatOclifLoadOptions = Config["options"] & {
5
+ readonly habitatClient: HabitatClient;
6
+ };
7
+ /** Adds the app-owned Habitat client to one native Oclif configuration. */
8
+ export declare function bindHabitatClient(options: Config["options"], client: HabitatClient): HabitatOclifLoadOptions;
9
+ /** Reads the ready Habitat client from the current native Oclif configuration. */
10
+ export declare function habitatClientFrom(config: Config): HabitatClient;
@@ -0,0 +1,16 @@
1
+ import { Errors } from "@oclif/core";
2
+ const HABITAT_CLIENT = "habitatClient";
3
+ /** Adds the app-owned Habitat client to one native Oclif configuration. */
4
+ export function bindHabitatClient(options, client) {
5
+ return Object.freeze({ ...options, [HABITAT_CLIENT]: client });
6
+ }
7
+ /** Reads the ready Habitat client from the current native Oclif configuration. */
8
+ export function habitatClientFrom(config) {
9
+ if (!hasHabitatClient(config.options)) {
10
+ throw new Errors.CLIError("The Habitat app did not supply its service binding.");
11
+ }
12
+ return config.options.habitatClient;
13
+ }
14
+ function hasHabitatClient(options) {
15
+ return HABITAT_CLIENT in options && options.habitatClient !== undefined;
16
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Writes one complete Habitat command result as JSON before the command returns.
3
+ *
4
+ * Awaiting the stream callback prevents Bun from ending the process while a
5
+ * large stdout write is still buffered at the Oclif process boundary. EPIPE
6
+ * remains successful because it means the downstream pipe deliberately
7
+ * stopped reading, matching Oclif's stdout behavior.
8
+ */
9
+ export declare function writeJsonResult(result: unknown): Promise<void>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Writes one complete Habitat command result as JSON before the command returns.
3
+ *
4
+ * Awaiting the stream callback prevents Bun from ending the process while a
5
+ * large stdout write is still buffered at the Oclif process boundary. EPIPE
6
+ * remains successful because it means the downstream pipe deliberately
7
+ * stopped reading, matching Oclif's stdout behavior.
8
+ */
9
+ export function writeJsonResult(result) {
10
+ return new Promise((resolve, reject) => {
11
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`, (error) => {
12
+ if (error && !isBrokenPipe(error)) {
13
+ reject(error);
14
+ return;
15
+ }
16
+ resolve();
17
+ });
18
+ });
19
+ }
20
+ function isBrokenPipe(error) {
21
+ return "code" in error && error.code === "EPIPE";
22
+ }
@@ -0,0 +1,45 @@
1
+ import { type PluginConfiguration, type Tree } from "@nx/devkit";
2
+ import { type Static, Type } from "typebox";
3
+ declare const HookGroupSchema: Type.TObject<{
4
+ _habitat: Type.TOptional<Type.TObject<{
5
+ identity: Type.TString;
6
+ revision: Type.TInteger;
7
+ }>>;
8
+ hooks: Type.TArray<Type.TUnknown>;
9
+ }>;
10
+ declare const HabitatOwnedHookGroupSchema: Type.TObject<{
11
+ _habitat: Type.TObject<{
12
+ identity: Type.TString;
13
+ revision: Type.TInteger;
14
+ }>;
15
+ hooks: Type.TArray<Type.TObject<{
16
+ type: Type.TLiteral<"command">;
17
+ command: Type.TString;
18
+ statusMessage: Type.TString;
19
+ timeout: Type.TInteger;
20
+ }>>;
21
+ }>;
22
+ type HookGroup = Static<typeof HookGroupSchema>;
23
+ type HabitatOwnedHookGroup = Static<typeof HabitatOwnedHookGroupSchema>;
24
+ /** App-owned identities and exact predecessor states consumed by native Nx initialization. */
25
+ export type HabitatConsumerBinding = Readonly<{
26
+ gritPackage: string;
27
+ hook: HabitatOwnedHookGroup;
28
+ nxPlugin: string;
29
+ predecessorHooks: readonly HookGroup[];
30
+ predecessorNxPlugins: readonly PluginConfiguration[];
31
+ }>;
32
+ /** Observable generator decision needed to schedule the consumer package manager once. */
33
+ export type HabitatInitializationResult = Readonly<{
34
+ packageChanged: boolean;
35
+ }>;
36
+ /**
37
+ * Converges one Nx consumer on the app-owned Habitat plugin, hook, and Grit trust.
38
+ *
39
+ * Every admission and compatibility decision completes before the first Tree
40
+ * write, so an incompatible consumer remains byte-for-byte unchanged.
41
+ */
42
+ export declare function initializeHabitatConsumer(tree: Tree, binding: HabitatConsumerBinding): HabitatInitializationResult;
43
+ /** Removes only Habitat's named hook group while preserving installed Nx integration. */
44
+ export declare function removeHabitatHook(tree: Tree, binding: HabitatConsumerBinding): void;
45
+ export {};
@@ -0,0 +1,201 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { readJson, readNxJson, updateNxJson, writeJson, } from "@nx/devkit";
3
+ import { Type } from "typebox";
4
+ import { Validator } from "typebox/schema";
5
+ const NX_CONFIG_PATH = "nx.json";
6
+ const PACKAGE_PATH = "package.json";
7
+ const CODEX_HOOKS_PATH = ".codex/hooks.json";
8
+ const HabitatHookMarkerSchema = Type.Object({
9
+ identity: Type.String({
10
+ minLength: 1,
11
+ description: "Stable package-owned identity for one Habitat hook contribution.",
12
+ }),
13
+ revision: Type.Integer({
14
+ minimum: 0,
15
+ description: "Monotonic payload revision for the named Habitat contribution.",
16
+ }),
17
+ }, { additionalProperties: false, description: "Habitat hook ownership marker." });
18
+ const HookGroupSchema = Type.Object({
19
+ _habitat: Type.Optional(HabitatHookMarkerSchema),
20
+ hooks: Type.Array(Type.Unknown(), {
21
+ description: "Ordered command contributions in one Codex hook group.",
22
+ }),
23
+ }, { additionalProperties: true, description: "One consumer-owned Codex hook group." });
24
+ const HabitatHookCommandSchema = Type.Object({
25
+ type: Type.Literal("command", {
26
+ description: "Codex hook handler kind owned by the Habitat initializer.",
27
+ }),
28
+ command: Type.String({
29
+ minLength: 1,
30
+ description: "Installed Habitat command executed by the Codex hook.",
31
+ }),
32
+ statusMessage: Type.String({
33
+ minLength: 1,
34
+ description: "Operator-facing status rendered while Habitat checks run.",
35
+ }),
36
+ timeout: Type.Integer({
37
+ minimum: 1,
38
+ description: "Maximum seconds allowed for the Habitat hook command.",
39
+ }),
40
+ }, { additionalProperties: false, description: "One Habitat-owned Codex hook command." });
41
+ const HabitatOwnedHookGroupSchema = Type.Object({
42
+ _habitat: HabitatHookMarkerSchema,
43
+ hooks: Type.Array(HabitatHookCommandSchema, {
44
+ minItems: 1,
45
+ description: "Commands contributed by the installed Habitat package.",
46
+ }),
47
+ }, { additionalProperties: false, description: "The named Habitat Codex hook contribution." });
48
+ const CodexHooksSchema = Type.Object({
49
+ hooks: Type.Optional(Type.Record(Type.String(), Type.Array(HookGroupSchema), {
50
+ description: "Ordered hook groups keyed by Codex event identity.",
51
+ })),
52
+ }, { additionalProperties: true, description: "Consumer-owned Codex hook configuration." });
53
+ const ConsumerPackageSchema = Type.Object({
54
+ trustedDependencies: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
55
+ description: "Package lifecycle scripts explicitly trusted by the Bun consumer.",
56
+ })),
57
+ }, { additionalProperties: true, description: "Nx consumer package metadata." });
58
+ const hooksValidator = new Validator({}, CodexHooksSchema);
59
+ const packageValidator = new Validator({}, ConsumerPackageSchema);
60
+ /**
61
+ * Converges one Nx consumer on the app-owned Habitat plugin, hook, and Grit trust.
62
+ *
63
+ * Every admission and compatibility decision completes before the first Tree
64
+ * write, so an incompatible consumer remains byte-for-byte unchanged.
65
+ */
66
+ export function initializeHabitatConsumer(tree, binding) {
67
+ const nxJson = requireNxJson(tree);
68
+ const hooks = readHooks(tree);
69
+ const packageJson = readPackage(tree);
70
+ const nxPlan = planNxInitialization(nxJson, binding);
71
+ const hookPlan = planHookInitialization(hooks, binding);
72
+ const packagePlan = planPackageInitialization(packageJson, binding.gritPackage);
73
+ if (nxPlan.changed)
74
+ updateNxJson(tree, nxPlan.value);
75
+ if (hookPlan.changed)
76
+ writeJson(tree, CODEX_HOOKS_PATH, hookPlan.value);
77
+ if (packagePlan.changed)
78
+ writeJson(tree, PACKAGE_PATH, packagePlan.value);
79
+ return { packageChanged: packagePlan.changed };
80
+ }
81
+ /** Removes only Habitat's named hook group while preserving installed Nx integration. */
82
+ export function removeHabitatHook(tree, binding) {
83
+ const hooks = readHooks(tree);
84
+ const hookPlan = planHookRemoval(hooks, binding);
85
+ if (hookPlan.changed)
86
+ writeJson(tree, CODEX_HOOKS_PATH, hookPlan.value);
87
+ }
88
+ function requireNxJson(tree) {
89
+ const nxJson = readNxJson(tree);
90
+ if (nxJson === null) {
91
+ throw new Error("Habitat initialization requires an Nx workspace with nx.json.");
92
+ }
93
+ return nxJson;
94
+ }
95
+ function readHooks(tree) {
96
+ if (!tree.exists(CODEX_HOOKS_PATH))
97
+ return { hooks: {} };
98
+ const input = readJson(tree, CODEX_HOOKS_PATH);
99
+ if (!hooksValidator.Check(input)) {
100
+ throw new Error(`${CODEX_HOOKS_PATH} is not a supported Codex hook document.`);
101
+ }
102
+ return input;
103
+ }
104
+ function readPackage(tree) {
105
+ if (!tree.exists(PACKAGE_PATH)) {
106
+ throw new Error("Habitat initialization requires an Nx workspace package.json.");
107
+ }
108
+ const input = readJson(tree, PACKAGE_PATH);
109
+ if (!packageValidator.Check(input)) {
110
+ throw new Error("package.json is not a supported Nx consumer package document.");
111
+ }
112
+ return input;
113
+ }
114
+ function planNxInitialization(nxJson, binding) {
115
+ const plugins = nxJson.plugins ?? [];
116
+ const matches = plugins.filter((plugin) => hasPluginIdentity(plugin, binding.nxPlugin) ||
117
+ binding.predecessorNxPlugins.some((predecessor) => hasPluginIdentity(plugin, typeof predecessor === "string" ? predecessor : predecessor.plugin)));
118
+ if (matches.length > 1) {
119
+ throw new Error("nx.json contains multiple Habitat Nx plugin registrations.");
120
+ }
121
+ const match = matches[0];
122
+ if (match === binding.nxPlugin)
123
+ return { changed: false, value: nxJson };
124
+ if (match !== undefined &&
125
+ !binding.predecessorNxPlugins.some((predecessor) => isDeepStrictEqual(predecessor, match))) {
126
+ throw new Error("nx.json contains an incompatible Habitat Nx plugin registration.");
127
+ }
128
+ const nextPlugins = match === undefined
129
+ ? [...plugins, binding.nxPlugin]
130
+ : plugins.map((plugin) => (plugin === match ? binding.nxPlugin : plugin));
131
+ return { changed: true, value: { ...nxJson, plugins: nextPlugins } };
132
+ }
133
+ function planHookInitialization(hooks, binding) {
134
+ const location = oneOwnedHookLocation(hooks, binding);
135
+ if (location !== undefined && isDeepStrictEqual(location.group, binding.hook)) {
136
+ return { changed: false, value: hooks };
137
+ }
138
+ if (location !== undefined &&
139
+ !binding.predecessorHooks.some((predecessor) => isDeepStrictEqual(predecessor, location.group))) {
140
+ throw new Error(`${CODEX_HOOKS_PATH} contains an incompatible Habitat hook contribution.`);
141
+ }
142
+ const events = hooks.hooks ?? {};
143
+ const stop = events.Stop ?? [];
144
+ const nextStop = location === undefined
145
+ ? [...stop, binding.hook]
146
+ : stop.map((group, index) => (index === location.index ? binding.hook : group));
147
+ return {
148
+ changed: true,
149
+ value: { ...hooks, hooks: { ...events, Stop: nextStop } },
150
+ };
151
+ }
152
+ function planHookRemoval(hooks, binding) {
153
+ const location = oneOwnedHookLocation(hooks, binding);
154
+ if (location === undefined)
155
+ return { changed: false, value: hooks };
156
+ if (!isDeepStrictEqual(location.group, binding.hook) &&
157
+ !binding.predecessorHooks.some((predecessor) => isDeepStrictEqual(predecessor, location.group))) {
158
+ throw new Error(`${CODEX_HOOKS_PATH} contains an incompatible Habitat hook contribution.`);
159
+ }
160
+ const events = hooks.hooks ?? {};
161
+ const stop = events.Stop ?? [];
162
+ return {
163
+ changed: true,
164
+ value: {
165
+ ...hooks,
166
+ hooks: { ...events, Stop: stop.filter((_group, index) => index !== location.index) },
167
+ },
168
+ };
169
+ }
170
+ function planPackageInitialization(packageJson, gritPackage) {
171
+ const trusted = packageJson.trustedDependencies ?? [];
172
+ const matches = trusted.filter((dependency) => dependency === gritPackage);
173
+ if (matches.length > 1) {
174
+ throw new Error(`package.json contains duplicate ${gritPackage} trust entries.`);
175
+ }
176
+ if (matches.length === 1)
177
+ return { changed: false, value: packageJson };
178
+ return {
179
+ changed: true,
180
+ value: { ...packageJson, trustedDependencies: [...trusted, gritPackage] },
181
+ };
182
+ }
183
+ function oneOwnedHookLocation(hooks, binding) {
184
+ const identity = binding.hook._habitat.identity;
185
+ const locations = Object.entries(hooks.hooks ?? {}).flatMap(([event, groups]) => groups.flatMap((group, index) => {
186
+ const marked = group._habitat?.identity === identity;
187
+ const predecessor = binding.predecessorHooks.some((candidate) => isDeepStrictEqual(candidate, group));
188
+ return marked || predecessor ? [{ event, group, index }] : [];
189
+ }));
190
+ if (locations.length > 1) {
191
+ throw new Error(`${CODEX_HOOKS_PATH} contains multiple Habitat hook contributions.`);
192
+ }
193
+ const location = locations[0];
194
+ if (location !== undefined && location.event !== "Stop") {
195
+ throw new Error(`${CODEX_HOOKS_PATH} contains a Habitat hook contribution outside Stop.`);
196
+ }
197
+ return location;
198
+ }
199
+ function hasPluginIdentity(plugin, identity) {
200
+ return plugin === identity || (typeof plugin === "object" && plugin.plugin === identity);
201
+ }
@@ -0,0 +1,29 @@
1
+ import type { HabitatClient } from "@habitat-ai/sdk";
2
+ import type { CreateNodes, TargetConfiguration } from "@nx/devkit";
3
+ type ResolveCatalogClient = {
4
+ readonly catalog: Pick<HabitatClient["catalog"], "resolve">;
5
+ };
6
+ type TargetInput = NonNullable<TargetConfiguration["inputs"]>[number];
7
+ /**
8
+ * Supplies the ready Habitat client for the workspace being projected by Nx.
9
+ *
10
+ * The Habitat app owns this capability because Nx plugin options are serialized
11
+ * configuration and cannot carry a client, provider, or runtime handle.
12
+ */
13
+ export type HabitatClientForWorkspace = (workspaceRoot: string) => ResolveCatalogClient | Promise<ResolveCatalogClient>;
14
+ /** App-owned runtime and provider facts required for sound target caching. */
15
+ export type HabitatNxBinding = {
16
+ readonly clientForWorkspace: HabitatClientForWorkspace;
17
+ readonly runtimeInputs: readonly [TargetInput, ...TargetInput[]];
18
+ };
19
+ /**
20
+ * Projects resolved Habitat applications and compatibility rules into native Nx targets.
21
+ *
22
+ * The factory receives the app-owned workspace client and runtime cache facts.
23
+ * It does not select providers, discover authority, execute checks, or name
24
+ * projects.
25
+ */
26
+ export declare function createHabitatNxPlugin(binding: HabitatNxBinding): Readonly<{
27
+ createNodes: CreateNodes<undefined>;
28
+ }>;
29
+ export {};