aweshare 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -1,13 +1,33 @@
1
1
  <div align="center">
2
-
3
- <img src="logo/logo.webp" alt="aweshare" width="860">
4
-
5
- # aweshare
6
-
7
- **Open-source, local-first AI capability relay**
8
-
9
- **English** · [简体中文](./README_cn.md)
10
-
2
+ <img src="./logo/logo.png" alt="aweshare" width="760">
3
+ <h1>aweshare: Local-first AI Capability Relay</h1>
4
+ <p><strong>An open-source, local-first AI capability relay.</strong></p>
5
+ <p>Share local Ollama/vLLM or authorized OpenAI/Anthropic backends through a grant-based hub, consumed with standard SDKs via <code>namespace/alias</code>.</p>
6
+ <p><strong>Upstream API keys never leave the producer's device.</strong></p>
7
+ <p>
8
+ <strong>English</strong> ·
9
+ <a href="./README_cn.md">简体中文</a> ·
10
+ <a href="https://www.npmjs.com/package/aweshare">npm</a> ·
11
+ <a href="https://github.com/wehuman01/aweshare">GitHub</a>
12
+ </p>
13
+ <p>
14
+ <a href="https://ko-fi.com/mugpeng"><img src="https://img.shields.io/badge/Ko--fi-Buy%20me%20a%20coffee-FF5E5B?style=flat-square&logo=ko-fi&logoColor=white" alt="Ko-fi"></a>
15
+ </p>
16
+ <p>
17
+ <a href="https://github.com/wehuman01/aweshare-source/releases"><img src="https://img.shields.io/badge/version-0.2.4-7C3AED?style=flat-square" alt="Version"></a>
18
+ <a href="https://github.com/wehuman01/aweshare"><img src="https://img.shields.io/badge/node-%E2%89%A522-0EA5E9?style=flat-square" alt="Node"></a>
19
+ <a href="https://github.com/wehuman01/aweshare/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-22C55E?style=flat-square" alt="License"></a>
20
+ <a href="https://www.npmjs.com/package/aweshare"><img src="https://img.shields.io/badge/npm-aweshare-7C3AED?style=flat-square" alt="npm package"></a>
21
+ </p>
22
+ <p>
23
+ <img src="https://img.shields.io/badge/status-beta-c96a3d?style=flat-square" alt="Status">
24
+ <img src="https://img.shields.io/badge/docker-ghcr.io%2Fwehuman01%2Faweshare-2496ED?style=flat-square&logo=docker" alt="Docker image">
25
+ <img src="https://img.shields.io/badge/protocol-OpenAI%20%7C%20Anthropic-0ea5a4?style=flat-square" alt="Protocols">
26
+ <img src="https://img.shields.io/badge/platform-docker%20%7C%20npm%20%7C%20node-334155?style=flat-square" alt="Platform">
27
+ <img src="https://img.shields.io/npm/dt/aweshare?style=flat-square" alt="npm downloads">
28
+ <img src="https://img.shields.io/github/stars/wehuman01/aweshare?style=flat-square" alt="GitHub stars">
29
+ <img src="https://img.shields.io/badge/deploy-self%20hosted-334155?style=flat-square" alt="Self hosted">
30
+ </p>
11
31
  </div>
12
32
 
13
33
  Producers run a lightweight agent on their own machine and share local Ollama/vLLM or **authorized** OpenAI/Anthropic backends. Upstream API keys live only on the producer's device and are injected by the local agent at forwarding time. Consumers point a standard OpenAI/Anthropic SDK at the hub and call models by `namespace/alias` — exactly like using any other model vendor.
@@ -212,9 +232,12 @@ Usage metering: one row per request (alias, real model, status, duration, byte c
212
232
  | `AWESHARE_CONSUMER_RPS` / `BURST` / `CONCURRENCY` | 10 / 20 / 8 | per-consumer limits |
213
233
  | `AWESHARE_HEAD_TIMEOUT_MS` / `IDLE_TIMEOUT_MS` | 120000 / 300000 | response-head timeout / stream idle timeout |
214
234
  | `AWESHARE_MAX_BODY_BYTES` | 32MB | request body cap |
235
+ | `AWESHARE_NO_UPDATE_CHECK` | unset | set to `1` to disable the passive update reminder |
215
236
 
216
237
  Health: agent heartbeats every 15s, silent 45s = dead; backends with 2 consecutive AUTH/QUOTA failures auto-degrade (alias shows `degraded`, dispatch stops), 30s probes recover. A new connection with the same producer token replaces the old one (latest-wins).
217
238
 
239
+ Updating a npm install: `aweshare self-update` (asks before installing; `--check` only shows versions). The CLI also reminds you at most once a day when a newer npm release exists.
240
+
218
241
  ## Development
219
242
 
220
243
  ```bash
@@ -232,7 +255,7 @@ aweshare hub serve
232
255
  aweshare agent doctor
233
256
  ```
234
257
 
235
- Releasing: push a `v*` tag with a matching `## [x.y.z]` section in `docs/CHANGELOG.md`; CI publishes the `aweshare` package to npm via Trusted Publishing (OIDC, no token secret) and the Docker image to `ghcr.io/wehuman01/aweshare`.
258
+ Releasing: push a `v*` tag with a matching `## [x.y.z]` section in `docs/CHANGELOG.md`; CI publishes the `aweshare` package to npm via Trusted Publishing (OIDC, no token secret), pushes the Docker image to `ghcr.io/wehuman01/aweshare`, and mirrors user-facing docs to the public repo (`wehuman01/aweshare`).
236
259
 
237
260
  Layout: `packages/protocol` (shared wire protocol) · `apps/hub` (HTTP+WS+SQLite+CLI) · `apps/agent` (CLI). Design docs live in `docs/specs/`; the changelog in `docs/CHANGELOG.md`; contribution scope in [CONTRIBUTING.md](./CONTRIBUTING.md).
238
261
 
package/bin/aweshare.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- // Umbrella CLI: `aweshare hub ...` / `aweshare agent ...`.
2
+ // Umbrella CLI: `aweshare hub ...` / `aweshare agent ...` / `aweshare self-update`.
3
3
  // Dev convenience — the real entry points live in apps/hub and apps/agent.
4
4
  import { readFileSync } from 'node:fs'
5
+ import { maybeCheckForUpdate, runSelfUpdate } from './self-update.mjs'
5
6
 
6
7
  const [, , cmd, ...rest] = process.argv
7
8
 
@@ -22,22 +23,33 @@ async function main() {
22
23
  Usage:
23
24
  aweshare hub <command> hub side: init, serve, token, grant, consumer, usage
24
25
  aweshare agent <command> producer side: init, start, doctor, grant, revoke, list
26
+ aweshare self-update [--check] update the aweshare CLI itself (npm registry)
27
+
28
+ Environment:
29
+ AWESHARE_NO_UPDATE_CHECK=1 disable the passive update reminder
25
30
 
26
31
  Install (published): npm install -g aweshare
27
32
  From source: pnpm install && pnpm build, then pnpm link --global`)
28
33
  return
29
34
  }
35
+ if (cmd === 'self-update') {
36
+ await runSelfUpdate(rest)
37
+ return
38
+ }
30
39
  const target = Object.hasOwn(routes, cmd) ? routes[cmd] : undefined
31
40
  if (!target) {
32
- console.error(`unknown target '${cmd}' — expected 'hub' or 'agent'`)
41
+ console.error(`unknown target '${cmd}' — expected 'hub', 'agent' or 'self-update'`)
33
42
  process.exitCode = 1
34
43
  return
35
44
  }
45
+ const reminder = maybeCheckForUpdate(process.argv.slice(2)).catch(() => null)
36
46
  process.argv = [process.argv[0], `aweshare-${cmd}`, ...rest]
37
47
  await import(target)
48
+ const message = await reminder
49
+ if (message) console.error(message)
38
50
  }
39
51
 
40
52
  main().catch((err) => {
41
- console.error(err instanceof Error ? err.stack ?? err.message : err)
53
+ console.error(`error: ${err instanceof Error ? err.message : err}`)
42
54
  process.exit(1)
43
55
  })
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ // `aweshare self-update` — update the npm-installed aweshare CLI in place.
3
+ // Also exports the passive update reminder used by the umbrella CLI.
4
+ // Plain Node, no dependencies: this file ships in the tarball as-is.
5
+ import { execFile } from 'node:child_process'
6
+ import { readFileSync } from 'node:fs'
7
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
8
+ import { homedir } from 'node:os'
9
+ import path from 'node:path'
10
+ import { createInterface } from 'node:readline'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
14
+ .version
15
+
16
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
17
+ const REMIND_INTERVAL_MS = 24 * 60 * 60 * 1000
18
+
19
+ function runCommand(command, args, { cwd, timeout = 120_000 } = {}) {
20
+ return new Promise((resolve, reject) => {
21
+ // npm ships as npm.cmd on Windows. Node 20+ (CVE-2024-27980) refuses to
22
+ // spawn .cmd/.bat shims without a shell, so the whole self-update flow
23
+ // fails on Windows unless we route through one.
24
+ execFile(
25
+ command,
26
+ args,
27
+ { timeout, cwd, shell: process.platform === 'win32' },
28
+ (error, stdout, stderr) => {
29
+ if (error) {
30
+ reject(new Error(stderr.trim() || error.message))
31
+ return
32
+ }
33
+ resolve(stdout.trim())
34
+ },
35
+ )
36
+ })
37
+ }
38
+
39
+ export async function getNpmLatestVersion(run = runCommand) {
40
+ return run('npm', ['view', 'aweshare', 'version'], { timeout: 60_000 })
41
+ }
42
+
43
+ /** Ask a y/n question on the terminal. Empty answer means yes. */
44
+ function confirm(question) {
45
+ return new Promise((resolve) => {
46
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
47
+ resolve(null)
48
+ return
49
+ }
50
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
51
+ rl.question(`${question} (y/n) `, (answer) => {
52
+ rl.close()
53
+ const trimmed = answer.trim().toLowerCase()
54
+ resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes')
55
+ })
56
+ })
57
+ }
58
+
59
+ /**
60
+ * `aweshare self-update [--check]`.
61
+ * Throws on failure; the umbrella CLI reports and exits non-zero.
62
+ */
63
+ export async function runSelfUpdate(args, { run = runCommand, confirmFn = confirm } = {}) {
64
+ const checkOnly = args.includes('--check')
65
+ const latest = await getNpmLatestVersion(run)
66
+
67
+ console.log(`current version: ${VERSION}`)
68
+ console.log(`latest version: ${latest}`)
69
+
70
+ if (VERSION === latest) {
71
+ console.log('already up to date')
72
+ return
73
+ }
74
+ if (checkOnly) return
75
+
76
+ const ok = await confirmFn(`Update aweshare from ${VERSION} to ${latest}?`)
77
+ if (ok === null) {
78
+ throw new Error('stdin is not a terminal — run interactively, or use: npm install -g aweshare')
79
+ }
80
+ if (!ok) {
81
+ console.log('update cancelled')
82
+ return
83
+ }
84
+
85
+ console.log('updating via npm...')
86
+ await run('npm', ['install', '-g', 'aweshare'], { timeout: 300_000 })
87
+ console.log(`updated to ${latest} — run 'aweshare -v' to verify`)
88
+ }
89
+
90
+ /** Long-running or non-working commands never see the reminder. */
91
+ export function shouldSkipUpdateCheck(argv) {
92
+ if (argv.length === 0) return true
93
+ const [cmd, sub] = argv
94
+ if (['help', '-h', '--help', '-v', '--version'].includes(cmd)) return true
95
+ if (cmd === 'self-update') return true
96
+ if ((cmd === 'hub' && sub === 'serve') || (cmd === 'agent' && sub === 'start')) return true
97
+ return false
98
+ }
99
+
100
+ function defaultCacheFile() {
101
+ return path.join(homedir(), '.cache', 'aweshare', 'update-check.json')
102
+ }
103
+
104
+ async function readJson(file) {
105
+ try {
106
+ return JSON.parse(await readFile(file, 'utf8'))
107
+ } catch {
108
+ return null
109
+ }
110
+ }
111
+
112
+ async function writeJson(file, data) {
113
+ try {
114
+ await mkdir(path.dirname(file), { recursive: true })
115
+ await writeFile(file, `${JSON.stringify(data, null, 2)}\n`)
116
+ } catch {
117
+ // a failed cache write must never break the command it decorates
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Passive reminder: at most one npm lookup and one reminder per 24h.
123
+ * Returns the reminder text to print (on stderr) or null.
124
+ */
125
+ export async function maybeCheckForUpdate(argv, { run = runCommand, cacheFile, now } = {}) {
126
+ if (process.env.AWESHARE_NO_UPDATE_CHECK === '1') return null
127
+ if (shouldSkipUpdateCheck(argv)) return null
128
+
129
+ cacheFile = cacheFile ?? defaultCacheFile()
130
+ now = now ?? Date.now()
131
+ let cache = await readJson(cacheFile)
132
+
133
+ let latest = null
134
+ if (cache && now - Date.parse(cache.lastChecked) < CHECK_INTERVAL_MS) {
135
+ latest = cache.latestVersion
136
+ } else {
137
+ try {
138
+ latest = await getNpmLatestVersion(run)
139
+ } catch {
140
+ return null
141
+ }
142
+ cache = {
143
+ lastChecked: new Date(now).toISOString(),
144
+ latestVersion: latest,
145
+ lastReminded: cache?.lastReminded ?? '',
146
+ }
147
+ await writeJson(cacheFile, cache)
148
+ }
149
+
150
+ if (!latest || latest === VERSION) return null
151
+
152
+ const lastRemindedMs = cache.lastReminded ? Date.parse(cache.lastReminded) : 0
153
+ if (now - lastRemindedMs < REMIND_INTERVAL_MS) return null
154
+
155
+ await writeJson(cacheFile, { ...cache, lastReminded: new Date(now).toISOString() })
156
+ return `Update available: ${VERSION} → ${latest}. Run \`aweshare self-update\` to update.`
157
+ }
158
+
159
+ // direct execution: `node bin/self-update.mjs --check`
160
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
161
+ runSelfUpdate(process.argv.slice(2)).catch((err) => {
162
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`)
163
+ process.exit(1)
164
+ })
165
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aweshare",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Open-source, local-first AI capability relay",
6
6
  "license": "MIT",
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "bin",
19
+ "!bin/*.test.mjs",
19
20
  "apps/hub/dist",
20
21
  "apps/hub/package.json",
21
22
  "apps/agent/dist",