@agentage/cli 0.0.3 → 0.0.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +67 -25
  3. package/dist/cli.d.ts +0 -1
  4. package/dist/cli.js +44 -4
  5. package/dist/commands/{setup.d.ts → auth/setup.d.ts} +6 -3
  6. package/dist/commands/{setup.js → auth/setup.js} +56 -11
  7. package/dist/commands/daemon/daemon-cmd.d.ts +2 -0
  8. package/dist/commands/daemon/daemon-cmd.js +124 -0
  9. package/dist/commands/memory/memory-verbs.d.ts +21 -0
  10. package/dist/commands/memory/memory-verbs.js +98 -0
  11. package/dist/commands/memory/memory.d.ts +2 -0
  12. package/dist/commands/memory/memory.js +54 -0
  13. package/dist/commands/{status.d.ts → status/status.d.ts} +1 -2
  14. package/dist/commands/status/status.js +86 -0
  15. package/dist/commands/update/update.d.ts +23 -0
  16. package/dist/commands/update/update.js +89 -0
  17. package/dist/commands/vault/vault-sync.d.ts +15 -0
  18. package/dist/commands/vault/vault-sync.js +100 -0
  19. package/dist/commands/vault/vault.d.ts +22 -0
  20. package/dist/commands/vault/vault.js +128 -0
  21. package/dist/daemon/actions.d.ts +5 -0
  22. package/dist/daemon/actions.js +22 -0
  23. package/dist/daemon/client-provider.d.ts +2 -0
  24. package/dist/daemon/client-provider.js +16 -0
  25. package/dist/daemon/guards.d.ts +3 -0
  26. package/dist/daemon/guards.js +13 -0
  27. package/dist/daemon/lifecycle.d.ts +17 -0
  28. package/dist/daemon/lifecycle.js +151 -0
  29. package/dist/daemon/mcp-http.d.ts +3 -0
  30. package/dist/daemon/mcp-http.js +34 -0
  31. package/dist/daemon/server.d.ts +26 -0
  32. package/dist/daemon/server.js +136 -0
  33. package/dist/daemon-entry.d.ts +7 -0
  34. package/dist/daemon-entry.js +128 -0
  35. package/dist/index.d.ts +0 -1
  36. package/dist/index.js +0 -1
  37. package/dist/lib/auth/api.d.ts +8 -0
  38. package/dist/lib/auth/api.js +97 -0
  39. package/dist/lib/auth/auth-errors.d.ts +7 -0
  40. package/dist/lib/auth/auth-errors.js +22 -0
  41. package/dist/lib/{callback-server.d.ts → auth/callback-server.d.ts} +0 -1
  42. package/dist/lib/{callback-server.js → auth/callback-server.js} +2 -2
  43. package/dist/lib/auth/env-match.d.ts +9 -0
  44. package/dist/lib/auth/env-match.js +17 -0
  45. package/dist/lib/auth/introspect.d.ts +7 -0
  46. package/dist/lib/auth/introspect.js +89 -0
  47. package/dist/lib/{oauth.d.ts → auth/oauth.d.ts} +6 -2
  48. package/dist/lib/{oauth.js → auth/oauth.js} +26 -7
  49. package/dist/lib/auth/provision.d.ts +14 -0
  50. package/dist/lib/auth/provision.js +62 -0
  51. package/dist/lib/daemon/daemon-client.d.ts +39 -0
  52. package/dist/lib/daemon/daemon-client.js +134 -0
  53. package/dist/lib/daemon/daemon-pref.d.ts +2 -0
  54. package/dist/lib/daemon/daemon-pref.js +10 -0
  55. package/dist/lib/{config.d.ts → fs/config.d.ts} +1 -1
  56. package/dist/lib/fs/config.js +55 -0
  57. package/dist/lib/fs/file-lock.d.ts +3 -0
  58. package/dist/lib/fs/file-lock.js +163 -0
  59. package/dist/lib/memory/memory-client.d.ts +30 -0
  60. package/dist/lib/memory/memory-client.js +98 -0
  61. package/dist/lib/net/http.d.ts +6 -0
  62. package/dist/lib/net/http.js +32 -0
  63. package/dist/lib/{origins.d.ts → net/origins.d.ts} +1 -1
  64. package/dist/lib/net/origins.js +39 -0
  65. package/dist/lib/net/user-agent.d.ts +6 -0
  66. package/dist/lib/net/user-agent.js +7 -0
  67. package/dist/lib/status/format.d.ts +1 -0
  68. package/dist/lib/status/format.js +12 -0
  69. package/dist/lib/status/status-info.d.ts +45 -0
  70. package/dist/lib/status/status-info.js +107 -0
  71. package/dist/lib/status/vaults-format.d.ts +2 -0
  72. package/dist/lib/status/vaults-format.js +44 -0
  73. package/dist/lib/status/vaults-status.d.ts +12 -0
  74. package/dist/lib/status/vaults-status.js +53 -0
  75. package/dist/lib/update/update-cache.d.ts +14 -0
  76. package/dist/lib/update/update-cache.js +63 -0
  77. package/dist/lib/update/update-check.d.ts +27 -0
  78. package/dist/lib/update/update-check.js +50 -0
  79. package/dist/lib/update/update-lock.d.ts +2 -0
  80. package/dist/lib/update/update-lock.js +109 -0
  81. package/dist/lib/vault/vault-registry.d.ts +12 -0
  82. package/dist/lib/vault/vault-registry.js +82 -0
  83. package/dist/lib/vault/vaults.d.ts +10 -0
  84. package/dist/lib/vault/vaults.js +66 -0
  85. package/dist/lib/vault/vaults.schema.d.ts +2 -0
  86. package/dist/lib/vault/vaults.schema.js +10 -0
  87. package/dist/mcp/local-server.d.ts +4 -0
  88. package/dist/mcp/local-server.js +26 -0
  89. package/dist/sync/couch/cycle.d.ts +2 -0
  90. package/dist/sync/couch/cycle.js +58 -0
  91. package/dist/sync/couch/discovery.d.ts +22 -0
  92. package/dist/sync/couch/discovery.js +34 -0
  93. package/dist/sync/couch/file-store.d.ts +2 -0
  94. package/dist/sync/couch/file-store.js +47 -0
  95. package/dist/sync/couch/local-commit.d.ts +2 -0
  96. package/dist/sync/couch/local-commit.js +24 -0
  97. package/dist/sync/couch/manager.d.ts +4 -0
  98. package/dist/sync/couch/manager.fixtures.d.ts +20 -0
  99. package/dist/sync/couch/manager.fixtures.js +56 -0
  100. package/dist/sync/couch/manager.js +126 -0
  101. package/dist/sync/couch/manager.types.d.ts +81 -0
  102. package/dist/sync/couch/manager.types.js +1 -0
  103. package/dist/sync/couch/mutation-target.d.ts +5 -0
  104. package/dist/sync/couch/mutation-target.js +33 -0
  105. package/dist/sync/couch/push-on-write.d.ts +3 -0
  106. package/dist/sync/couch/push-on-write.js +33 -0
  107. package/dist/sync/couch/state-store.d.ts +3 -0
  108. package/dist/sync/couch/state-store.js +28 -0
  109. package/dist/sync/couch/targets.d.ts +9 -0
  110. package/dist/sync/couch/targets.js +23 -0
  111. package/dist/sync/couch/wire.d.ts +6 -0
  112. package/dist/sync/couch/wire.js +16 -0
  113. package/dist/sync/discover/watcher.d.ts +26 -0
  114. package/dist/sync/discover/watcher.js +159 -0
  115. package/dist/sync/git/conflict.d.ts +1 -0
  116. package/dist/sync/git/conflict.js +16 -0
  117. package/dist/sync/git/cycle.d.ts +18 -0
  118. package/dist/sync/git/cycle.js +158 -0
  119. package/dist/sync/git/git-exec.d.ts +22 -0
  120. package/dist/sync/git/git-exec.js +56 -0
  121. package/dist/sync/git/manager.d.ts +30 -0
  122. package/dist/sync/git/manager.js +114 -0
  123. package/dist/sync/git/planner.d.ts +15 -0
  124. package/dist/sync/git/planner.js +46 -0
  125. package/dist/sync/git/remote-url.d.ts +3 -0
  126. package/dist/sync/git/remote-url.js +30 -0
  127. package/dist/utils/version.d.ts +0 -1
  128. package/dist/utils/version.js +0 -1
  129. package/package.json +20 -7
  130. package/dist/cli.d.ts.map +0 -1
  131. package/dist/cli.js.map +0 -1
  132. package/dist/commands/setup.d.ts.map +0 -1
  133. package/dist/commands/setup.js.map +0 -1
  134. package/dist/commands/status.d.ts.map +0 -1
  135. package/dist/commands/status.js +0 -36
  136. package/dist/commands/status.js.map +0 -1
  137. package/dist/index.d.ts.map +0 -1
  138. package/dist/index.js.map +0 -1
  139. package/dist/lib/api.d.ts +0 -12
  140. package/dist/lib/api.d.ts.map +0 -1
  141. package/dist/lib/api.js +0 -46
  142. package/dist/lib/api.js.map +0 -1
  143. package/dist/lib/callback-server.d.ts.map +0 -1
  144. package/dist/lib/callback-server.js.map +0 -1
  145. package/dist/lib/config.d.ts.map +0 -1
  146. package/dist/lib/config.js +0 -34
  147. package/dist/lib/config.js.map +0 -1
  148. package/dist/lib/oauth.d.ts.map +0 -1
  149. package/dist/lib/oauth.js.map +0 -1
  150. package/dist/lib/origins.d.ts.map +0 -1
  151. package/dist/lib/origins.js +0 -26
  152. package/dist/lib/origins.js.map +0 -1
  153. package/dist/lib/status-info.d.ts +0 -18
  154. package/dist/lib/status-info.d.ts.map +0 -1
  155. package/dist/lib/status-info.js +0 -39
  156. package/dist/lib/status-info.js.map +0 -1
  157. package/dist/utils/version.d.ts.map +0 -1
  158. 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,96 @@
3
3
  [![CI](https://github.com/agentage/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/agentage/cli/actions/workflows/ci.yml)
4
4
  [![npm version](https://badge.fury.io/js/%40agentage%2Fcli.svg)](https://badge.fury.io/js/%40agentage%2Fcli)
5
5
 
6
- The [agentage](https://agentage.io) command line.
6
+ Give every AI on your machine one shared memory - plain markdown you own, synced across your devices.
7
7
 
8
- ## Install
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
+ ![Architecture](https://github.com/agentage/cli/raw/master/docs/architecture.svg)
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
- Requires Node.js >= 22.
29
+ **2. Create a memory.** This makes a plain folder of markdown files at `~/vaults/notes`.
30
+ No sign-in needed.
15
31
 
16
- ## Commands
17
-
18
- ### `agentage setup`
32
+ ```bash
33
+ agentage vault add notes --local
34
+ ```
19
35
 
20
- Signs this machine in to your agentage account. Opens a browser for OAuth 2.1
21
- sign-in (PKCE) and stores the resulting tokens in `~/.agentage/auth.json`
22
- (mode 0600). No passwords ever touch the terminal.
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.
23
38
 
24
39
  ```bash
25
- agentage setup # browser sign-in, then prints status
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
40
+ claude mcp add --transport http agentage http://127.0.0.1:4243/mcp
29
41
  ```
30
42
 
31
- ### `agentage status`
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.
45
+
46
+ ## What your AI can do
47
+
48
+ Once connected, your AI can:
32
49
 
33
- Shows this machine's connection, one line per fact: CLI version, target,
34
- sign-in state, and endpoint reachability.
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:
35
62
 
36
63
  ```bash
37
- agentage status
38
- agentage status --json
64
+ agentage setup
39
65
  ```
40
66
 
41
- ## Environment
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
+ ## Going deeper
42
77
 
43
- | Variable | Purpose | Default |
44
- | --------------------- | --------------------------- | ------------- |
45
- | `AGENTAGE_SITE_FQDN` | Target host | `agentage.io` |
46
- | `AGENTAGE_CONFIG_DIR` | Credential/config directory | `~/.agentage` |
78
+ - [`docs/architecture.md`](docs/architecture.md) - how the CLI, the local helper, your
79
+ files, and sync fit together.
80
+ - [`docs/reference.md`](docs/reference.md) - every command and flag, the full sync
81
+ options, MCP details, and environment variables.
47
82
 
48
83
  ## Development
49
84
 
50
85
  ```bash
51
86
  npm ci
52
- npm run verify # type-check + lint + format + unit tests + build
53
- npm run build && npm run test:e2e # live e2e (Playwright) against the dev stack
87
+ npm run verify # type-check + lint + format:check + unit tests + build
88
+ ```
89
+
90
+ End-to-end tests (Playwright) run in tiers - the offline tiers need no network or
91
+ account. See [`docs/reference.md`](docs/reference.md) for more.
92
+
93
+ ```bash
94
+ npm run build && npm run test:e2e # all e2e tiers
95
+ npm run build && npm run test:e2e -- --grep @offline # offline tiers only
54
96
  ```
55
97
 
56
98
  ## License
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=cli.d.ts.map
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 { registerSetup } from './commands/setup.js';
4
- import { registerStatus } from './commands/status.js';
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.name('agentage').description('The agentage CLI').version(VERSION);
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 '../lib/callback-server.js';
3
- import { exchangeCode, registerClient, revokeToken } from '../lib/oauth.js';
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 '../lib/callback-server.js';
3
- import { deleteAuth, ensureConfigDir, readAuth, saveAuth } from '../lib/config.js';
4
- import { buildAuthorizeUrl, exchangeCode, pkcePair, randomState, registerClient, revokeToken, } from '../lib/oauth.js';
5
- import { links, siteFqdn } from '../lib/origins.js';
6
- import { runStatus } from './status.js';
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
- if (readAuth() && !opts.reauth) {
43
- console.log(`Already signed in. Run ${chalk.white('agentage setup --reauth')} to sign in again.\n`);
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
- saveAuth(toAuthState(fqdn, clientId, tokens));
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,2 @@
1
+ import { type Command } from 'commander';
2
+ export declare const registerDaemon: (program: Command) => void;
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ import { type Command } from 'commander';
2
+ export declare const registerMemory: (program: Command) => void;