@axium/client 0.5.0 → 0.7.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.
package/assets/styles.css CHANGED
@@ -65,6 +65,27 @@ textarea {
65
65
  outline: none;
66
66
  }
67
67
 
68
+ select,
69
+ ::picker(select) {
70
+ appearance: base-select;
71
+ }
72
+
73
+ ::picker(select) {
74
+ border: 1px solid var(--border-accent);
75
+ padding: 1em;
76
+ border-radius: 0.5em;
77
+ background-color: var(--bg-menu);
78
+ }
79
+
80
+ option {
81
+ padding: 0.25em 0;
82
+ border-radius: 0.5em;
83
+ }
84
+
85
+ option:hover {
86
+ background-color: var(--bg-alt);
87
+ }
88
+
68
89
  button {
69
90
  cursor: pointer;
70
91
  }
@@ -0,0 +1,22 @@
1
+ [Unit]
2
+ Description=Axium Client Daemon
3
+ Wants=network-online.target
4
+ After=network-online.target
5
+ RequiresMountsFor=%Y
6
+ StartLimitIntervalSec=1min
7
+ StartLimitBurst=5
8
+ StartLimitAction=none
9
+ FailureAction=none
10
+
11
+ [Service]
12
+ Type=simple
13
+ ExecStartPre=/usr/bin/env cd "%Y/../../.."
14
+ ExecStart=/usr/bin/env npx --prefix "%Y/../../.." axium-client run
15
+ ExecReload=kill -HUP $MAINPID
16
+ SyslogIdentifier=axium-client
17
+ Restart=on-failure
18
+ RestartSec=3s
19
+ TimeoutStopSec=15s
20
+
21
+ [Install]
22
+ WantedBy=default.target
@@ -0,0 +1,24 @@
1
+ export declare const configDir: string;
2
+ export declare const cacheDir: string;
3
+ export declare function session(): {
4
+ id: string;
5
+ userId: string;
6
+ expires: Date;
7
+ created: Date;
8
+ elevated: boolean;
9
+ user: {
10
+ id: string;
11
+ name: string;
12
+ email: string;
13
+ preferences: Record<string, any>;
14
+ roles: string[];
15
+ registeredAt: Date;
16
+ isAdmin: boolean;
17
+ emailVerified?: Date | null | undefined;
18
+ image?: string | null | undefined;
19
+ };
20
+ };
21
+ export declare function loadConfig(safe: boolean): Promise<void>;
22
+ export declare function saveConfig(): void;
23
+ export declare const _dayMs: number;
24
+ export declare function updateCache(force: boolean): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import * as io from '@axium/core/node/io';
2
+ import { loadPlugin } from '@axium/core/node/plugins';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path/posix';
6
+ import * as z from 'zod';
7
+ import { ClientConfig, config } from '../config.js';
8
+ import { fetchAPI, setPrefix, setToken } from '../requests.js';
9
+ import { getCurrentSession } from '../user.js';
10
+ export const configDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'axium');
11
+ mkdirSync(configDir, { recursive: true });
12
+ const axcConfig = join(configDir, 'config.json');
13
+ export const cacheDir = join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'axium');
14
+ mkdirSync(cacheDir, { recursive: true });
15
+ export function session() {
16
+ if (!config.token)
17
+ io.exit('Not logged in.', 4);
18
+ if (!config.cache)
19
+ io.exit('No session data available.', 3);
20
+ return config.cache.session;
21
+ }
22
+ export async function loadConfig(safe) {
23
+ try {
24
+ Object.assign(config, io.readJSON(axcConfig, ClientConfig));
25
+ if (config.server)
26
+ setPrefix(config.server);
27
+ if (config.token)
28
+ setToken(config.token);
29
+ for (const plugin of config.plugins ?? [])
30
+ await loadPlugin('client', plugin, axcConfig, safe);
31
+ }
32
+ catch (e) {
33
+ io.warn('Failed to load config: ' + (e instanceof z.core.$ZodError ? z.prettifyError(e) : io._debugOutput ? e.stack : e.message));
34
+ }
35
+ }
36
+ export function saveConfig() {
37
+ io.writeJSON(axcConfig, config);
38
+ io.debug('Saved config to ' + axcConfig);
39
+ }
40
+ export const _dayMs = 24 * 3600_000;
41
+ export async function updateCache(force) {
42
+ if (!force && config.cache && config.cache.fetched + _dayMs > Date.now())
43
+ return;
44
+ io.start('Fetching metadata');
45
+ const [session, apps] = await Promise.all([getCurrentSession(), fetchAPI('GET', 'apps')]).catch(err => io.exit(err.message));
46
+ try {
47
+ config.cache = { fetched: Date.now(), session, apps };
48
+ saveConfig();
49
+ io.done();
50
+ }
51
+ catch {
52
+ return;
53
+ }
54
+ }
@@ -0,0 +1,2 @@
1
+ #! /usr/bin/env node
2
+ export {};
@@ -0,0 +1,244 @@
1
+ #! /usr/bin/env node
2
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
3
+ if (value !== null && value !== void 0) {
4
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
5
+ var dispose, inner;
6
+ if (async) {
7
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
8
+ dispose = value[Symbol.asyncDispose];
9
+ }
10
+ if (dispose === void 0) {
11
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
12
+ dispose = value[Symbol.dispose];
13
+ if (async) inner = dispose;
14
+ }
15
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
16
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
17
+ env.stack.push({ value: value, dispose: dispose, async: async });
18
+ }
19
+ else if (async) {
20
+ env.stack.push({ async: true });
21
+ }
22
+ return value;
23
+ };
24
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
25
+ return function (env) {
26
+ function fail(e) {
27
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
28
+ env.hasError = true;
29
+ }
30
+ var r, s = 0;
31
+ function next() {
32
+ while (r = env.stack.pop()) {
33
+ try {
34
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
35
+ if (r.dispose) {
36
+ var result = r.dispose.call(r.value);
37
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
38
+ }
39
+ else s |= 1;
40
+ }
41
+ catch (e) {
42
+ fail(e);
43
+ }
44
+ }
45
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
46
+ if (env.hasError) throw env.error;
47
+ }
48
+ return next();
49
+ };
50
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
51
+ var e = new Error(message);
52
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
+ });
54
+ import { outputDaemonStatus, io, pluginText } from '@axium/core/node';
55
+ import { _findPlugin, plugins } from '@axium/core/plugins';
56
+ import { program } from 'commander';
57
+ import { createServer } from 'node:http';
58
+ import { createInterface } from 'node:readline/promises';
59
+ import { styleText } from 'node:util';
60
+ import * as z from 'zod';
61
+ import $pkg from '../../package.json' with { type: 'json' };
62
+ import { config, resolveServerURL } from '../config.js';
63
+ import { prefix, setPrefix, setToken } from '../requests.js';
64
+ import { getCurrentSession, logout } from '../user.js';
65
+ import { loadConfig, saveConfig, updateCache } from './config.js';
66
+ const safe = z.stringbool().default(false).parse(process.env.SAFE?.toLowerCase()) || process.argv.includes('--safe');
67
+ await loadConfig(safe);
68
+ process.on('SIGHUP', () => {
69
+ io.info('Reloading configuration due to SIGHUP.');
70
+ void loadConfig(safe);
71
+ });
72
+ var rl, axiumPlugin;
73
+ const env_1 = { stack: [], error: void 0, hasError: false };
74
+ try {
75
+ rl = __addDisposableResource(env_1, createInterface({
76
+ input: process.stdin,
77
+ output: process.stdout,
78
+ }), false);
79
+ rl.on('SIGINT', () => io.exit('Aborted.', 7));
80
+ program
81
+ .version($pkg.version)
82
+ .name('axium-client')
83
+ .alias('axc')
84
+ .description('Axium client CLI')
85
+ .configureHelp({ showGlobalOptions: true })
86
+ .option('--debug', 'override debug mode')
87
+ .option('--no-debug', 'override debug mode')
88
+ .option('--refresh-session', 'Force a refresh of session and user metadata from server')
89
+ .option('--cache-only', 'Run entirely from local cache, even if it is expired.')
90
+ .option('--safe', 'do not execute code from plugins');
91
+ program.on('option:debug', () => io._setDebugOutput(true));
92
+ program.hook('preAction', async (_, action) => {
93
+ const opt = action.optsWithGlobals();
94
+ if (!config.token)
95
+ return;
96
+ if (!opt.cacheOnly && action.name() != 'login')
97
+ await updateCache(opt.refreshSession);
98
+ });
99
+ program
100
+ .command('login')
101
+ .description('Log in to your account on an Axium server')
102
+ .argument('[server]', 'Axium server URL')
103
+ .action(async (url) => {
104
+ if (prefix[0] != '/')
105
+ url ||= prefix;
106
+ url ||= await rl.question('Axium server URL: ');
107
+ url = resolveServerURL(url);
108
+ setPrefix(url);
109
+ const sessionReady = Promise.withResolvers();
110
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
111
+ const server = createServer(async (req, res) => {
112
+ res.setHeader('access-control-allow-origin', '*');
113
+ res.setHeader('access-control-allow-methods', '*');
114
+ res.setHeader('access-control-allow-headers', '*');
115
+ if (req.method == 'HEAD' || req.method == 'OPTIONS') {
116
+ res.writeHead(200).end();
117
+ return;
118
+ }
119
+ if (!req.headers['content-type']?.endsWith('/json')) {
120
+ res.writeHead(415).end('Unexpected content type');
121
+ return;
122
+ }
123
+ if (req.method !== 'POST') {
124
+ res.writeHead(405).end('Unexpected request method');
125
+ return;
126
+ }
127
+ const { promise: bodyReady, resolve, reject } = Promise.withResolvers();
128
+ let body = '';
129
+ req.on('data', chunk => (body += chunk.toString()));
130
+ req.on('end', resolve);
131
+ req.on('error', reject);
132
+ try {
133
+ await bodyReady;
134
+ sessionReady.resolve(JSON.parse(body));
135
+ res.writeHead(200).end();
136
+ }
137
+ catch (e) {
138
+ res.statusCode = 500;
139
+ res.end('Internal server error: ' + e.message);
140
+ sessionReady.reject(e.message);
141
+ }
142
+ });
143
+ const serverReady = Promise.withResolvers();
144
+ server.listen(() => {
145
+ const { port } = server.address();
146
+ serverReady.resolve(port);
147
+ });
148
+ server.on('error', e => io.exit('Failed to start local callback server: ' + e.message, 5));
149
+ const port = await serverReady.promise;
150
+ const authURL = new URL('/login/client?port=' + port, url).href;
151
+ console.log('Authenticate by visiting this URL in your browser: ' + styleText('underline', authURL));
152
+ const { token } = await sessionReady.promise.catch(e => io.exit('Failed to obtain session: ' + e, 6));
153
+ setToken(token);
154
+ server.close();
155
+ io.start('Verifying session');
156
+ const session = await getCurrentSession().catch(e => io.exit(e.message, 6));
157
+ io.done();
158
+ io.debug('Session UUID: ' + session.id);
159
+ console.log(`Welcome ${session.user.name}! Your session is valid until ${session.expires.toLocaleDateString()}.`);
160
+ config.token = token;
161
+ config.server = url;
162
+ saveConfig();
163
+ await updateCache(true);
164
+ });
165
+ program.command('logout').action(async () => {
166
+ if (!config.token)
167
+ io.exit('Not logged in.', 4);
168
+ if (!config.cache)
169
+ io.exit('No session data available.', 3);
170
+ await logout(config.cache.session.userId, config.cache.session.id);
171
+ });
172
+ program.command('status').action(() => {
173
+ if (!config.token)
174
+ return console.log('Not logged in.');
175
+ if (!config.cache)
176
+ return console.log('No session data available.');
177
+ console.log('Logged in to', new URL(prefix).host);
178
+ console.log(styleText('whiteBright', 'Session ID:'), config.cache.session.id);
179
+ const { user } = config.cache.session;
180
+ console.log(styleText('whiteBright', 'User:'), user.name, `<${user.email}>`, styleText('dim', `(${user.id})`));
181
+ outputDaemonStatus('axium-client');
182
+ });
183
+ program
184
+ .command('run')
185
+ .argument('[plugin]', 'The plugin to run')
186
+ .action(async (name) => {
187
+ if (name) {
188
+ const plugin = _findPlugin(name);
189
+ await plugin._client?.run();
190
+ return;
191
+ }
192
+ for (const plugin of plugins.values())
193
+ await plugin._client?.run();
194
+ });
195
+ axiumPlugin = program.command('plugin').alias('plugins').description('Manage plugins');
196
+ axiumPlugin
197
+ .command('list')
198
+ .alias('ls')
199
+ .description('List loaded plugins')
200
+ .option('-l, --long', 'use the long listing format')
201
+ .option('--no-versions', 'do not show plugin versions')
202
+ .action((opt) => {
203
+ if (!plugins.size) {
204
+ console.log('No plugins loaded.');
205
+ return;
206
+ }
207
+ if (!opt.long) {
208
+ console.log(Array.from(plugins.keys()).join(', '));
209
+ return;
210
+ }
211
+ console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
212
+ for (const plugin of plugins.values()) {
213
+ console.log(plugin.name, opt.versions ? plugin.version : '');
214
+ }
215
+ });
216
+ axiumPlugin
217
+ .command('info')
218
+ .description('Get information about a plugin')
219
+ .argument('<plugin>', 'the plugin to get information about')
220
+ .action((search) => {
221
+ const plugin = _findPlugin(search);
222
+ for (const line of pluginText(plugin))
223
+ console.log(line);
224
+ });
225
+ axiumPlugin
226
+ .command('remove')
227
+ .alias('rm')
228
+ .description('Remove a plugin')
229
+ .argument('<plugin>', 'the plugin to remove')
230
+ .action((search) => {
231
+ const plugin = _findPlugin(search);
232
+ config.plugins = config.plugins.filter(p => p !== plugin.specifier);
233
+ plugins.delete(plugin.name);
234
+ saveConfig();
235
+ });
236
+ await program.parseAsync();
237
+ }
238
+ catch (e_1) {
239
+ env_1.error = e_1;
240
+ env_1.hasError = true;
241
+ }
242
+ finally {
243
+ __disposeResources(env_1);
244
+ }
@@ -0,0 +1,37 @@
1
+ import * as z from 'zod';
2
+ export declare const ClientConfig: z.ZodObject<{
3
+ token: z.ZodOptional<z.ZodNullable<z.ZodBase64>>;
4
+ server: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
5
+ cache: z.ZodOptional<z.ZodNullable<z.ZodObject<{
6
+ fetched: z.ZodInt;
7
+ session: z.ZodObject<{
8
+ id: z.ZodUUID;
9
+ userId: z.ZodUUID;
10
+ expires: z.ZodCoercedDate<unknown>;
11
+ created: z.ZodCoercedDate<unknown>;
12
+ elevated: z.ZodBoolean;
13
+ user: z.ZodObject<{
14
+ id: z.ZodUUID;
15
+ name: z.ZodString;
16
+ email: z.ZodEmail;
17
+ emailVerified: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
18
+ image: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
19
+ preferences: z.ZodRecord<z.ZodString, z.ZodAny>;
20
+ roles: z.ZodArray<z.ZodString>;
21
+ registeredAt: z.ZodCoercedDate<unknown>;
22
+ isAdmin: z.ZodBoolean;
23
+ }, z.core.$strip>;
24
+ }, z.core.$strip>;
25
+ apps: z.ZodArray<z.ZodObject<{
26
+ id: z.ZodString;
27
+ name: z.ZodOptional<z.ZodString>;
28
+ image: z.ZodOptional<z.ZodString>;
29
+ icon: z.ZodOptional<z.ZodString>;
30
+ }, z.core.$strip>>;
31
+ }, z.core.$loose>>>;
32
+ plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$loose>;
34
+ export interface ClientConfig extends z.infer<typeof ClientConfig> {
35
+ }
36
+ export declare const config: ClientConfig;
37
+ export declare function resolveServerURL(server: string): string;
package/dist/config.js ADDED
@@ -0,0 +1,33 @@
1
+ import { debug, warn } from '@axium/core/io';
2
+ import { App, Session, User } from '@axium/core';
3
+ import * as z from 'zod';
4
+ export const ClientConfig = z.looseObject({
5
+ token: z.base64().nullish(),
6
+ server: z.url().nullish(),
7
+ // Cache to reduce server load:
8
+ cache: z
9
+ .looseObject({
10
+ fetched: z.int(),
11
+ session: Session.extend({ user: User }),
12
+ apps: App.array(),
13
+ })
14
+ .nullish(),
15
+ plugins: z.string().array().default([]),
16
+ });
17
+ export const config = {
18
+ plugins: [],
19
+ };
20
+ export function resolveServerURL(server) {
21
+ if (!server.startsWith('http://') && !server.startsWith('https://'))
22
+ server = 'https://' + server;
23
+ const url = new URL(server);
24
+ if (url.pathname.endsWith('/api'))
25
+ url.pathname += '/';
26
+ else if (url.pathname.at(-1) == '/' && !url.pathname.endsWith('/api/'))
27
+ url.pathname += 'api/';
28
+ if (url.pathname != '/api/')
29
+ warn('Resolved server URL is not at the top level: ' + url.href);
30
+ else
31
+ debug('Resolved server URL: ' + url.href);
32
+ return url.href;
33
+ }
package/dist/user.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare function getSessions(userId: string): Promise<Session[]>;
13
13
  export declare function logout(userId: string, ...sessionId: string[]): Promise<Session[]>;
14
14
  export declare function logoutAll(userId: string): Promise<Session[]>;
15
15
  export declare function logoutCurrentSession(): Promise<Session>;
16
- export declare function register(_data: Record<string, FormDataEntryValue>): Promise<void>;
16
+ export declare function register(_data: Record<string, unknown>): Promise<void>;
17
17
  export declare function userInfo(userId: string): Promise<UserPublic & Partial<User>>;
18
18
  export declare function updateUser(userId: string, data: Record<string, FormDataEntryValue>): Promise<User>;
19
19
  export declare function fullUserInfo(userId: string): Promise<User & {
@@ -1,10 +1,9 @@
1
1
  <script lang="ts">
2
- import FormDialog from './FormDialog.svelte';
2
+ import type { User } from '@axium/core';
3
3
  import { permissionNames, type AccessControllable } from '@axium/core/access';
4
4
  import type { Entries } from 'utilium';
5
+ import FormDialog from './FormDialog.svelte';
5
6
  import UserCard from './UserCard.svelte';
6
- import type { Permission, AccessControl } from '@axium/core/access';
7
- import type { User } from '@axium/core';
8
7
 
9
8
  let {
10
9
  item = $bindable(),
@@ -1,14 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Dialog from './Dialog.svelte';
3
3
 
4
- function resolveRedirectAfter() {
5
- const url = new URL(location.href);
6
- const maybe = url.searchParams.get('after');
7
- if (!maybe || maybe == url.pathname) return '/';
8
- for (const prefix of ['/api/']) if (maybe.startsWith(prefix)) return '/';
9
- return maybe || '/';
10
- }
11
-
12
4
  let {
13
5
  children,
14
6
  dialog = $bindable(),
@@ -52,8 +44,7 @@
52
44
  const data = Object.fromEntries(new FormData(e.currentTarget));
53
45
  submit(data)
54
46
  .then(result => {
55
- if (pageMode) window.location.href = resolveRedirectAfter();
56
- else dialog!.close();
47
+ if (!pageMode) dialog!.close();
57
48
  })
58
49
  .catch((e: any) => {
59
50
  if (!e) error = 'An unknown error occurred';
package/lib/Login.svelte CHANGED
@@ -1,15 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { loginByEmail } from '@axium/client/user';
3
3
  import FormDialog from './FormDialog.svelte';
4
+ import redirectAfter from './auth_redirect.js';
4
5
 
5
6
  let { dialog = $bindable(), fullPage = false }: { dialog?: HTMLDialogElement; fullPage?: boolean } = $props();
6
7
 
7
- function submit(data: { email: string }) {
8
+ async function submit(data: { email: string }) {
8
9
  if (typeof data.email != 'string') {
9
10
  throw 'Tried to upload a file for an email. Huh?!';
10
11
  }
11
12
 
12
- return loginByEmail(data.email);
13
+ await loginByEmail(data.email);
14
+ if (fullPage && redirectAfter) location.href = redirectAfter;
13
15
  }
14
16
  </script>
15
17
 
@@ -1,11 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { register } from '@axium/client/user';
3
3
  import FormDialog from './FormDialog.svelte';
4
+ import redirectAfter from './auth_redirect.js';
4
5
 
5
6
  let { dialog = $bindable(), fullPage = false }: { dialog?: HTMLDialogElement; fullPage?: boolean } = $props();
7
+
8
+ async function submit(data: Record<string, FormDataEntryValue>) {
9
+ await register(data);
10
+ if (fullPage && redirectAfter) location.href = redirectAfter;
11
+ }
6
12
  </script>
7
13
 
8
- <FormDialog bind:dialog submitText="Register" submit={register} pageMode={fullPage}>
14
+ <FormDialog bind:dialog submitText="Register" {submit} pageMode={fullPage}>
9
15
  <div>
10
16
  <label for="name">Display Name</label>
11
17
  <input name="name" type="text" required />
@@ -0,0 +1,28 @@
1
+ import { getCurrentSession } from '@axium/client/user';
2
+
3
+ function resolveRedirect(): string | false {
4
+ const url = new URL(location.href);
5
+ const maybe = url.searchParams.get('after');
6
+ if (!['/login', '/register'].includes(url.pathname)) return false;
7
+ if (!maybe || maybe == url.pathname) return '/';
8
+
9
+ if (maybe[0] != '/' || maybe[1] == '/') {
10
+ console.error('Ignoring potentially malicious redirect:', maybe);
11
+ return false;
12
+ }
13
+
14
+ const redirect = new URL(maybe, location.origin);
15
+
16
+ return redirect.pathname + redirect.search || '/';
17
+ }
18
+
19
+ const redirect = resolveRedirect();
20
+
21
+ try {
22
+ if (!redirect) throw 'No redirect';
23
+ // Auto-redirect if already logged in.
24
+ const session = await getCurrentSession();
25
+ if (session) location.href = redirect;
26
+ } catch {}
27
+
28
+ export default redirect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/client",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "author": "James Prevett <jp@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -15,6 +15,10 @@
15
15
  "bugs": {
16
16
  "url": "https://github.com/james-pre/axium/issues"
17
17
  },
18
+ "bin": {
19
+ "axium-client": "dist/cli/index.js",
20
+ "axc": "dist/cli/index.js"
21
+ },
18
22
  "type": "module",
19
23
  "main": "dist/index.js",
20
24
  "types": "dist/index.d.ts",
@@ -22,7 +26,8 @@
22
26
  "assets",
23
27
  "dist",
24
28
  "lib",
25
- "styles"
29
+ "styles",
30
+ "axium-client.service"
26
31
  ],
27
32
  "exports": {
28
33
  ".": "./dist/index.js",
@@ -35,7 +40,7 @@
35
40
  "build": "tsc"
36
41
  },
37
42
  "peerDependencies": {
38
- "@axium/core": ">=0.6.0",
43
+ "@axium/core": ">=0.10.0",
39
44
  "utilium": "^2.3.8",
40
45
  "zod": "^4.0.5",
41
46
  "svelte": "^5.36.0"