@aligndottech/cli 0.18.0 → 0.19.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.
Files changed (37) hide show
  1. package/README.md +10 -8
  2. package/dist/commands/check.d.ts.map +1 -1
  3. package/dist/commands/check.js +18 -1
  4. package/dist/commands/check.js.map +1 -1
  5. package/dist/commands/setup.js +1 -1
  6. package/dist/commands/setup.js.map +1 -1
  7. package/dist/commands/why.d.ts.map +1 -1
  8. package/dist/commands/why.js +26 -8
  9. package/dist/commands/why.js.map +1 -1
  10. package/dist/index.js +6 -4
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/hook-payload.js +34 -4
  13. package/dist/lib/hook-payload.js.map +1 -1
  14. package/dist/lib/local-db.d.ts +54 -0
  15. package/dist/lib/local-db.d.ts.map +1 -1
  16. package/dist/lib/local-db.js +180 -6
  17. package/dist/lib/local-db.js.map +1 -1
  18. package/dist/lib/local-embeddings.d.ts.map +1 -1
  19. package/dist/lib/local-embeddings.js +13 -2
  20. package/dist/lib/local-embeddings.js.map +1 -1
  21. package/dist/lib/local-gateway-client.d.ts +36 -1
  22. package/dist/lib/local-gateway-client.d.ts.map +1 -1
  23. package/dist/lib/local-gateway-client.js +96 -8
  24. package/dist/lib/local-gateway-client.js.map +1 -1
  25. package/dist/lib/local-llm.d.ts +78 -15
  26. package/dist/lib/local-llm.d.ts.map +1 -1
  27. package/dist/lib/local-llm.js +327 -88
  28. package/dist/lib/local-llm.js.map +1 -1
  29. package/dist/lib/local-relationship-classifier.d.ts +22 -4
  30. package/dist/lib/local-relationship-classifier.d.ts.map +1 -1
  31. package/dist/lib/local-relationship-classifier.js +19 -9
  32. package/dist/lib/local-relationship-classifier.js.map +1 -1
  33. package/dist/lib/usage-telemetry.d.ts +36 -1
  34. package/dist/lib/usage-telemetry.d.ts.map +1 -1
  35. package/dist/lib/usage-telemetry.js +73 -3
  36. package/dist/lib/usage-telemetry.js.map +1 -1
  37. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { type DecisionRelationship } from '@aligndottech/connector-core';
2
+ import { type LlmFailure } from './local-llm.js';
2
3
  export declare const RELATIONSHIP_TYPES: readonly ["supersedes", "conflicts_with", "contradicts", "duplicates", "clarifies", "relates", "refines", "supports", "questions", "blocks"];
3
4
  export type RelationshipType = DecisionRelationship;
4
5
  export interface ClassifiedRelationship {
@@ -7,9 +8,11 @@ export interface ClassifiedRelationship {
7
8
  reason?: string;
8
9
  }
9
10
  /**
10
- * Why a candidate edge could not be typed. Only distinctions the code can actually
11
- * make: `callChat` cannot tell a timeout from a 429 from an empty body, so there is
12
- * deliberately no `classifier_timeout`.
11
+ * Why a candidate edge could not be typed. This enum stays coarse on purpose: since
12
+ * ALI-692 `callChat` CAN tell a timeout from a 429 from an empty body, but the finer
13
+ * detail travels on the outcome's `failure` (provider, model, detail) rather than by adding
14
+ * a reason per failure mode, so presenters name the model without this union growing
15
+ * a member every time a provider invents a new way to fail.
13
16
  */
14
17
  export type ClassifierFailureReason =
15
18
  /** Nothing to call: no provider key in the environment, no local Ollama. */
@@ -27,14 +30,29 @@ export type ClassifierFailureReason =
27
30
  export type ClassificationOutcome = {
28
31
  ok: true;
29
32
  relationship: ClassifiedRelationship;
30
- } | {
33
+ }
34
+ /**
35
+ * `failure` carries WHICH provider and model failed, when one did. It is on the
36
+ * value rather than on a module getter so a presenter reads the diagnosis for the
37
+ * candidate it actually asked about - the reason `align check` could previously
38
+ * report classifier_error with no remedy attached.
39
+ */
40
+ | {
31
41
  ok: false;
32
42
  reason: ClassifierFailureReason;
43
+ failure?: LlmFailure;
33
44
  };
34
45
  interface DecisionLite {
35
46
  title: string;
36
47
  summary: string;
37
48
  }
49
+ /**
50
+ * Design constraint for any future multi-model adjudication here (ALI-692): if two
51
+ * models ever judge one question (a conflict verdict plus a second opinion, a jury),
52
+ * the pair must span model FAMILIES. A family's leniency is systematic, so same-family
53
+ * agreement is one opinion counted twice, not independent confirmation. Today exactly
54
+ * one model answers, so there is nothing to enforce - this note is the requirement.
55
+ */
38
56
  export declare function classifyRelationship(subject: DecisionLite, candidate: DecisionLite): Promise<ClassificationOutcome>;
39
57
  export {};
40
58
  //# sourceMappingURL=local-relationship-classifier.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-relationship-classifier.d.ts","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,8BAA8B,CAAC;AAOtC,eAAO,MAAM,kBAAkB,8IAAyB,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,uBAAuB;AACjC,4EAA4E;AAC1E,YAAY;AACd,sEAAsE;GACpE,kBAAkB;AACpB,sFAAsF;GACpF,wBAAwB;AAC1B;;;;GAIG;GACD,sBAAsB,CAAC;AAE3B,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,sBAAsB,CAAA;CAAE,GAClD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC;AAEnD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,YAAY,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAehC"}
1
+ {"version":3,"file":"local-relationship-classifier.d.ts","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAA2C,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAK1F,eAAO,MAAM,kBAAkB,8IAAyB,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB;AACjC,4EAA4E;AAC1E,YAAY;AACd,sEAAsE;GACpE,kBAAkB;AACpB,sFAAsF;GACpF,wBAAwB;AAC1B;;;;GAIG;GACD,sBAAsB,CAAC;AAE3B,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,sBAAsB,CAAA;CAAE;AACpD;;;;;GAKG;GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAC;IAAC,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzE,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,YAAY,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAoBhC"}
@@ -7,7 +7,7 @@
7
7
  // returned a bare null for all three causes, and the caller - unable to tell them
8
8
  // apart, or apart from "classified, no conflict" - reported `aligned`.
9
9
  import { DECISION_RELATIONSHIPS, DETERMINISTIC_TEMPERATURE, isDecisionRelationship, } from '@aligndottech/connector-core';
10
- import { callChat, getUnvettedOllamaModels, hasConfiguredProvider } from './local-llm.js';
10
+ import { callChatDetailed, hasConfiguredProvider } from './local-llm.js';
11
11
  // ALI-219: the canonical decision-graph vocabulary is the single source of truth
12
12
  // (connector-core). The local classifier must only emit types the graph accepts,
13
13
  // or a local edge is invalid on personal->org sync. Re-exported for callers/tests.
@@ -19,21 +19,31 @@ const SYSTEM_PROMPT = "You classify how decision B relates to decision A in a so
19
19
  function buildUserPrompt(a, b) {
20
20
  return `Decision A: ${a.title}. ${a.summary}\n\nDecision B: ${b.title}. ${b.summary}`;
21
21
  }
22
+ /**
23
+ * Design constraint for any future multi-model adjudication here (ALI-692): if two
24
+ * models ever judge one question (a conflict verdict plus a second opinion, a jury),
25
+ * the pair must span model FAMILIES. A family's leniency is systematic, so same-family
26
+ * agreement is one opinion counted twice, not independent confirmation. Today exactly
27
+ * one model answers, so there is nothing to enforce - this note is the requirement.
28
+ */
22
29
  export async function classifyRelationship(subject, candidate) {
23
30
  // ALI-218/219: pin the shared deterministic temperature so the same decision
24
31
  // pair types the same way every run - offline relationship detection must be
25
32
  // deterministic, and the value is the one both paths share (connector-core).
26
- const raw = await callChat(SYSTEM_PROMPT, buildUserPrompt(subject, candidate), {
33
+ const result = await callChatDetailed(SYSTEM_PROMPT, buildUserPrompt(subject, candidate), {
27
34
  temperature: DETERMINISTIC_TEMPERATURE,
28
35
  });
29
- if (!raw) {
30
- // Order matters: an unvetted local model is a more specific diagnosis than either of
31
- // the other two, and `hasConfiguredProvider()` is env-only so it cannot see it.
32
- if (getUnvettedOllamaModels())
33
- return { ok: false, reason: 'unvetted_local_model' };
34
- return { ok: false, reason: hasConfiguredProvider() ? 'classifier_error' : 'no_llm_key' };
36
+ if (!result.ok) {
37
+ // Each mapping below reads the failure the call RETURNED, so it describes this
38
+ // candidate's attempt and nothing else. `hasConfiguredProvider()` is still the
39
+ // tiebreak for no_provider, because it is env-only and so is the only thing that
40
+ // can tell "keys exist but every one was rejected" from "nothing is configured".
41
+ const reason = result.failure.kind === 'unrecognised_local_models' ? 'unvetted_local_model'
42
+ : result.failure.kind === 'provider_stopped' ? 'classifier_error'
43
+ : hasConfiguredProvider() ? 'classifier_error' : 'no_llm_key';
44
+ return { ok: false, reason, failure: result.failure };
35
45
  }
36
- const relationship = parseRelationship(raw);
46
+ const relationship = parseRelationship(result.text);
37
47
  return relationship ? { ok: true, relationship } : { ok: false, reason: 'classifier_unparseable' };
38
48
  }
39
49
  function parseRelationship(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"local-relationship-classifier.js","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-relationship-classifier.js","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,43 @@ import type { EnvironmentConfig } from './config.js';
8
8
  * contact us and have no tenant, so counting them needs an explicit consent flow and its own
9
9
  * storage (`telemetry_events.tenant_id` is `NOT NULL REFERENCES tenants(id)`).
10
10
  *
11
- * Set `ALIGN_TELEMETRY=0` to opt out entirely.
11
+ * Set `ALIGN_TELEMETRY=0` (or `false` / `no` / `off`) to opt out entirely.
12
12
  */
13
13
  export declare const TELEMETRY_TIMEOUT_MS = 2000;
14
14
  export declare function recordCommandUsage(env: EnvironmentConfig, command: string): Promise<void>;
15
+ /**
16
+ * The `--env` the user actually typed, including one Commander handed to a parent.
17
+ *
18
+ * `align import git --env local` leaves the subcommand's own `opts()` empty, because `--env`
19
+ * is declared on both and Commander resolves that in the parent's favour (align-cli#79, which
20
+ * fixed the same read for the import commands via subcommandOpts). Reading `.opts()` here
21
+ * would send a local command's event to the cloud default - this slice's own bug, one layer up.
22
+ */
23
+ export declare function envFlagOf(cmd: {
24
+ optsWithGlobals(): Record<string, unknown>;
25
+ }): string | undefined;
26
+ /**
27
+ * Resolve the env the command actually addressed, then report against THAT.
28
+ *
29
+ * The postAction hook used to hand over `getEnvironment(getDefaultEnv())`, and
30
+ * `align setup --local` deliberately leaves the default env alone (local-mode.ts), so a
31
+ * machine that had ever run `align login` reported every `ask --env local` to the cloud -
32
+ * the one thing PR #77 said local mode does not do. Only the three-member `local` command
33
+ * group was excluded, and no local user types `align local ask`.
34
+ *
35
+ * `preferLocalEmbedded` is deliberately NOT passed: it is a routing preference for the
36
+ * read commands, and asking for it here would be a second opinion on where the command
37
+ * went. The flag and ALIGN_ENV are what the user chose explicitly, and those are what a
38
+ * consent decision may rest on. Note that preference and this gate are today mutually
39
+ * exclusive by arithmetic rather than by design - the redirect fires only when the cloud env
40
+ * has no token, which is the same condition recordCommandUsage returns on - so if that token
41
+ * check is ever relaxed to count tokenless users, pass the preference here as well.
42
+ *
43
+ * `setup` is suppressed once local-embedded is configured. The interactive "Local only" choice
44
+ * sets no flag, and the default env stays cloud on purpose, so the only evidence the session was
45
+ * local is what the run left behind - and a `setup` that ends with the machine in local mode is
46
+ * a local session. It costs one activation count on a once-per-machine command, in the direction
47
+ * that cannot leak.
48
+ */
49
+ export declare function recordInvocationUsage(envFlag: string | undefined, command: string): Promise<void>;
15
50
  //# sourceMappingURL=usage-telemetry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage-telemetry.d.ts","sourceRoot":"","sources":["../../src/lib/usage-telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAE1C,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C/F"}
1
+ {"version":3,"file":"usage-telemetry.d.ts","sourceRoot":"","sources":["../../src/lib/usage-telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,OAAQ,CAAC;AAiB1C,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD/F;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE;IAAE,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CASjG;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -7,17 +7,36 @@
7
7
  * contact us and have no tenant, so counting them needs an explicit consent flow and its own
8
8
  * storage (`telemetry_events.tenant_id` is `NOT NULL REFERENCES tenants(id)`).
9
9
  *
10
- * Set `ALIGN_TELEMETRY=0` to opt out entirely.
10
+ * Set `ALIGN_TELEMETRY=0` (or `false` / `no` / `off`) to opt out entirely.
11
11
  */
12
12
  export const TELEMETRY_TIMEOUT_MS = 2_000;
13
+ /**
14
+ * Set and not recognisably ON means OFF. Enumerating the falsy words instead - `0|false|no|off` -
15
+ * guesses at what a user will type, and every guess that misses is a live send by someone who
16
+ * believes they opted out: `disabled` and `n` both sent. Trimmed, because a trailing space or
17
+ * newline comes free from a `.env` file or a here-doc. Unset is ON, which is the documented
18
+ * cloud default and the only value this cannot see.
19
+ */
20
+ const OPT_IN_VALUES = new Set(['1', 'true', 'yes', 'on']);
21
+ function telemetryOptedOut() {
22
+ const raw = process.env['ALIGN_TELEMETRY'];
23
+ if (raw === undefined || raw.trim() === '')
24
+ return false;
25
+ return !OPT_IN_VALUES.has(raw.trim().toLowerCase());
26
+ }
13
27
  export async function recordCommandUsage(env, command) {
14
- if (process.env['ALIGN_TELEMETRY'] === '0')
28
+ if (telemetryOptedOut())
15
29
  return;
16
30
  // `align local ...` is the explicitly-offline path. Its caller may still hold a cloud token
17
- // (the hook resolves the DEFAULT env, not the one the command used), so the token check below
31
+ // (the hook may resolve an env other than the one the command used), so the token check below
18
32
  // is not enough on its own.
19
33
  if (command === 'local' || command.startsWith('local '))
20
34
  return;
35
+ // The mode is the consent boundary (PR #77: cloud is opt-out, local is no-phone-home). It has
36
+ // to gate on its own because a token can be in scope without cloud consent: ALIGN_TOKEN
37
+ // exported into the shell, or a logged-in default env resolved by the caller.
38
+ if (env.mode === 'local-embedded')
39
+ return;
21
40
  if (!env.authToken || !env.tenantId)
22
41
  return;
23
42
  // A blackholing proxy hangs rather than rejecting, so a bare await would freeze the CLI after
@@ -60,4 +79,55 @@ export async function recordCommandUsage(env, command) {
60
79
  clearTimeout(timer);
61
80
  }
62
81
  }
82
+ /**
83
+ * The `--env` the user actually typed, including one Commander handed to a parent.
84
+ *
85
+ * `align import git --env local` leaves the subcommand's own `opts()` empty, because `--env`
86
+ * is declared on both and Commander resolves that in the parent's favour (align-cli#79, which
87
+ * fixed the same read for the import commands via subcommandOpts). Reading `.opts()` here
88
+ * would send a local command's event to the cloud default - this slice's own bug, one layer up.
89
+ */
90
+ export function envFlagOf(cmd) {
91
+ const opts = cmd.optsWithGlobals();
92
+ const value = opts['env'];
93
+ if (typeof value === 'string')
94
+ return value;
95
+ // `align setup --local` is how a user ENTERS local mode, and it is not spelled `--env local`,
96
+ // so reading only `env` reported the one command whose whole purpose is going private. Checked
97
+ // after `env` so an explicitly typed cloud env still wins.
98
+ if (opts['local'] === true)
99
+ return 'local';
100
+ return undefined;
101
+ }
102
+ /**
103
+ * Resolve the env the command actually addressed, then report against THAT.
104
+ *
105
+ * The postAction hook used to hand over `getEnvironment(getDefaultEnv())`, and
106
+ * `align setup --local` deliberately leaves the default env alone (local-mode.ts), so a
107
+ * machine that had ever run `align login` reported every `ask --env local` to the cloud -
108
+ * the one thing PR #77 said local mode does not do. Only the three-member `local` command
109
+ * group was excluded, and no local user types `align local ask`.
110
+ *
111
+ * `preferLocalEmbedded` is deliberately NOT passed: it is a routing preference for the
112
+ * read commands, and asking for it here would be a second opinion on where the command
113
+ * went. The flag and ALIGN_ENV are what the user chose explicitly, and those are what a
114
+ * consent decision may rest on. Note that preference and this gate are today mutually
115
+ * exclusive by arithmetic rather than by design - the redirect fires only when the cloud env
116
+ * has no token, which is the same condition recordCommandUsage returns on - so if that token
117
+ * check is ever relaxed to count tokenless users, pass the preference here as well.
118
+ *
119
+ * `setup` is suppressed once local-embedded is configured. The interactive "Local only" choice
120
+ * sets no flag, and the default env stays cloud on purpose, so the only evidence the session was
121
+ * local is what the run left behind - and a `setup` that ends with the machine in local mode is
122
+ * a local session. It costs one activation count on a once-per-machine command, in the direction
123
+ * that cannot leak.
124
+ */
125
+ export async function recordInvocationUsage(envFlag, command) {
126
+ const { createConfigStore } = await import('./config.js');
127
+ const { resolveEnv } = await import('./resolve-env.js');
128
+ const config = createConfigStore();
129
+ if (command === 'setup' && config.getEnvironment('local').mode === 'local-embedded')
130
+ return;
131
+ await recordCommandUsage(config.getEnvironment(resolveEnv(envFlag)), command);
132
+ }
63
133
  //# sourceMappingURL=usage-telemetry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage-telemetry.js","sourceRoot":"","sources":["../../src/lib/usage-telemetry.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAsB,EAAE,OAAe;IAC9E,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,GAAG;QAAE,OAAO;IACnD,4FAA4F;IAC5F,8FAA8F;IAC9F,4BAA4B;IAC5B,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO;IAChE,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO;IAE5C,8FAA8F;IAC9F,yFAAyF;IACzF,oDAAoD;IACpD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,MAAM,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAC9C,MAAM,GAAG,OAAO,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,EAAE,CAAC;IACX,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAEzB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,IAAI,CAAC;YACjB,KAAK,CAAC,GAAG,GAAG,CAAC,UAAU,mBAAmB,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,GAAG,CAAC,SAAS,EAAE;oBACxC,aAAa,EAAE,GAAG,CAAC,QAAQ;iBAC5B;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,SAAS,EAAE,aAAa;oBACxB,QAAQ,EAAE,YAAY;oBACtB,QAAQ,EAAE,KAAK;oBACf,UAAU,EAAE,EAAE,OAAO,EAAE;iBACxB,CAAC;aACH,CAAC;YACF,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,0FAA0F;QAC1F,8EAA8E;IAChF,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"usage-telemetry.js","sourceRoot":"","sources":["../../src/lib/usage-telemetry.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligndottech/cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "mcpName": "io.github.aligndottech/cli",
5
5
  "type": "module",
6
6
  "description": "Align CLI - capture decisions, check alignment, and manage connectors",