@agentage/cli 0.0.4 → 0.26.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 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 { readAuth } from '../../lib/fs/config.js';
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(readAuth(), siteFqdn());
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
  };
@@ -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 {
@@ -11,6 +11,7 @@ export interface AuthState {
11
11
  id: string;
12
12
  email: string;
13
13
  };
14
+ kind?: 'pat';
14
15
  }
15
16
  export declare const getConfigDir: () => string;
16
17
  export declare const ensureConfigDir: () => string;
@@ -32,6 +32,7 @@ export interface StatusReport {
32
32
  tokenExpiresAt?: string;
33
33
  note?: string;
34
34
  transient?: boolean;
35
+ pat?: boolean;
35
36
  mismatch?: AuthEnvMismatch;
36
37
  };
37
38
  endpoint: {
@@ -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.4",
3
+ "version": "0.26.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",
@@ -40,15 +40,15 @@
40
40
  "prepublishOnly": "npm run verify"
41
41
  },
42
42
  "dependencies": {
43
- "@agentage/memory-core": "^0.3.2",
44
- "@agentage/server-memory": "^0.0.3",
43
+ "@agentage/memory-core": "^0.4.0",
44
+ "@agentage/server-memory": "^0.2.0",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "chalk": "^5.6.2",
47
- "commander": "^14.0.3",
46
+ "chalk": "^6.0.0",
47
+ "commander": "^15.0.0",
48
48
  "open": "^11.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@anthropic-ai/sdk": "0.110.0",
51
+ "@anthropic-ai/sdk": "0.115.0",
52
52
  "@playwright/test": "latest",
53
53
  "@types/node": "latest",
54
54
  "@typescript-eslint/eslint-plugin": "latest",
@@ -58,7 +58,7 @@
58
58
  "eslint-config-prettier": "latest",
59
59
  "eslint-plugin-prettier": "latest",
60
60
  "prettier": "latest",
61
- "typescript": "latest",
61
+ "typescript": "6.0.3",
62
62
  "vitest": "latest"
63
63
  },
64
64
  "keywords": [