@agentage/cli 0.0.4 → 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/README.md +14 -0
- package/dist/commands/status/status.d.ts +1 -0
- package/dist/commands/status/status.js +6 -4
- package/dist/lib/auth/api.js +2 -0
- package/dist/lib/auth/credentials.d.ts +12 -0
- package/dist/lib/auth/credentials.js +50 -0
- package/dist/lib/auth/provision.js +9 -0
- package/dist/lib/fs/config.d.ts +1 -0
- package/dist/lib/status/status-info.d.ts +1 -0
- package/dist/lib/status/status-info.js +4 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -73,6 +73,20 @@ Prefer to host it yourself? You can point a memory at your own git remote instea
|
|
|
73
73
|
`agentage vault add <name> --git <remote>` - see [`docs/reference.md`](docs/reference.md)
|
|
74
74
|
for details.
|
|
75
75
|
|
|
76
|
+
## Token auth (CI / headless)
|
|
77
|
+
|
|
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:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
export AGENTAGE_TOKEN=aga_...
|
|
84
|
+
agentage status
|
|
85
|
+
```
|
|
86
|
+
|
|
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.
|
|
89
|
+
|
|
76
90
|
## Going deeper
|
|
77
91
|
|
|
78
92
|
- [`docs/architecture.md`](docs/architecture.md) - how the CLI, the local helper, your
|
|
@@ -3,5 +3,6 @@ import { type StatusReport } from '../../lib/status/status-info.js';
|
|
|
3
3
|
export declare const printStatus: (report: StatusReport) => void;
|
|
4
4
|
export declare const runStatus: (opts?: {
|
|
5
5
|
json?: boolean;
|
|
6
|
+
token?: string;
|
|
6
7
|
}) => Promise<void>;
|
|
7
8
|
export declare const registerStatus: (program: Command) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveAuth } from '../../lib/auth/credentials.js';
|
|
3
3
|
import { siteFqdn } from '../../lib/net/origins.js';
|
|
4
4
|
import { formatUptime } from '../../lib/status/format.js';
|
|
5
5
|
import { gatherStatus, } from '../../lib/status/status-info.js';
|
|
@@ -16,12 +16,13 @@ const authLine = (auth) => {
|
|
|
16
16
|
// Env mismatch: the credential is valid but for another target - neutral `!`, not the expired ✗.
|
|
17
17
|
if (auth.mismatch)
|
|
18
18
|
return `${chalk.yellow('!')} ${auth.note ?? 'signed in to another environment'}`;
|
|
19
|
+
const via = auth.pat ? ' via token' : '';
|
|
19
20
|
if (!auth.signedIn)
|
|
20
21
|
return `${mark(false)} ${auth.note ?? 'not signed in'}`;
|
|
21
22
|
// Transient: we hold a valid-looking token but could not re-verify - a non-terminal `~`, never ✗.
|
|
22
23
|
if (auth.transient)
|
|
23
|
-
return `${chalk.yellow('~')} ${auth.note ?? 'signed in'}`;
|
|
24
|
-
return `${mark(true)} signed in (session active)`;
|
|
24
|
+
return `${chalk.yellow('~')} ${auth.note ?? 'signed in'}${via}`;
|
|
25
|
+
return `${mark(true)} signed in${via} (session active)`;
|
|
25
26
|
};
|
|
26
27
|
const updateLine = (update) => {
|
|
27
28
|
switch (update.status.kind) {
|
|
@@ -70,7 +71,7 @@ export const printStatus = (report) => {
|
|
|
70
71
|
console.log(chalk.yellow(`\n${report.update.message}`));
|
|
71
72
|
};
|
|
72
73
|
export const runStatus = async (opts = {}) => {
|
|
73
|
-
const report = await gatherStatus(
|
|
74
|
+
const report = await gatherStatus(resolveAuth({ token: opts.token }), siteFqdn());
|
|
74
75
|
if (opts.json) {
|
|
75
76
|
console.log(JSON.stringify(report, null, 2));
|
|
76
77
|
return;
|
|
@@ -82,5 +83,6 @@ export const registerStatus = (program) => {
|
|
|
82
83
|
.command('status')
|
|
83
84
|
.description('Show CLI, account, and endpoint status')
|
|
84
85
|
.option('--json', 'machine-readable output')
|
|
86
|
+
.option('--token <token>', 'authenticate with a personal access token (aga_...); or set AGENTAGE_TOKEN')
|
|
85
87
|
.action(runStatus);
|
|
86
88
|
};
|
package/dist/lib/auth/api.js
CHANGED
|
@@ -9,6 +9,8 @@ const cliHeaders = () => requestHeaders({ component: 'cli' });
|
|
|
9
9
|
// throws: AuthRequiredError when the grant is dead (401/invalid_grant/invalid_client), else
|
|
10
10
|
// TransientAuthError (429/5xx/network/timeout) - a blip that must not be read as a dead session.
|
|
11
11
|
export const refreshOrThrow = async (auth, links) => {
|
|
12
|
+
if (auth.kind === 'pat')
|
|
13
|
+
throw new AuthRequiredError('personal access token expired or revoked - mint a new one in the dashboard (Settings -> API tokens)');
|
|
12
14
|
if (!auth.tokens.refreshToken)
|
|
13
15
|
throw new AuthRequiredError('no refresh token');
|
|
14
16
|
let fresh;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthState } from '../fs/config.js';
|
|
2
|
+
export declare const PAT_PREFIX = "aga_";
|
|
3
|
+
export declare const TOKEN_ENV_VAR = "AGENTAGE_TOKEN";
|
|
4
|
+
export interface PatOptions {
|
|
5
|
+
token?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const isPatShape: (value: string) => boolean;
|
|
8
|
+
export declare const assertPatShape: (value: string) => string;
|
|
9
|
+
export declare const rawPatToken: (opts?: PatOptions) => string | undefined;
|
|
10
|
+
export declare const patAuthState: (token: string, fqdn?: string) => AuthState;
|
|
11
|
+
export declare const isPatAuth: (auth: AuthState | null) => boolean;
|
|
12
|
+
export declare const resolveAuth: (opts?: PatOptions, read?: () => AuthState | null) => AuthState | null;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readAuth } from '../fs/config.js';
|
|
2
|
+
import { siteFqdn } from '../net/origins.js';
|
|
3
|
+
// Personal access tokens (PATs) are opaque platform tokens minted in the dashboard (Settings ->
|
|
4
|
+
// API tokens). They are `oauthAccessToken` rows, so the cloud memory MCP + the OAuth introspection
|
|
5
|
+
// endpoint validate them exactly like an OAuth access token - anywhere the CLI sends a stored
|
|
6
|
+
// access token, a PAT works as the bearer. They carry no refresh token and are non-interactive:
|
|
7
|
+
// they are the CI / headless credential.
|
|
8
|
+
export const PAT_PREFIX = 'aga_';
|
|
9
|
+
// Env var (CI-friendly, primary) and the per-command flag name for the PAT.
|
|
10
|
+
export const TOKEN_ENV_VAR = 'AGENTAGE_TOKEN';
|
|
11
|
+
// A PAT is opaque; we only assert the `aga_` shape and non-empty tail so a copy-paste slip fails
|
|
12
|
+
// with a clear message instead of a raw 401 later. The server is the real validator.
|
|
13
|
+
export const isPatShape = (value) => value.startsWith(PAT_PREFIX) && value.length > PAT_PREFIX.length;
|
|
14
|
+
export const assertPatShape = (value) => {
|
|
15
|
+
const token = value.trim();
|
|
16
|
+
if (!isPatShape(token))
|
|
17
|
+
throw new Error(`token must be a personal access token starting with "${PAT_PREFIX}" ` +
|
|
18
|
+
`(mint one in the dashboard: Settings -> API tokens)`);
|
|
19
|
+
return token;
|
|
20
|
+
};
|
|
21
|
+
// The raw PAT from flag > env, or undefined when neither is set. An empty/whitespace env value is
|
|
22
|
+
// treated as unset so a stray `export AGENTAGE_TOKEN=` never shadows a stored OAuth session.
|
|
23
|
+
export const rawPatToken = (opts = {}) => {
|
|
24
|
+
if (opts.token !== undefined && opts.token.trim() !== '')
|
|
25
|
+
return opts.token.trim();
|
|
26
|
+
const env = process.env[TOKEN_ENV_VAR];
|
|
27
|
+
if (env !== undefined && env.trim() !== '')
|
|
28
|
+
return env.trim();
|
|
29
|
+
return undefined;
|
|
30
|
+
};
|
|
31
|
+
// A synthesized AuthState backed by a PAT: the token rides through as the bearer everywhere an
|
|
32
|
+
// OAuth access token would. `kind: 'pat'` marks it so refresh / OAuth-session-only paths can fail
|
|
33
|
+
// with a clear message instead of attempting an impossible refresh. siteFqdn is pinned to the
|
|
34
|
+
// current target so the env-mismatch guard never fires (a PAT is not tied to a stored environment).
|
|
35
|
+
export const patAuthState = (token, fqdn = siteFqdn()) => ({
|
|
36
|
+
siteFqdn: fqdn,
|
|
37
|
+
clientId: 'pat',
|
|
38
|
+
kind: 'pat',
|
|
39
|
+
tokens: { accessToken: assertPatShape(token) },
|
|
40
|
+
});
|
|
41
|
+
export const isPatAuth = (auth) => auth?.kind === 'pat';
|
|
42
|
+
// Resolve the active credential with precedence flag > env PAT > stored OAuth. When a PAT is
|
|
43
|
+
// present the OAuth/DCR flow is skipped entirely and the PAT is the bearer; otherwise fall back to
|
|
44
|
+
// the stored OAuth session on disk (null when signed out).
|
|
45
|
+
export const resolveAuth = (opts = {}, read = readAuth) => {
|
|
46
|
+
const pat = rawPatToken(opts);
|
|
47
|
+
if (pat !== undefined)
|
|
48
|
+
return patAuthState(pat);
|
|
49
|
+
return read();
|
|
50
|
+
};
|
|
@@ -26,6 +26,15 @@ export const provisionAccountVault = async (name, deps = defaultProvisionDeps())
|
|
|
26
26
|
message: registeredLocally(name, ' - run `agentage setup` to sync.'),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
// A PAT is an MCP-surface credential; the backend REST provisioning endpoint rejects plain
|
|
30
|
+
// bearers (only session cookies), so it cannot provision an account channel. Fail clearly.
|
|
31
|
+
if (auth.kind === 'pat') {
|
|
32
|
+
return {
|
|
33
|
+
status: 'unauthenticated',
|
|
34
|
+
message: registeredLocally(name, ' - account-channel provisioning needs an interactive session (run `agentage setup`); ' +
|
|
35
|
+
'a personal access token only authorizes memory (MCP) calls.'),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
29
38
|
const links = deps.links();
|
|
30
39
|
let res;
|
|
31
40
|
try {
|
package/dist/lib/fs/config.d.ts
CHANGED
|
@@ -96,12 +96,14 @@ export const gatherStatus = async (auth, fqdn) => {
|
|
|
96
96
|
report.auth = mismatchAuth(mismatch);
|
|
97
97
|
return report;
|
|
98
98
|
}
|
|
99
|
+
// Only carry `pat: true`; omit it for OAuth so existing exact-match reports stay unchanged.
|
|
100
|
+
const patFlag = auth.kind === 'pat' ? { pat: true } : {};
|
|
99
101
|
try {
|
|
100
102
|
const session = await introspectToken(auth, target);
|
|
101
|
-
report.auth = { signedIn: true, tokenExpiresAt: session.expiresAt };
|
|
103
|
+
report.auth = { signedIn: true, tokenExpiresAt: session.expiresAt, ...patFlag };
|
|
102
104
|
}
|
|
103
105
|
catch (err) {
|
|
104
|
-
report.auth = classifyAuthError(err);
|
|
106
|
+
report.auth = { ...classifyAuthError(err), ...patFlag };
|
|
105
107
|
}
|
|
106
108
|
return report;
|
|
107
109
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentage/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "The agentage CLI - connect this machine to agentage from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentage/memory-core": "^0.3.2",
|
|
44
|
-
"@agentage/server-memory": "^0.0
|
|
44
|
+
"@agentage/server-memory": "^0.1.0",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
46
|
"chalk": "^5.6.2",
|
|
47
47
|
"commander": "^14.0.3",
|
|
48
48
|
"open": "^11.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@anthropic-ai/sdk": "0.
|
|
51
|
+
"@anthropic-ai/sdk": "0.112.1",
|
|
52
52
|
"@playwright/test": "latest",
|
|
53
53
|
"@types/node": "latest",
|
|
54
54
|
"@typescript-eslint/eslint-plugin": "latest",
|