anyray-connect 0.1.0 → 0.3.0

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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `anyray-connect status` — read-only snapshot of the current setup.
3
+ *
4
+ * Prints which tools are pointed at Anyray (via each adapter's `pointsAt` /
5
+ * `detect`), the active gateway origin, the cached name/team from ~/.anyray, and
6
+ * the dev's content-free savings so far. Writes nothing.
7
+ *
8
+ * PRIVACY: reads metadata only (gateway origin, cached identity, content-free
9
+ * savings aggregates) — never prompt/response content.
10
+ */
11
+ import { REGISTRY } from '../tools/index.js';
12
+ import { loadProfile } from '../util/persist.js';
13
+ import { resolveGatewayOrigin, requestKey } from './shared.js';
14
+ import { fetchSavings, formatSavingsLine } from '../util/savings.js';
15
+ import { bold, cyan, dim, heading, info, note, ok } from '../util/log.js';
16
+ export const runStatus = async (args) => {
17
+ const profile = await loadProfile();
18
+ let origin;
19
+ try {
20
+ origin = resolveGatewayOrigin(args.gateway, profile);
21
+ }
22
+ catch (error) {
23
+ info(bold('Anyray connect — status'));
24
+ note(`Gateway: ${error.message}`);
25
+ return 0;
26
+ }
27
+ info(bold('Anyray connect — status'));
28
+ heading('Gateway');
29
+ info(` Origin: ${cyan(origin)}`);
30
+ if (profile.gateway) {
31
+ note(` (cached in ~/.anyray)`);
32
+ }
33
+ else if (process.env.ANYRAY_GATEWAY_URL) {
34
+ note(` (from $ANYRAY_GATEWAY_URL)`);
35
+ }
36
+ else if (!args.gateway) {
37
+ note(` (default — not yet connected; run \`anyray-connect\` to set up)`);
38
+ }
39
+ heading('Identity');
40
+ info(` Name: ${profile.name ?? dim('(not set)')}`);
41
+ info(` Team: ${profile.team ?? dim('(not set)')}`);
42
+ heading('Tools');
43
+ for (const adapter of REGISTRY) {
44
+ const detected = await adapter.detect();
45
+ if (!detected.installed) {
46
+ note(` ${adapter.title}: not installed`);
47
+ continue;
48
+ }
49
+ if (!adapter.pointsAt) {
50
+ note(` ${adapter.title}: installed ${dim('(status not introspectable)')}`);
51
+ continue;
52
+ }
53
+ const pointed = await adapter.pointsAt(origin);
54
+ if (pointed) {
55
+ ok(` ${adapter.title}: pointed at Anyray`);
56
+ }
57
+ else {
58
+ note(` ${adapter.title}: installed, not pointed at this gateway`);
59
+ }
60
+ }
61
+ // Savings: best-effort. An older gateway (404) or a network hiccup just omits
62
+ // the line — status must never fail because the endpoint is missing.
63
+ const key = requestKey(profile.clientKey, 'anyray-placeholder');
64
+ const savings = await fetchSavings(origin, key);
65
+ if (savings) {
66
+ heading('Savings');
67
+ info(` ${formatSavingsLine(savings)}`);
68
+ }
69
+ return 0;
70
+ };
71
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `anyray-connect sync` — pull the team's shared skills/instructions from the
3
+ * gateway and write them as `SKILL.md` files into each supported tool's skills
4
+ * directory. Only files whose version changed are rewritten; the per-skill
5
+ * version baseline lives in ~/.anyray.
6
+ *
7
+ * PRIVACY: skill bodies are content-free TEAM POLICY text (instructions), not
8
+ * user prompts — persisting them is intended. Nothing here logs prompt/response
9
+ * content, and ~/.anyray keeps only ids + version strings, never bodies.
10
+ */
11
+ import { REGISTRY } from '../tools/index.js';
12
+ import { loadProfile, saveProfile } from '../util/persist.js';
13
+ import { fetchTeamPolicy, syncPolicyToDirs } from '../util/policySync.js';
14
+ import { resolveGatewayOrigin, requestKey } from './shared.js';
15
+ import { bold, cyan, dim, fail, heading, info, note, ok } from '../util/log.js';
16
+ /** Skills dirs of every adapter that models one (e.g. Claude Code). */
17
+ const collectSkillsDirs = () => {
18
+ const dirs = [];
19
+ for (const adapter of REGISTRY) {
20
+ const dir = adapter.skillsDir?.();
21
+ if (dir)
22
+ dirs.push(dir);
23
+ }
24
+ return dirs;
25
+ };
26
+ export const runSync = async (args) => {
27
+ const profile = await loadProfile();
28
+ info(bold('Anyray connect — sync'));
29
+ let origin;
30
+ try {
31
+ origin = resolveGatewayOrigin(args.gateway, profile);
32
+ }
33
+ catch (error) {
34
+ fail(error.message);
35
+ return 1;
36
+ }
37
+ note(`Gateway: ${origin}`);
38
+ const skillsDirs = collectSkillsDirs();
39
+ if (skillsDirs.length === 0) {
40
+ warnNoDirs();
41
+ return 0;
42
+ }
43
+ const key = requestKey(profile.clientKey, 'anyray-placeholder');
44
+ let policy;
45
+ try {
46
+ policy = await fetchTeamPolicy(origin, key);
47
+ }
48
+ catch (error) {
49
+ // Graceful: a non-200/network error prints the reason and exits non-zero,
50
+ // but never crashes.
51
+ fail(error.message);
52
+ return 1;
53
+ }
54
+ if (policy.skills.length === 0) {
55
+ note('No team skills published yet — nothing to sync.');
56
+ return 0;
57
+ }
58
+ const summary = await syncPolicyToDirs(policy, skillsDirs, profile.syncedSkills ?? {});
59
+ heading('Skills');
60
+ for (const result of summary.results) {
61
+ if (result.written) {
62
+ ok(` ${result.name} ${dim(`(${result.id}@${result.version})`)} → ${result.files.length} file(s)`);
63
+ }
64
+ else {
65
+ note(` ${result.name} ${dim(`(${result.id}@${result.version})`)} — up to date`);
66
+ }
67
+ }
68
+ // Persist the new version baseline (preserving the rest of the profile) so
69
+ // the next sync only rewrites what changed.
70
+ try {
71
+ await saveProfile({
72
+ ...profile,
73
+ syncedSkills: summary.versions,
74
+ syncedAt: summary.updatedAt ?? new Date().toISOString(),
75
+ });
76
+ }
77
+ catch (error) {
78
+ note(`Could not record sync state: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ heading('Summary');
81
+ info(` ${cyan(String(summary.changed))} updated, ${summary.unchanged} unchanged, across ${skillsDirs.length} tool dir(s).`);
82
+ return 0;
83
+ };
84
+ const warnNoDirs = () => {
85
+ note('No installed tool exposes a skills directory — nothing to sync.');
86
+ };
87
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,12 @@
5
5
  * `ANTHROPIC_BASE_URL`, a bearer token via `ANTHROPIC_AUTH_TOKEN`, and extra
6
6
  * request headers via `ANTHROPIC_CUSTOM_HEADERS`. We point the base URL at the
7
7
  * gateway and stamp `x-anyray-metadata` for content-free spend attribution. We
8
- * also pin `x-anyray-provider: anthropic` so it works even before an org-wide
9
- * default provider is configured.
8
+ * deliberately do NOT pin `x-anyray-provider` in placeholder mode the
9
+ * gateway's admin-configured routing decides the provider. Pinning `anthropic`
10
+ * here used to override that routing (e.g. an org routing everything to
11
+ * Bedrock), forwarding the placeholder key to Anthropic where every request
12
+ * 401s. Subscription mode is the one exception (see below): the seat OAuth
13
+ * token is only valid at Anthropic, so passthrough pins it.
10
14
  *
11
15
  * Auth depends on the target's `authMode`:
12
16
  * - `placeholder` (default): send the dev's personal gateway key
@@ -32,8 +36,14 @@ const OWNED_KEYS = [
32
36
  'ANTHROPIC_CUSTOM_HEADERS',
33
37
  ];
34
38
  const customHeaders = (meta, subscription, clientKey) => {
35
- const lines = ['x-anyray-provider: anthropic'];
39
+ // No provider pin in placeholder mode: the gateway's routing config owns
40
+ // that decision (see the module comment).
41
+ const lines = [];
36
42
  if (subscription) {
43
+ // Seat OAuth tokens are only valid against Anthropic — routing the
44
+ // passthrough anywhere else is a guaranteed failure, so the pin here is
45
+ // part of the passthrough contract, not a routing choice.
46
+ lines.push('x-anyray-provider: anthropic');
37
47
  lines.push('x-anyray-auth-mode: passthrough');
38
48
  // The seat OAuth token owns Authorization, so the personal gateway key
39
49
  // rides its own header for the gateway to identify the dev.
@@ -60,9 +70,10 @@ export const claudeCode = {
60
70
  const path = settingsPath();
61
71
  const subscription = target.authMode === 'subscription';
62
72
  const meta = metadataHeaderValue(target.metadata);
73
+ const headers = customHeaders(meta, subscription, target.clientKey);
63
74
  const env = {
64
75
  ANTHROPIC_BASE_URL: target.anthropicBaseUrl,
65
- ANTHROPIC_CUSTOM_HEADERS: customHeaders(meta, subscription, target.clientKey),
76
+ ...(headers ? { ANTHROPIC_CUSTOM_HEADERS: headers } : {}),
66
77
  // Placeholder mode sends the dev's personal gateway key when one was
67
78
  // issued, else the shared placeholder, for the gateway to swap;
68
79
  // subscription mode must NOT set one — any ANTHROPIC_AUTH_TOKEN would
@@ -89,6 +100,12 @@ export const claudeCode = {
89
100
  const settings = await readJson(path);
90
101
  const existingEnv = settings.env ?? {};
91
102
  settings.env = { ...existingEnv, ...env };
103
+ // With nothing to send, drop the key entirely — a stale value left by an
104
+ // older connect (which pinned x-anyray-provider) would keep overriding the
105
+ // org's routing.
106
+ if (!headers) {
107
+ delete settings.env.ANTHROPIC_CUSTOM_HEADERS;
108
+ }
92
109
  if (subscription) {
93
110
  // A leftover token (ours or hand-set) would defeat seat auth — drop it.
94
111
  if (existingEnv.ANTHROPIC_AUTH_TOKEN) {
@@ -99,6 +116,23 @@ export const claudeCode = {
99
116
  await writeJson(path, settings);
100
117
  return { status: 'configured', messages, changedFiles: [path] };
101
118
  },
119
+ skillsDir() {
120
+ return join(homedir(), '.claude', 'skills');
121
+ },
122
+ async pointsAt(origin) {
123
+ const path = settingsPath();
124
+ if (!(await fileExists(path)))
125
+ return false;
126
+ try {
127
+ const settings = await readJson(path);
128
+ const env = settings.env;
129
+ const base = env?.ANTHROPIC_BASE_URL;
130
+ return typeof base === 'string' && base.replace(/\/+$/, '') === origin;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ },
102
136
  async revert(opts) {
103
137
  const path = settingsPath();
104
138
  if (!(await fileExists(path))) {
@@ -1 +1 @@
1
- {"version":3,"file":"claudeCode.js","sourceRoot":"","sources":["../../src/tools/claudeCode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,YAAY,GAAG,GAAW,EAAE,CAChC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;AAE9C,MAAM,UAAU,GAAG;IACjB,oBAAoB;IACpB,sBAAsB;IACtB,0BAA0B;CAClB,CAAC;AAEX,MAAM,aAAa,GAAG,CACpB,IAAwB,EACxB,YAAqB,EACrB,SAA6B,EACrB,EAAE;IACV,MAAM,KAAK,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC/C,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAC9C,uEAAuE;QACvE,4DAA4D;QAC5D,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAgB;IACrC,EAAE,EAAE,aAAa;IACjB,KAAK,EAAE,aAAa;IACpB,SAAS,EAAE,IAAI;IAEf,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO;YACL,SAAS;YACT,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,wBAAwB;SAC9D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CACT,MAAqB,EACrB,IAAkB;QAElB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,KAAK,cAAc,CAAC;QACxD,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,GAAG,GAA2B;YAClC,kBAAkB,EAAE,MAAM,CAAC,gBAAgB;YAC3C,wBAAwB,EAAE,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC;YAC7E,qEAAqE;YACrE,gEAAgE;YAChE,sEAAsE;YACtE,qEAAqE;YACrE,mEAAmE;YACnE,GAAG,CAAC,YAAY;gBACd,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;SACzE,CAAC;QAEF,MAAM,QAAQ,GAAG;YACf,GAAG,IAAI,+BAA+B,MAAM,CAAC,gBAAgB,EAAE;YAC/D,YAAY;gBACV,CAAC,CAAC,qHACE,MAAM,CAAC,SAAS;oBACd,CAAC,CAAC,oDAAoD;oBACtD,CAAC,CAAC,EACN,EAAE;gBACJ,CAAC,CAAC,MAAM,CAAC,SAAS;oBAChB,CAAC,CAAC,oEAAoE;oBACtE,CAAC,CAAC,gEAAgE;YACtE,gBAAgB,IAAI,IAAI,QAAQ,EAAE;YAClC,oEAAoE;SACrE,CAAC;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;QAE3D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,WAAW,GACd,QAAQ,CAAC,GAA0C,IAAI,EAAE,CAAC;QAC7D,QAAQ,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,EAAE,GAAG,GAAG,EAAE,CAAC;QAC1C,IAAI,YAAY,EAAE,CAAC;YACjB,wEAAwE;YACxE,IAAI,WAAW,CAAC,oBAAoB,EAAE,CAAC;gBACrC,QAAQ,CAAC,IAAI,CACX,iFAAiF,CAClF,CAAC;YACJ,CAAC;YACD,OAAQ,QAAQ,CAAC,GAA8B,CAAC,oBAAoB,CAAC;QACvE,CAAC;QACD,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEhC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAkB;QAC7B,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC9B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAClE,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO;gBACL,MAAM,EAAE,UAAU;gBAClB,QAAQ,EAAE,CAAC,iCAAiC,IAAI,EAAE,CAAC;aACpD,CAAC;QACJ,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAyC,CAAC;QAC/D,IAAI,GAAG,EAAE,CAAC;YACR,KAAK,MAAM,GAAG,IAAI,UAAU;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,QAAQ,CAAC,GAAG,CAAC;YACvD,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,QAAQ,GAAG,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;QACtD,wEAAwE;QACxE,kEAAkE;QAClE,IAAI,MAAM,UAAU,CAAC,GAAG,IAAI,aAAa,CAAC,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,wBAAwB,IAAI,0CAA0C,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;IAChE,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"claudeCode.js","sourceRoot":"","sources":["../../src/tools/claudeCode.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,QAA6B,EACT,EAAE;IACtB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAkHH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,QAA6B,EACT,EAAE;IACtB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC"}
@@ -1,11 +1,35 @@
1
- import { writeFile, readFile, chmod } from 'node:fs/promises';
1
+ import { writeFile, readFile, chmod, stat } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
- const profilePath = () => join(homedir(), '.anyray');
5
- /** Read ~/.anyray; returns {} on missing or corrupt file. */
4
+ // Resolved per-call (not cached) so a changed $HOME is always honored.
5
+ const stateDir = () => join(homedir(), '.anyray');
6
+ /**
7
+ * Where the connect profile lives.
8
+ *
9
+ * Historically this is the file `~/.anyray`. But a locally-run Anyray gateway
10
+ * uses `~/.anyray/` as its DATA DIRECTORY (audit logs etc.), so on a machine
11
+ * that also runs the gateway, `~/.anyray` is a directory and writing it as a
12
+ * file fails with EISDIR. Resolve the collision: when `~/.anyray` is a
13
+ * directory, store the profile as a file inside it; otherwise use the simple
14
+ * `~/.anyray` file. The two states are mutually exclusive (a path is a file or
15
+ * a directory, never both), so no migration is needed.
16
+ */
17
+ const profilePath = async () => {
18
+ const dir = stateDir();
19
+ try {
20
+ if ((await stat(dir)).isDirectory()) {
21
+ return join(dir, 'connect.json');
22
+ }
23
+ }
24
+ catch {
25
+ /* missing → use the simple ~/.anyray file path */
26
+ }
27
+ return dir;
28
+ };
29
+ /** Read the connect profile; returns {} on missing or corrupt file. */
6
30
  export const loadProfile = async () => {
7
31
  try {
8
- const raw = await readFile(profilePath(), 'utf-8');
32
+ const raw = await readFile(await profilePath(), 'utf-8');
9
33
  const parsed = JSON.parse(raw);
10
34
  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
11
35
  return {};
@@ -15,13 +39,14 @@ export const loadProfile = async () => {
15
39
  return {};
16
40
  }
17
41
  };
18
- /** Write ~/.anyray (mode 600) — replaced, not merged, so cleared values stay cleared. */
42
+ /** Write the connect profile (mode 600) — replaced, not merged, so cleared values stay cleared. */
19
43
  export const saveProfile = async (profile) => {
20
44
  const clean = Object.fromEntries(Object.entries(profile).filter(([, v]) => v !== undefined));
21
- await writeFile(profilePath(), `${JSON.stringify(clean, null, 2)}\n`, {
45
+ const path = await profilePath();
46
+ await writeFile(path, `${JSON.stringify(clean, null, 2)}\n`, {
22
47
  mode: 0o600,
23
48
  });
24
49
  // mode only applies on creation — tighten a pre-existing file too.
25
- await chmod(profilePath(), 0o600);
50
+ await chmod(path, 0o600);
26
51
  };
27
52
  //# sourceMappingURL=persist.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/util/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAE7D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAA4B,EAAE;IAC5D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtF,OAAO,MAAuB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,yFAAyF;AACzF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAiB,EAAE;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAC1C,CAAC;IACnB,MAAM,SAAS,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QACpE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,mEAAmE;IACnE,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC,CAAC"}
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/util/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA0BjC,uEAAuE;AACvE,MAAM,QAAQ,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,GAAG,KAAK,IAAqB,EAAE;IAC9C,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAA4B,EAAE;IAC5D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtF,OAAO,MAAuB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,mGAAmG;AACnG,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAiB,EAAE;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAC1C,CAAC;IACnB,MAAM,IAAI,GAAG,MAAM,WAAW,EAAE,CAAC;IACjC,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC3D,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,mEAAmE;IACnE,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC,CAAC"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Team skills/policy sync (client side).
3
+ *
4
+ * `anyray-connect sync` pulls the org's shared skills/instructions from the
5
+ * gateway and writes them as `SKILL.md` files into each supported coding tool's
6
+ * skills directory (e.g. Claude Code `~/.claude/skills/<id>/SKILL.md`). This is
7
+ * content-free TEAM POLICY text (instructions the org wants its tools to
8
+ * follow) — it is NOT user prompt/response content, so writing it to disk is
9
+ * fine and does not touch the privacy invariant.
10
+ *
11
+ * Contract (gateway-side): `GET <gatewayUrl>/connect/policy` with header
12
+ * `x-anyray-api-key: <personal key or placeholder>` → 200
13
+ * { "skills": [ { "id", "name", "version", "body" } ], "updatedAt": "ISO" }
14
+ *
15
+ * On a non-200 or a network error we fail gracefully with a clear message
16
+ * (never crash). The last-synced version per skill is tracked in ~/.anyray so
17
+ * re-runs only rewrite files whose `version` changed.
18
+ */
19
+ import { mkdir, writeFile } from 'node:fs/promises';
20
+ import { join } from 'node:path';
21
+ const POLICY_TIMEOUT_MS = 10_000;
22
+ const optionalString = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
23
+ /**
24
+ * A skill `id` becomes a directory name (`<skillsDir>/<id>/SKILL.md`). The
25
+ * gateway only validates it as non-empty, so a malicious or mistyped id like
26
+ * `../../evil` would escape the skills dir and write SKILL.md anywhere. Restrict
27
+ * it to a single safe path segment: a leading alphanumeric, then alphanumerics,
28
+ * `.`, `_`, `-` — which excludes `/`, `\`, `..`, and absolute paths.
29
+ */
30
+ const isSafeSkillId = (id) => /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(id) && id !== '.' && id !== '..';
31
+ /**
32
+ * Fetch the team policy from the gateway. Throws an Error with a clear,
33
+ * content-free message on non-200 or network failure — callers catch it and
34
+ * print it (no crash).
35
+ */
36
+ export const fetchTeamPolicy = async (gatewayUrl, apiKey, opts = {}) => {
37
+ const timeoutMs = opts.timeoutMs ?? POLICY_TIMEOUT_MS;
38
+ const url = `${gatewayUrl}/connect/policy`;
39
+ const controller = new AbortController();
40
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
41
+ let res;
42
+ try {
43
+ res = await fetch(url, {
44
+ method: 'GET',
45
+ headers: { 'x-anyray-api-key': apiKey },
46
+ signal: controller.signal,
47
+ });
48
+ }
49
+ catch (error) {
50
+ const reason = error instanceof Error && error.name === 'AbortError'
51
+ ? `no response within ${timeoutMs}ms`
52
+ : error instanceof Error
53
+ ? error.message
54
+ : String(error);
55
+ throw new Error(`could not reach the gateway at ${url}: ${reason}`);
56
+ }
57
+ finally {
58
+ clearTimeout(timer);
59
+ }
60
+ if (!res.ok) {
61
+ throw new Error(`team policy fetch failed: the gateway responded ${res.status} at ${url}`);
62
+ }
63
+ let body;
64
+ try {
65
+ body = (await res.json());
66
+ }
67
+ catch {
68
+ throw new Error('team policy fetch failed: the gateway returned a malformed response');
69
+ }
70
+ const rawSkills = Array.isArray(body.skills) ? body.skills : [];
71
+ const skills = [];
72
+ for (const raw of rawSkills) {
73
+ const entry = raw;
74
+ const id = optionalString(entry?.id);
75
+ const version = optionalString(entry?.version);
76
+ // Skip entries that can't be keyed/diffed; an id+version are required for
77
+ // the SKILL.md path and the version diff. Reject ids that aren't a safe path
78
+ // segment — they'd let a hostile policy write outside the skills dir.
79
+ if (!id || !version || !isSafeSkillId(id))
80
+ continue;
81
+ skills.push({
82
+ id,
83
+ name: optionalString(entry?.name) ?? id,
84
+ version,
85
+ body: typeof entry?.body === 'string' ? entry.body : '',
86
+ });
87
+ }
88
+ return { skills, updatedAt: optionalString(body.updatedAt) };
89
+ };
90
+ /**
91
+ * Diff the fetched policy against the previously synced versions and write
92
+ * `SKILL.md` for every skill whose version changed into each provided skills
93
+ * dir. Returns a content-free summary plus the new version baseline to persist.
94
+ *
95
+ * Pure I/O over the inputs — fetching and persistence are the caller's job, so
96
+ * this stays unit-testable with a real temp dir and no network.
97
+ */
98
+ export const syncPolicyToDirs = async (policy, skillsDirs, previousVersions) => {
99
+ const results = [];
100
+ const versions = {};
101
+ let changed = 0;
102
+ let unchanged = 0;
103
+ for (const skill of policy.skills) {
104
+ // Defense-in-depth: never turn an unsafe id into a write path, even if a
105
+ // caller passed skills that didn't go through fetchTeamPolicy's filter.
106
+ if (!isSafeSkillId(skill.id))
107
+ continue;
108
+ versions[skill.id] = skill.version;
109
+ const isUpToDate = previousVersions[skill.id] === skill.version;
110
+ if (isUpToDate) {
111
+ unchanged += 1;
112
+ results.push({
113
+ id: skill.id,
114
+ name: skill.name,
115
+ version: skill.version,
116
+ written: false,
117
+ files: [],
118
+ });
119
+ continue;
120
+ }
121
+ const files = [];
122
+ for (const dir of skillsDirs) {
123
+ const skillDir = join(dir, skill.id);
124
+ const file = join(skillDir, 'SKILL.md');
125
+ await mkdir(skillDir, { recursive: true });
126
+ // Skill bodies are team policy text (content-free); persisting them is
127
+ // intended. A trailing newline keeps the files diff-friendly.
128
+ await writeFile(file, ensureTrailingNewline(skill.body), 'utf-8');
129
+ files.push(file);
130
+ }
131
+ changed += 1;
132
+ results.push({
133
+ id: skill.id,
134
+ name: skill.name,
135
+ version: skill.version,
136
+ written: true,
137
+ files,
138
+ });
139
+ }
140
+ return { results, changed, unchanged, versions, updatedAt: policy.updatedAt };
141
+ };
142
+ const ensureTrailingNewline = (body) => body.endsWith('\n') ? body : `${body}\n`;
143
+ //# sourceMappingURL=policySync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policySync.js","sourceRoot":"","sources":["../../src/util/policySync.ts"],"names":[],"mappings":"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"}
@@ -6,15 +6,17 @@
6
6
  * the tool — it ends up pointed at a dead URL with no obvious way back. This is
7
7
  * the one mistake a fleet-rollout on-ramp can't make.
8
8
  *
9
- * We probe `<origin>/v1/models` and treat *any* HTTP response including 401/
10
- * 404 as "reachable": the gateway is up and talking, which is all we need to
11
- * verify before writing config. Only a transport failure (DNS, refused, timeout)
12
- * counts as unreachable.
9
+ * We probe `GET <origin>/` the gateway's unauthenticated health route, which
10
+ * a healthy gateway answers with 200 and require a 2xx. A non-2xx means
11
+ * whatever answered is not a healthy Anyray gateway (wrong URL, a proxy in the
12
+ * way, …): treating "responded 404" as success just defers the breakage to the
13
+ * dev's first real request. Transport failures (DNS, refused, timeout) are
14
+ * unreachable too. `--force` remains the escape hatch.
13
15
  */
14
16
  const DEFAULT_TIMEOUT_MS = 3000;
15
17
  export const checkGateway = async (origin, opts = {}) => {
16
18
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
17
- const url = `${origin}/v1/models`;
19
+ const url = `${origin}/`;
18
20
  const controller = new AbortController();
19
21
  const timer = setTimeout(() => controller.abort(), timeoutMs);
20
22
  try {
@@ -22,9 +24,12 @@ export const checkGateway = async (origin, opts = {}) => {
22
24
  method: 'GET',
23
25
  signal: controller.signal,
24
26
  });
25
- // Any response means the gateway is up and speaking HTTP — auth/route
26
- // errors are fine here; we only need to know it's alive.
27
- return { reachable: true, detail: `responded ${res.status}` };
27
+ if (res.ok)
28
+ return { reachable: true, detail: `responded ${res.status}` };
29
+ return {
30
+ reachable: false,
31
+ detail: `responded ${res.status} at ${url} — a healthy gateway answers 200 there`,
32
+ };
28
33
  }
29
34
  catch (error) {
30
35
  const reason = error instanceof Error && error.name === 'AbortError'
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/util/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAahC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAc,EACd,OAAyB,EAAE,EACD,EAAE;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,GAAG,MAAM,YAAY,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,sEAAsE;QACtE,yDAAyD;QACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GACV,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;YACnD,CAAC,CAAC,sBAAsB,SAAS,IAAI;YACrC,CAAC,CAAC,KAAK,YAAY,KAAK;gBACtB,CAAC,CAAC,KAAK,CAAC,OAAO;gBACf,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/util/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAahC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAc,EACd,OAAyB,EAAE,EACD,EAAE;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,CAAC;IACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1E,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,aAAa,GAAG,CAAC,MAAM,OAAO,GAAG,wCAAwC;SAClF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GACV,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;YACnD,CAAC,CAAC,sBAAsB,SAAS,IAAI;YACrC,CAAC,CAAC,KAAK,YAAY,KAAK;gBACtB,CAAC,CAAC,KAAK,CAAC,OAAO;gBACf,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Personal savings readout — the tangible number that ends onboarding.
3
+ *
4
+ * Contract (gateway-side): `GET <origin>/connect/savings` with header
5
+ * `x-anyray-api-key: <personal key or placeholder>` → 200
6
+ * { "tokensSaved": number, "savingsUsd": number,
7
+ * "requestCount": number, "windowDays": number }
8
+ *
9
+ * Best-effort: an older gateway returns 404 (or the network fails); callers
10
+ * silently skip the savings line — it must NEVER break the apply flow. Brand-new
11
+ * users see all-zero numbers, which we render as an encouraging "start sending
12
+ * traffic" line rather than "$0.00".
13
+ *
14
+ * PRIVACY: the endpoint returns content-free aggregates only (token counts, a
15
+ * dollar figure, a request count). Nothing here touches prompt/response content.
16
+ */
17
+ const SAVINGS_TIMEOUT_MS = 5000;
18
+ const finiteNumber = (value) => typeof value === 'number' && Number.isFinite(value) ? value : 0;
19
+ /**
20
+ * Fetch the dev's content-free savings, or `undefined` on any error / non-200
21
+ * (older gateway, network hiccup). Never throws — onboarding must not break.
22
+ */
23
+ export const fetchSavings = async (origin, apiKey, opts = {}) => {
24
+ const timeoutMs = opts.timeoutMs ?? SAVINGS_TIMEOUT_MS;
25
+ const url = `${origin}/connect/savings`;
26
+ const controller = new AbortController();
27
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
28
+ try {
29
+ const res = await fetch(url, {
30
+ method: 'GET',
31
+ headers: { 'x-anyray-api-key': apiKey },
32
+ signal: controller.signal,
33
+ });
34
+ if (!res.ok)
35
+ return undefined;
36
+ const body = (await res.json());
37
+ return {
38
+ tokensSaved: finiteNumber(body.tokensSaved),
39
+ savingsUsd: finiteNumber(body.savingsUsd),
40
+ requestCount: finiteNumber(body.requestCount),
41
+ windowDays: finiteNumber(body.windowDays),
42
+ };
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ finally {
48
+ clearTimeout(timer);
49
+ }
50
+ };
51
+ /** Round to whole tokens and group with thousands separators. */
52
+ const formatTokens = (tokens) => Math.round(tokens).toLocaleString('en-US');
53
+ /** Two-decimal USD, e.g. `$12.30`. */
54
+ const formatUsd = (usd) => `$${usd.toLocaleString('en-US', {
55
+ minimumFractionDigits: 2,
56
+ maximumFractionDigits: 2,
57
+ })}`;
58
+ /**
59
+ * Render the savings line for onboarding/status. Returns the encouraging
60
+ * zero-state line when there's no traffic yet (no requests / nothing saved).
61
+ */
62
+ export const formatSavingsLine = (savings) => {
63
+ const noTraffic = savings.requestCount <= 0 ||
64
+ (savings.tokensSaved <= 0 && savings.savingsUsd <= 0);
65
+ if (noTraffic) {
66
+ return 'No savings yet — start sending traffic through Anyray to see them add up.';
67
+ }
68
+ const window = savings.windowDays > 0 ? ` (last ${Math.round(savings.windowDays)}d)` : '';
69
+ return `You've saved ~${formatUsd(savings.savingsUsd)} / ~${formatTokens(savings.tokensSaved)} tokens via Anyray so far${window}.`;
70
+ };
71
+ //# sourceMappingURL=savings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savings.js","sourceRoot":"","sources":["../../src/util/savings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAkBhC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAc,EACd,MAAc,EACd,OAAuB,EAAE,EACK,EAAE;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,GAAG,MAAM,kBAAkB,CAAC;IACxC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE;YACvC,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;QAC3D,OAAO;YACL,WAAW,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC;YAC3C,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;YACzC,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC;YAC7C,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;SAC1C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC;AAEF,iEAAiE;AACjE,MAAM,YAAY,GAAG,CAAC,MAAc,EAAU,EAAE,CAC9C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;AAE7C,sCAAsC;AACtC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE,CACxC,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE;IAC9B,qBAAqB,EAAE,CAAC;IACxB,qBAAqB,EAAE,CAAC;CACzB,CAAC,EAAE,CAAC;AAEP;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAgB,EAAU,EAAE;IAC5D,MAAM,SAAS,GACb,OAAO,CAAC,YAAY,IAAI,CAAC;QACzB,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;IACxD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,2EAA2E,CAAC;IACrF,CAAC;IACD,MAAM,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,OAAO,iBAAiB,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CACtE,OAAO,CAAC,WAAW,CACpB,4BAA4B,MAAM,GAAG,CAAC;AACzC,CAAC,CAAC"}