@agentage/cli 0.0.3 → 0.25.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/CHANGELOG.md +68 -0
- package/README.md +79 -23
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +44 -4
- package/dist/commands/{setup.d.ts → auth/setup.d.ts} +6 -3
- package/dist/commands/{setup.js → auth/setup.js} +56 -11
- package/dist/commands/daemon/daemon-cmd.d.ts +2 -0
- package/dist/commands/daemon/daemon-cmd.js +124 -0
- package/dist/commands/memory/memory-verbs.d.ts +21 -0
- package/dist/commands/memory/memory-verbs.js +98 -0
- package/dist/commands/memory/memory.d.ts +2 -0
- package/dist/commands/memory/memory.js +54 -0
- package/dist/commands/{status.d.ts → status/status.d.ts} +2 -2
- package/dist/commands/status/status.js +88 -0
- package/dist/commands/update/update.d.ts +23 -0
- package/dist/commands/update/update.js +89 -0
- package/dist/commands/vault/vault-sync.d.ts +15 -0
- package/dist/commands/vault/vault-sync.js +100 -0
- package/dist/commands/vault/vault.d.ts +22 -0
- package/dist/commands/vault/vault.js +128 -0
- package/dist/daemon/actions.d.ts +5 -0
- package/dist/daemon/actions.js +22 -0
- package/dist/daemon/client-provider.d.ts +2 -0
- package/dist/daemon/client-provider.js +16 -0
- package/dist/daemon/guards.d.ts +3 -0
- package/dist/daemon/guards.js +13 -0
- package/dist/daemon/lifecycle.d.ts +17 -0
- package/dist/daemon/lifecycle.js +151 -0
- package/dist/daemon/mcp-http.d.ts +3 -0
- package/dist/daemon/mcp-http.js +34 -0
- package/dist/daemon/server.d.ts +26 -0
- package/dist/daemon/server.js +136 -0
- package/dist/daemon-entry.d.ts +7 -0
- package/dist/daemon-entry.js +128 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/lib/auth/api.d.ts +8 -0
- package/dist/lib/auth/api.js +99 -0
- package/dist/lib/auth/auth-errors.d.ts +7 -0
- package/dist/lib/auth/auth-errors.js +22 -0
- package/dist/lib/{callback-server.d.ts → auth/callback-server.d.ts} +0 -1
- package/dist/lib/{callback-server.js → auth/callback-server.js} +2 -2
- package/dist/lib/auth/credentials.d.ts +12 -0
- package/dist/lib/auth/credentials.js +50 -0
- package/dist/lib/auth/env-match.d.ts +9 -0
- package/dist/lib/auth/env-match.js +17 -0
- package/dist/lib/auth/introspect.d.ts +7 -0
- package/dist/lib/auth/introspect.js +89 -0
- package/dist/lib/{oauth.d.ts → auth/oauth.d.ts} +6 -2
- package/dist/lib/{oauth.js → auth/oauth.js} +26 -7
- package/dist/lib/auth/provision.d.ts +14 -0
- package/dist/lib/auth/provision.js +71 -0
- package/dist/lib/daemon/daemon-client.d.ts +39 -0
- package/dist/lib/daemon/daemon-client.js +134 -0
- package/dist/lib/daemon/daemon-pref.d.ts +2 -0
- package/dist/lib/daemon/daemon-pref.js +10 -0
- package/dist/lib/{config.d.ts → fs/config.d.ts} +2 -1
- package/dist/lib/fs/config.js +55 -0
- package/dist/lib/fs/file-lock.d.ts +3 -0
- package/dist/lib/fs/file-lock.js +163 -0
- package/dist/lib/memory/memory-client.d.ts +30 -0
- package/dist/lib/memory/memory-client.js +98 -0
- package/dist/lib/net/http.d.ts +6 -0
- package/dist/lib/net/http.js +32 -0
- package/dist/lib/{origins.d.ts → net/origins.d.ts} +1 -1
- package/dist/lib/net/origins.js +39 -0
- package/dist/lib/net/user-agent.d.ts +6 -0
- package/dist/lib/net/user-agent.js +7 -0
- package/dist/lib/status/format.d.ts +1 -0
- package/dist/lib/status/format.js +12 -0
- package/dist/lib/status/status-info.d.ts +46 -0
- package/dist/lib/status/status-info.js +109 -0
- package/dist/lib/status/vaults-format.d.ts +2 -0
- package/dist/lib/status/vaults-format.js +44 -0
- package/dist/lib/status/vaults-status.d.ts +12 -0
- package/dist/lib/status/vaults-status.js +53 -0
- package/dist/lib/update/update-cache.d.ts +14 -0
- package/dist/lib/update/update-cache.js +63 -0
- package/dist/lib/update/update-check.d.ts +27 -0
- package/dist/lib/update/update-check.js +50 -0
- package/dist/lib/update/update-lock.d.ts +2 -0
- package/dist/lib/update/update-lock.js +109 -0
- package/dist/lib/vault/vault-registry.d.ts +12 -0
- package/dist/lib/vault/vault-registry.js +82 -0
- package/dist/lib/vault/vaults.d.ts +10 -0
- package/dist/lib/vault/vaults.js +66 -0
- package/dist/lib/vault/vaults.schema.d.ts +2 -0
- package/dist/lib/vault/vaults.schema.js +10 -0
- package/dist/mcp/local-server.d.ts +4 -0
- package/dist/mcp/local-server.js +26 -0
- package/dist/sync/couch/cycle.d.ts +2 -0
- package/dist/sync/couch/cycle.js +58 -0
- package/dist/sync/couch/discovery.d.ts +22 -0
- package/dist/sync/couch/discovery.js +34 -0
- package/dist/sync/couch/file-store.d.ts +2 -0
- package/dist/sync/couch/file-store.js +47 -0
- package/dist/sync/couch/local-commit.d.ts +2 -0
- package/dist/sync/couch/local-commit.js +24 -0
- package/dist/sync/couch/manager.d.ts +4 -0
- package/dist/sync/couch/manager.fixtures.d.ts +20 -0
- package/dist/sync/couch/manager.fixtures.js +56 -0
- package/dist/sync/couch/manager.js +126 -0
- package/dist/sync/couch/manager.types.d.ts +81 -0
- package/dist/sync/couch/manager.types.js +1 -0
- package/dist/sync/couch/mutation-target.d.ts +5 -0
- package/dist/sync/couch/mutation-target.js +33 -0
- package/dist/sync/couch/push-on-write.d.ts +3 -0
- package/dist/sync/couch/push-on-write.js +33 -0
- package/dist/sync/couch/state-store.d.ts +3 -0
- package/dist/sync/couch/state-store.js +28 -0
- package/dist/sync/couch/targets.d.ts +9 -0
- package/dist/sync/couch/targets.js +23 -0
- package/dist/sync/couch/wire.d.ts +6 -0
- package/dist/sync/couch/wire.js +16 -0
- package/dist/sync/discover/watcher.d.ts +26 -0
- package/dist/sync/discover/watcher.js +159 -0
- package/dist/sync/git/conflict.d.ts +1 -0
- package/dist/sync/git/conflict.js +16 -0
- package/dist/sync/git/cycle.d.ts +18 -0
- package/dist/sync/git/cycle.js +158 -0
- package/dist/sync/git/git-exec.d.ts +22 -0
- package/dist/sync/git/git-exec.js +56 -0
- package/dist/sync/git/manager.d.ts +30 -0
- package/dist/sync/git/manager.js +114 -0
- package/dist/sync/git/planner.d.ts +15 -0
- package/dist/sync/git/planner.js +46 -0
- package/dist/sync/git/remote-url.d.ts +3 -0
- package/dist/sync/git/remote-url.js +30 -0
- package/dist/utils/version.d.ts +0 -1
- package/dist/utils/version.js +0 -1
- package/package.json +20 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -36
- package/dist/commands/status.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/api.d.ts +0 -12
- package/dist/lib/api.d.ts.map +0 -1
- package/dist/lib/api.js +0 -46
- package/dist/lib/api.js.map +0 -1
- package/dist/lib/callback-server.d.ts.map +0 -1
- package/dist/lib/callback-server.js.map +0 -1
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -34
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/oauth.d.ts.map +0 -1
- package/dist/lib/oauth.js.map +0 -1
- package/dist/lib/origins.d.ts.map +0 -1
- package/dist/lib/origins.js +0 -26
- package/dist/lib/origins.js.map +0 -1
- package/dist/lib/status-info.d.ts +0 -18
- package/dist/lib/status-info.d.ts.map +0 -1
- package/dist/lib/status-info.js +0 -39
- package/dist/lib/status-info.js.map +0 -1
- package/dist/utils/version.d.ts.map +0 -1
- package/dist/utils/version.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Agentage CLI will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [0.0.4] - 2026-07-09
|
|
11
|
+
|
|
12
|
+
### Breaking
|
|
13
|
+
|
|
14
|
+
- Removed the standalone `agentage mcp` stdio command. MCP is now served by the local daemon over Streamable HTTP at `http://127.0.0.1:4243/mcp` (on by default; disable with `daemon start --no-mcp` or `AGENTAGE_DAEMON_NO_MCP=1`). Point clients at the daemon URL: `claude mcp add --transport http agentage http://127.0.0.1:4243/mcp`.
|
|
15
|
+
|
|
16
|
+
### New Features
|
|
17
|
+
|
|
18
|
+
- Account sync: the daemon syncs account (cloud) vaults, with live autodiscovery of new vaults and account-vault provisioning.
|
|
19
|
+
- `setup` now starts the daemon after a successful sign-in, so sync and MCP are ready immediately.
|
|
20
|
+
- `status` now reports the daemon (pid, port, uptime), MCP serving state, a `target` reachability check, and a per-vault breakdown (name, sync channel, status).
|
|
21
|
+
- Outgoing requests now identify the caller with `User-Agent` and CLI/daemon version headers.
|
|
22
|
+
|
|
23
|
+
### Improvements
|
|
24
|
+
|
|
25
|
+
- Auth: sessions refresh silently; a transient auth-server hiccup (429/5xx/network) is retried instead of being reported as a signed-out session.
|
|
26
|
+
- A credential for a different environment than the current target is now reported as an env mismatch (with a recovery hint), not a misleading "session expired".
|
|
27
|
+
- `status` auth line reads `signed in (session active)` rather than a raw UTC token-expiry timestamp.
|
|
28
|
+
- README rewritten for non-technical readers, with the full command and integration reference moved to `docs/reference.md`.
|
|
29
|
+
- Daemon hardened against CSRF and DNS rebinding on its local API; lifecycle and port-in-use races fixed.
|
|
30
|
+
- Sync: remote-URL allowlist and redaction; channel-based internal structure.
|
|
31
|
+
- Offline commands no longer hang; cleaner stdout/stderr discipline.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Data integrity: concurrent writers can no longer silently drop registry updates. The advisory file lock now creates atomically (write-then-link) and never steals a live holder's lock under CPU contention.
|
|
36
|
+
- Packaging hygiene: dependency pins, trimmed npm tarball, removed tracked build artifacts.
|
|
37
|
+
|
|
38
|
+
### Internal
|
|
39
|
+
|
|
40
|
+
- `src/lib` and `src/commands` regrouped into domain folders; oversized modules split.
|
|
41
|
+
|
|
42
|
+
## [0.0.3] - 2026-06-27
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- `status` now reaches the backend at the dedicated `api.<fqdn>` host (the apex `<fqdn>/api` was retired in the 2026-06-17 subdomain cutover), so production no longer reports the endpoint unreachable.
|
|
47
|
+
- `status` no longer crashes when the bearer maps to no active session (`get-session` returns `200 + null`); it now reports signed-out with a `run: agentage setup` hint.
|
|
48
|
+
|
|
49
|
+
## [0.0.2] - 2026-06-26
|
|
50
|
+
|
|
51
|
+
### New Features
|
|
52
|
+
- Add `setup` and `status` commands as part of the agentage Memory client reboot
|
|
53
|
+
- Add daemon autostart on boot/login (platform-aware configuration)
|
|
54
|
+
- Handle machine tombstoned state (410 response) gracefully in the daemon
|
|
55
|
+
|
|
56
|
+
### Improvements
|
|
57
|
+
- Route daemon version-mismatch notice and update notice to stderr for cleaner stdout output
|
|
58
|
+
- Reduce update-check cache from 24h to 1h for more timely update notifications
|
|
59
|
+
|
|
60
|
+
## [0.0.1] - 2026-06-12
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- `setup` - browser OAuth 2.1 sign-in (Dynamic Client Registration + PKCE) with a
|
|
65
|
+
localhost callback; tokens stored in `~/.agentage/auth.json` (mode 0600); flags:
|
|
66
|
+
`--disconnect`, `--reauth`, `--no-browser`.
|
|
67
|
+
- `status` - one line per fact: CLI version, target, sign-in state, and endpoint
|
|
68
|
+
reachability (`--json` supported).
|
package/README.md
CHANGED
|
@@ -3,54 +3,110 @@
|
|
|
3
3
|
[](https://github.com/agentage/cli/actions/workflows/ci.yml)
|
|
4
4
|
[](https://badge.fury.io/js/%40agentage%2Fcli)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Give every AI on your machine one shared memory - plain markdown you own, synced across your devices.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Claude, Cursor, and your other AI tools each keep their own separate memory today. This
|
|
9
|
+
CLI gives them a single shared one. It lives on your disk as ordinary markdown files
|
|
10
|
+
you can open, edit, and back up like any other folder. Your AI tools read and write
|
|
11
|
+
those same files, so they all remember the same things. Connect a free account and your
|
|
12
|
+
memory follows you to every device - and nothing is locked in, because the files are
|
|
13
|
+
always just markdown you can export.
|
|
14
|
+
|
|
15
|
+
## How it fits together
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
Three steps from nothing to a shared memory your AI can use.
|
|
22
|
+
|
|
23
|
+
**1. Install it.** Needs Node.js 22 or newer.
|
|
9
24
|
|
|
10
25
|
```bash
|
|
11
26
|
npm install -g @agentage/cli
|
|
12
27
|
```
|
|
13
28
|
|
|
14
|
-
|
|
29
|
+
**2. Create a memory.** This makes a plain folder of markdown files at `~/vaults/notes`.
|
|
30
|
+
No sign-in needed.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agentage vault add notes --local
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**3. Connect your AI.** This example wires up Claude Code over MCP, the open protocol AI
|
|
37
|
+
tools use to call tools. Any AI tool that speaks MCP connects the same way.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add --transport http agentage http://127.0.0.1:4243/mcp
|
|
41
|
+
```
|
|
15
42
|
|
|
16
|
-
|
|
43
|
+
That's it. Ask your AI to remember something, and it lands in your `~/vaults/notes`
|
|
44
|
+
folder as a markdown file you own.
|
|
17
45
|
|
|
18
|
-
|
|
46
|
+
## What your AI can do
|
|
19
47
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
48
|
+
Once connected, your AI can:
|
|
49
|
+
|
|
50
|
+
- Remember things you tell it, saved as a note.
|
|
51
|
+
- Find them again later by keyword.
|
|
52
|
+
- Read back a specific note in full.
|
|
53
|
+
- Update a note in place, or add to it.
|
|
54
|
+
- Browse the folders your notes are organized into.
|
|
55
|
+
- Remove a note it no longer needs (deletes are recoverable).
|
|
56
|
+
|
|
57
|
+
You can do all of this from the terminal too - run `agentage memory --help` to see how.
|
|
58
|
+
|
|
59
|
+
## Sync across devices
|
|
60
|
+
|
|
61
|
+
Want the same memory on your laptop, desktop, and phone? Connect a free account:
|
|
23
62
|
|
|
24
63
|
```bash
|
|
25
|
-
agentage setup
|
|
26
|
-
agentage setup --no-browser # print the sign-in URL instead of opening a browser
|
|
27
|
-
agentage setup --reauth # force a fresh sign-in
|
|
28
|
-
agentage setup --disconnect # sign out and remove local credentials
|
|
64
|
+
agentage setup
|
|
29
65
|
```
|
|
30
66
|
|
|
31
|
-
|
|
67
|
+
This opens your browser for a one-time sign-in - no API key to copy or passwords in the
|
|
68
|
+
terminal. After that, sync runs quietly in the background and the same memory follows
|
|
69
|
+
you everywhere. Your files always stay yours: they remain plain markdown on your disk,
|
|
70
|
+
and you can export them any time.
|
|
71
|
+
|
|
72
|
+
Prefer to host it yourself? You can point a memory at your own git remote instead with
|
|
73
|
+
`agentage vault add <name> --git <remote>` - see [`docs/reference.md`](docs/reference.md)
|
|
74
|
+
for details.
|
|
75
|
+
|
|
76
|
+
## Token auth (CI / headless)
|
|
32
77
|
|
|
33
|
-
|
|
34
|
-
|
|
78
|
+
For CI or non-interactive machines, skip the browser sign-in and authenticate with a
|
|
79
|
+
personal access token. Mint one in the dashboard under **Settings -> API tokens**
|
|
80
|
+
(scopes `memory:read` / `memory:write`), then set it in the environment:
|
|
35
81
|
|
|
36
82
|
```bash
|
|
83
|
+
export AGENTAGE_TOKEN=aga_...
|
|
37
84
|
agentage status
|
|
38
|
-
agentage status --json
|
|
39
85
|
```
|
|
40
86
|
|
|
41
|
-
|
|
87
|
+
The token is used as the bearer for memory (MCP) calls; `--token aga_...` works per command
|
|
88
|
+
too. Account-channel provisioning still needs an interactive `agentage setup` session.
|
|
42
89
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
90
|
+
## Going deeper
|
|
91
|
+
|
|
92
|
+
- [`docs/architecture.md`](docs/architecture.md) - how the CLI, the local helper, your
|
|
93
|
+
files, and sync fit together.
|
|
94
|
+
- [`docs/reference.md`](docs/reference.md) - every command and flag, the full sync
|
|
95
|
+
options, MCP details, and environment variables.
|
|
47
96
|
|
|
48
97
|
## Development
|
|
49
98
|
|
|
50
99
|
```bash
|
|
51
100
|
npm ci
|
|
52
|
-
npm run verify
|
|
53
|
-
|
|
101
|
+
npm run verify # type-check + lint + format:check + unit tests + build
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
End-to-end tests (Playwright) run in tiers - the offline tiers need no network or
|
|
105
|
+
account. See [`docs/reference.md`](docs/reference.md) for more.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm run build && npm run test:e2e # all e2e tiers
|
|
109
|
+
npm run build && npm run test:e2e -- --grep @offline # offline tiers only
|
|
54
110
|
```
|
|
55
111
|
|
|
56
112
|
## License
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { registerDaemon } from './commands/daemon/daemon-cmd.js';
|
|
5
|
+
import { registerMemory } from './commands/memory/memory.js';
|
|
6
|
+
import { registerSetup } from './commands/auth/setup.js';
|
|
7
|
+
import { registerStatus } from './commands/status/status.js';
|
|
8
|
+
import { registerUpdate } from './commands/update/update.js';
|
|
9
|
+
import { registerVault } from './commands/vault/vault.js';
|
|
10
|
+
import { disableDaemon } from './lib/daemon/daemon-pref.js';
|
|
11
|
+
import { refreshUpdateCache, updateHint } from './lib/update/update-cache.js';
|
|
5
12
|
import { VERSION } from './utils/version.js';
|
|
13
|
+
// Dependency-free guard so the message survives even if deps fail to parse on old Node.
|
|
14
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
15
|
+
if (nodeMajor < 22) {
|
|
16
|
+
process.stderr.write(`agentage requires Node.js >= 22 (you have v${process.versions.node})\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
6
19
|
const program = new Command();
|
|
7
|
-
program
|
|
20
|
+
program
|
|
21
|
+
.name('agentage')
|
|
22
|
+
.description('The offline-first terminal client for agentage Memory')
|
|
23
|
+
.version(VERSION)
|
|
24
|
+
.option('--no-daemon', 'run memory verbs in-process instead of via the daemon');
|
|
25
|
+
program.addHelpText('after', '\nNew here? Run: agentage vault add <name> --local, or agentage setup to connect an account.');
|
|
26
|
+
program.hook('preAction', () => {
|
|
27
|
+
if (program.opts().daemon === false)
|
|
28
|
+
disableDaemon();
|
|
29
|
+
});
|
|
30
|
+
// After any command (except the update-aware ones, which report richer state themselves), print a
|
|
31
|
+
// one-line hint from the cache, then kick a fire-and-forget refresh. Never awaited, never throws -
|
|
32
|
+
// the command output is already done and the cache-only read keeps this instant. The hint is
|
|
33
|
+
// suppressed under --json so it never corrupts machine-readable output.
|
|
34
|
+
program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
35
|
+
const name = actionCommand.name();
|
|
36
|
+
if (name === 'status' || name === 'update')
|
|
37
|
+
return;
|
|
38
|
+
if (!actionCommand.opts()['json']) {
|
|
39
|
+
const hint = updateHint();
|
|
40
|
+
if (hint)
|
|
41
|
+
console.error(chalk.dim(hint)); // stderr: never corrupt piped stdout
|
|
42
|
+
}
|
|
43
|
+
void refreshUpdateCache();
|
|
44
|
+
});
|
|
8
45
|
registerSetup(program);
|
|
9
46
|
registerStatus(program);
|
|
47
|
+
registerVault(program);
|
|
48
|
+
registerMemory(program);
|
|
49
|
+
registerDaemon(program);
|
|
50
|
+
registerUpdate(program);
|
|
10
51
|
program.parseAsync().catch((err) => {
|
|
11
52
|
console.error(err instanceof Error ? err.message : String(err));
|
|
12
53
|
process.exit(1);
|
|
13
54
|
});
|
|
14
|
-
//# sourceMappingURL=cli.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Command } from 'commander';
|
|
2
|
-
import { startCallbackServer } from '
|
|
3
|
-
import {
|
|
2
|
+
import { startCallbackServer } from '../../lib/auth/callback-server.js';
|
|
3
|
+
import { ensureDaemon } from '../../lib/daemon/daemon-client.js';
|
|
4
|
+
import { introspectToken } from '../../lib/auth/api.js';
|
|
5
|
+
import { exchangeCode, registerClient, revokeToken } from '../../lib/auth/oauth.js';
|
|
4
6
|
export interface SetupOptions {
|
|
5
7
|
disconnect?: boolean;
|
|
6
8
|
reauth?: boolean;
|
|
@@ -13,7 +15,8 @@ export interface SetupDeps {
|
|
|
13
15
|
startServer: typeof startCallbackServer;
|
|
14
16
|
openBrowser: (url: string) => Promise<void>;
|
|
15
17
|
printStatus: () => Promise<void>;
|
|
18
|
+
ensureDaemon: typeof ensureDaemon;
|
|
19
|
+
introspect: typeof introspectToken;
|
|
16
20
|
}
|
|
17
21
|
export declare const runSetup: (opts: SetupOptions, deps?: SetupDeps) => Promise<void>;
|
|
18
22
|
export declare const registerSetup: (program: Command) => void;
|
|
19
|
-
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { startCallbackServer } from '
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { startCallbackServer } from '../../lib/auth/callback-server.js';
|
|
3
|
+
import { ensureDaemon } from '../../lib/daemon/daemon-client.js';
|
|
4
|
+
import { daemonDisabled } from '../../lib/daemon/daemon-pref.js';
|
|
5
|
+
import { deleteAuth, ensureConfigDir, mutateAuth, readAuth, } from '../../lib/fs/config.js';
|
|
6
|
+
import { AuthRequiredError, introspectToken } from '../../lib/auth/api.js';
|
|
7
|
+
import { detectEnvMismatch } from '../../lib/auth/env-match.js';
|
|
8
|
+
import { buildAuthorizeUrl, exchangeCode, pkcePair, randomState, registerClient, revokeToken, } from '../../lib/auth/oauth.js';
|
|
9
|
+
import { links, siteFqdn } from '../../lib/net/origins.js';
|
|
10
|
+
import { ensureVaultsConfig } from '../../lib/vault/vaults.js';
|
|
11
|
+
import { runStatus } from '../status/status.js';
|
|
7
12
|
const defaultDeps = {
|
|
8
13
|
register: registerClient,
|
|
9
14
|
exchange: exchangeCode,
|
|
@@ -14,6 +19,8 @@ const defaultDeps = {
|
|
|
14
19
|
await open(url);
|
|
15
20
|
},
|
|
16
21
|
printStatus: () => runStatus({}),
|
|
22
|
+
ensureDaemon,
|
|
23
|
+
introspect: introspectToken,
|
|
17
24
|
};
|
|
18
25
|
const toAuthState = (fqdn, clientId, tokens) => ({
|
|
19
26
|
siteFqdn: fqdn,
|
|
@@ -24,6 +31,18 @@ const toAuthState = (fqdn, clientId, tokens) => ({
|
|
|
24
31
|
expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : undefined,
|
|
25
32
|
},
|
|
26
33
|
});
|
|
34
|
+
// After sign-in, bring the daemon up so account sync starts immediately; ensureDaemon reuses a
|
|
35
|
+
// live daemon and starts a stopped one (idempotent). Honor the --no-daemon / AGENTAGE_NO_DAEMON
|
|
36
|
+
// disable switches.
|
|
37
|
+
const startDaemon = async (deps) => {
|
|
38
|
+
if (daemonDisabled()) {
|
|
39
|
+
console.log('Daemon disabled - account sync will run in-process on demand.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const client = await deps.ensureDaemon();
|
|
43
|
+
const fail = 'Could not start the daemon - run: agentage daemon start';
|
|
44
|
+
console.log(client ? 'Daemon running.' : fail);
|
|
45
|
+
};
|
|
27
46
|
const disconnect = async (deps) => {
|
|
28
47
|
const auth = readAuth();
|
|
29
48
|
if (!auth) {
|
|
@@ -34,19 +53,45 @@ const disconnect = async (deps) => {
|
|
|
34
53
|
deleteAuth();
|
|
35
54
|
console.log('Disconnected - local credentials removed.');
|
|
36
55
|
};
|
|
56
|
+
// Validate the stored session instead of trusting mere token presence. Returns true to short-circuit
|
|
57
|
+
// (valid or a transient blip - creds intact), false to proceed into a fresh sign-in (truly expired or
|
|
58
|
+
// the credential belongs to a different target than the current run).
|
|
59
|
+
const alreadySignedIn = async (auth, deps) => {
|
|
60
|
+
const mismatch = detectEnvMismatch(auth, siteFqdn());
|
|
61
|
+
if (mismatch) {
|
|
62
|
+
console.log(`Signed in to ${mismatch.credentialFqdn} (${mismatch.credentialEnv}), but this run targets ` +
|
|
63
|
+
`${mismatch.targetFqdn} (${mismatch.targetEnv}) - signing you into ${mismatch.targetEnv}.\n`);
|
|
64
|
+
return false; // proceed to a fresh sign-in against the current target
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await deps.introspect(auth, links(auth.siteFqdn));
|
|
68
|
+
console.log(`Already signed in. Run ${chalk.white('agentage setup --reauth')} to sign in again.\n`);
|
|
69
|
+
await deps.printStatus();
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err instanceof AuthRequiredError) {
|
|
74
|
+
console.log('Session expired - signing you in again.\n');
|
|
75
|
+
return false; // fall through to the sign-in flow, no --reauth needed
|
|
76
|
+
}
|
|
77
|
+
// Transient: do not force reauth on a blip; keep credentials and show status.
|
|
78
|
+
console.log('You appear signed in (could not fully verify - temporary).\n');
|
|
79
|
+
await deps.printStatus();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
37
83
|
export const runSetup = async (opts, deps = defaultDeps) => {
|
|
38
84
|
if (opts.disconnect) {
|
|
39
85
|
await disconnect(deps);
|
|
40
86
|
return;
|
|
41
87
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
await deps.printStatus();
|
|
88
|
+
const stored = readAuth();
|
|
89
|
+
if (stored && !opts.reauth && (await alreadySignedIn(stored, deps)))
|
|
45
90
|
return;
|
|
46
|
-
}
|
|
47
91
|
const fqdn = siteFqdn();
|
|
48
92
|
const target = links(fqdn);
|
|
49
93
|
ensureConfigDir();
|
|
94
|
+
ensureVaultsConfig();
|
|
50
95
|
const { verifier, challenge } = pkcePair();
|
|
51
96
|
const state = randomState();
|
|
52
97
|
const server = await deps.startServer(state);
|
|
@@ -73,8 +118,9 @@ export const runSetup = async (opts, deps = defaultDeps) => {
|
|
|
73
118
|
redirectUri: server.redirectUri,
|
|
74
119
|
verifier,
|
|
75
120
|
});
|
|
76
|
-
|
|
121
|
+
await mutateAuth(() => toAuthState(fqdn, clientId, tokens));
|
|
77
122
|
console.log(chalk.green('\nSigned in.\n'));
|
|
123
|
+
await startDaemon(deps);
|
|
78
124
|
await deps.printStatus();
|
|
79
125
|
}
|
|
80
126
|
finally {
|
|
@@ -99,4 +145,3 @@ export const registerSetup = (program) => {
|
|
|
99
145
|
}
|
|
100
146
|
});
|
|
101
147
|
};
|
|
102
|
-
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isDaemonRunning, resolvePort, signalPid, stopDaemonSafely, } from '../../daemon/lifecycle.js';
|
|
3
|
+
import { health, mismatchNotice, spawnDaemon, syncStatus } from '../../lib/daemon/daemon-client.js';
|
|
4
|
+
const startAction = async (opts = {}) => {
|
|
5
|
+
const port = resolvePort();
|
|
6
|
+
const existing = await health(port);
|
|
7
|
+
if (existing) {
|
|
8
|
+
console.log(chalk.gray(`Daemon already running (pid ${existing.pid}, port ${port}).`));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const noMcp = opts.mcp === false;
|
|
12
|
+
const outcome = await spawnDaemon(port, { noMcp });
|
|
13
|
+
if (!outcome.ok) {
|
|
14
|
+
if (outcome.reason === 'port-in-use') {
|
|
15
|
+
console.error(chalk.red(`Port ${port} is in use by another process - set AGENTAGE_DAEMON_PORT to use a different port.`));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(chalk.red('Daemon failed to start.'));
|
|
19
|
+
}
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const h = await health(port);
|
|
24
|
+
const mcp = noMcp ? 'off' : 'on';
|
|
25
|
+
console.log(chalk.green(`Daemon started (pid ${h?.pid ?? '?'}, port ${port}, mcp ${mcp}).`));
|
|
26
|
+
};
|
|
27
|
+
// Detect the daemon the same way `start` does (tokenless /health), so a legacy daemon that never
|
|
28
|
+
// wrote this config dir's pidfile is not misreported as "not running". With a pidfile we own, use
|
|
29
|
+
// the safe pid-confirming stop; otherwise fall back to signalling the pid /health reports, or - if
|
|
30
|
+
// none is available - report the truth rather than a false "not running".
|
|
31
|
+
const stopAction = async () => {
|
|
32
|
+
if (isDaemonRunning()) {
|
|
33
|
+
if (await stopDaemonSafely())
|
|
34
|
+
console.log(chalk.green('Daemon stopped.'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const port = resolvePort();
|
|
38
|
+
const h = await health(port);
|
|
39
|
+
if (!h) {
|
|
40
|
+
console.log(chalk.gray('Daemon is not running.'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof h.pid === 'number' && signalPid(h.pid)) {
|
|
44
|
+
console.log(chalk.green(`Daemon stopped (pid ${h.pid}).`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const who = typeof h.pid === 'number' ? `pid ${h.pid}` : `port ${port}`;
|
|
48
|
+
console.error(chalk.yellow(`Daemon is running (${who}) but has no pidfile to stop it safely; stop it with: kill ${h.pid ?? '<pid>'}`));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
};
|
|
51
|
+
const statusAction = async () => {
|
|
52
|
+
const port = resolvePort();
|
|
53
|
+
const h = await health(port);
|
|
54
|
+
if (!h) {
|
|
55
|
+
console.log(isDaemonRunning()
|
|
56
|
+
? chalk.yellow(`Daemon pid file present but unreachable on port ${port}.`)
|
|
57
|
+
: chalk.gray('Daemon is not running.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`pid ${h.pid ?? '?'}`);
|
|
61
|
+
console.log(`port ${port}`);
|
|
62
|
+
if (typeof h.uptime === 'number')
|
|
63
|
+
console.log(`uptime ${h.uptime}s`);
|
|
64
|
+
if (typeof h.served === 'number')
|
|
65
|
+
console.log(`served ${h.served}`);
|
|
66
|
+
console.log(`mcp ${h.mcp === false ? 'off' : 'on'}`);
|
|
67
|
+
console.log(`version ${h.version}`);
|
|
68
|
+
const notice = mismatchNotice(h.version);
|
|
69
|
+
if (notice)
|
|
70
|
+
console.log(chalk.yellow(notice));
|
|
71
|
+
const sync = await syncStatus(port);
|
|
72
|
+
if (sync && sync.vaults.length > 0) {
|
|
73
|
+
console.log('sync');
|
|
74
|
+
for (const v of sync.vaults) {
|
|
75
|
+
const cadence = v.intervalSeconds > 0 ? `every ${v.intervalSeconds}s` : 'manual';
|
|
76
|
+
const state = v.running
|
|
77
|
+
? 'running'
|
|
78
|
+
: v.lastError
|
|
79
|
+
? `error: ${v.lastError}`
|
|
80
|
+
: v.lastRun
|
|
81
|
+
? `ok ${v.lastRun}`
|
|
82
|
+
: 'scheduled';
|
|
83
|
+
console.log(` ${v.vault.padEnd(16)} ${cadence.padEnd(12)} ${state}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (sync?.couch && sync.couch.length > 0) {
|
|
87
|
+
console.log('couch sync');
|
|
88
|
+
for (const v of sync.couch) {
|
|
89
|
+
const cadence = v.intervalSeconds > 0 ? `every ${v.intervalSeconds}s` : 'manual';
|
|
90
|
+
const state = v.paused
|
|
91
|
+
? `paused: ${v.paused}`
|
|
92
|
+
: v.running
|
|
93
|
+
? 'running'
|
|
94
|
+
: v.lastError
|
|
95
|
+
? `error: ${v.lastError}`
|
|
96
|
+
: v.lastSync
|
|
97
|
+
? `ok ${v.lastSync}`
|
|
98
|
+
: 'scheduled';
|
|
99
|
+
const pending = v.pendingCount > 0 ? ` ${v.pendingCount} pending` : '';
|
|
100
|
+
console.log(` ${v.vault.padEnd(16)} ${cadence.padEnd(12)} ${state}${pending}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (sync?.discover && sync.discover.roots.length > 0) {
|
|
104
|
+
console.log(`discover roots (${sync.discover.roots.length})`);
|
|
105
|
+
for (const root of sync.discover.roots)
|
|
106
|
+
console.log(` ${root}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
export const registerDaemon = (program) => {
|
|
110
|
+
const daemon = program.command('daemon').description('Manage the local engine daemon');
|
|
111
|
+
daemon
|
|
112
|
+
.command('start')
|
|
113
|
+
.description('Start the daemon (idempotent)')
|
|
114
|
+
.option('--no-mcp', 'do not serve the local MCP endpoint at /mcp')
|
|
115
|
+
.action((opts) => startAction(opts));
|
|
116
|
+
daemon
|
|
117
|
+
.command('stop')
|
|
118
|
+
.description('Stop the daemon')
|
|
119
|
+
.action(() => stopAction());
|
|
120
|
+
daemon
|
|
121
|
+
.command('status')
|
|
122
|
+
.description('Show the daemon pid, uptime, and version')
|
|
123
|
+
.action(() => statusAction());
|
|
124
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type MemoryClient } from '../../lib/memory/memory-client.js';
|
|
2
|
+
export interface CommonOpts {
|
|
3
|
+
vault?: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const runSearch: (query: string, opts: CommonOpts & {
|
|
7
|
+
limit?: string;
|
|
8
|
+
}, client?: MemoryClient) => Promise<void>;
|
|
9
|
+
export declare const runRead: (ref: string, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
|
|
10
|
+
export declare const runWrite: (ref: string, opts: CommonOpts & {
|
|
11
|
+
body?: string;
|
|
12
|
+
frontmatter?: string;
|
|
13
|
+
}, client?: MemoryClient) => Promise<void>;
|
|
14
|
+
export declare const runEdit: (ref: string, opts: CommonOpts & {
|
|
15
|
+
old?: string;
|
|
16
|
+
new?: string;
|
|
17
|
+
body?: string;
|
|
18
|
+
append?: boolean;
|
|
19
|
+
}, client?: MemoryClient) => Promise<void>;
|
|
20
|
+
export declare const runList: (folder: string | undefined, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
|
|
21
|
+
export declare const runDelete: (ref: string, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ensureDaemon } from '../../lib/daemon/daemon-client.js';
|
|
3
|
+
import { daemonDisabled } from '../../lib/daemon/daemon-pref.js';
|
|
4
|
+
import { createDirectClient } from '../../lib/memory/memory-client.js';
|
|
5
|
+
import { loadVaultsConfig } from '../../lib/vault/vaults.js';
|
|
6
|
+
// Default engine path (DO3/DO4): the daemon - single writer, autostarted - when reachable; the
|
|
7
|
+
// in-process DirectClient as a seamless fallback (--no-daemon, sandbox/CI, or fork blocked).
|
|
8
|
+
const resolveClient = async () => {
|
|
9
|
+
const direct = () => createDirectClient(loadVaultsConfig().config);
|
|
10
|
+
if (daemonDisabled())
|
|
11
|
+
return direct();
|
|
12
|
+
const daemon = await ensureDaemon();
|
|
13
|
+
return daemon ?? direct();
|
|
14
|
+
};
|
|
15
|
+
const readStdin = async () => {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for await (const chunk of process.stdin)
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
20
|
+
};
|
|
21
|
+
// --frontmatter must be a JSON object; a raw parser error or a non-object value is unfriendly.
|
|
22
|
+
const parseFrontmatter = (raw) => {
|
|
23
|
+
if (!raw)
|
|
24
|
+
return undefined;
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
31
|
+
throw new Error(`--frontmatter must be a JSON object, e.g. '{"tags":["x"]}': ${detail}`);
|
|
32
|
+
}
|
|
33
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
34
|
+
throw new Error(`--frontmatter must be a JSON object, e.g. '{"tags":["x"]}'`);
|
|
35
|
+
return parsed;
|
|
36
|
+
};
|
|
37
|
+
const emit = (json, data, human) => {
|
|
38
|
+
if (json)
|
|
39
|
+
console.log(JSON.stringify(data, null, 2));
|
|
40
|
+
else
|
|
41
|
+
human();
|
|
42
|
+
};
|
|
43
|
+
// Flatten a folder-tree to the file paths it contains, in tree order.
|
|
44
|
+
const treeFiles = (entries) => entries.flatMap((e) => (e.type === 'file' ? [e.path] : e.entries ? treeFiles(e.entries) : []));
|
|
45
|
+
export const runSearch = async (query, opts, client) => {
|
|
46
|
+
const c = client ?? (await resolveClient());
|
|
47
|
+
const out = await c.search(query, {
|
|
48
|
+
vault: opts.vault,
|
|
49
|
+
limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
|
|
50
|
+
});
|
|
51
|
+
emit(opts.json ?? false, out, () => {
|
|
52
|
+
if (out.results.length === 0)
|
|
53
|
+
console.log('No matches.');
|
|
54
|
+
for (const h of out.results)
|
|
55
|
+
console.log(`${h.path}\n ${chalk.gray(h.snippet)}`);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
export const runRead = async (ref, opts, client) => {
|
|
59
|
+
const c = client ?? (await resolveClient());
|
|
60
|
+
const doc = await c.read(ref, { vault: opts.vault });
|
|
61
|
+
emit(opts.json ?? false, doc, () => console.log(doc.body));
|
|
62
|
+
};
|
|
63
|
+
export const runWrite = async (ref, opts, client) => {
|
|
64
|
+
const c = client ?? (await resolveClient());
|
|
65
|
+
const inlineBody = opts.body !== undefined && opts.body !== '-';
|
|
66
|
+
if (!inlineBody && process.stdin.isTTY)
|
|
67
|
+
throw new Error('provide --body, or pipe content on stdin');
|
|
68
|
+
const body = inlineBody ? opts.body : await readStdin();
|
|
69
|
+
const frontmatter = parseFrontmatter(opts.frontmatter);
|
|
70
|
+
const out = await c.write(ref, body, { vault: opts.vault, frontmatter });
|
|
71
|
+
emit(opts.json ?? false, out, () => console.log(chalk.green(`Wrote ${out.path}`)));
|
|
72
|
+
};
|
|
73
|
+
export const runEdit = async (ref, opts, client) => {
|
|
74
|
+
const c = client ?? (await resolveClient());
|
|
75
|
+
if (opts.old === undefined && opts.body === undefined)
|
|
76
|
+
throw new Error('specify --old/--new for a replacement or --body to overwrite');
|
|
77
|
+
const op = opts.old !== undefined
|
|
78
|
+
? { mode: 'str_replace', old_str: opts.old, new_str: opts.new ?? '' }
|
|
79
|
+
: { mode: opts.append ? 'append' : 'replace', body: opts.body };
|
|
80
|
+
const out = await c.edit(ref, op, { vault: opts.vault });
|
|
81
|
+
emit(opts.json ?? false, out, () => console.log(chalk.green(`Edited ${out.path}`)));
|
|
82
|
+
};
|
|
83
|
+
export const runList = async (folder, opts, client) => {
|
|
84
|
+
const c = client ?? (await resolveClient());
|
|
85
|
+
const out = await c.list(folder, { vault: opts.vault });
|
|
86
|
+
emit(opts.json ?? false, out, () => {
|
|
87
|
+
const files = treeFiles(out.entries);
|
|
88
|
+
if (files.length === 0)
|
|
89
|
+
console.log('No documents.');
|
|
90
|
+
for (const p of files)
|
|
91
|
+
console.log(p);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
export const runDelete = async (ref, opts, client) => {
|
|
95
|
+
const c = client ?? (await resolveClient());
|
|
96
|
+
const out = await c.delete(ref, { vault: opts.vault });
|
|
97
|
+
emit(opts.json ?? false, out, () => console.log(`Deleted ${out.path} (recoverable from git history)`));
|
|
98
|
+
};
|