@agentteams/cli 0.1.82 → 0.1.84
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/.eslintcache +1 -1
- package/README.md +4 -3
- package/dist/auth/activeCredential.d.ts +22 -0
- package/dist/auth/activeCredential.d.ts.map +1 -0
- package/dist/auth/activeCredential.js +25 -0
- package/dist/auth/activeCredential.js.map +1 -0
- package/dist/auth/credentialStore.d.ts +89 -0
- package/dist/auth/credentialStore.d.ts.map +1 -0
- package/dist/auth/credentialStore.js +254 -0
- package/dist/auth/credentialStore.js.map +1 -0
- package/dist/auth/personalTokenClient.d.ts +155 -0
- package/dist/auth/personalTokenClient.d.ts.map +1 -0
- package/dist/auth/personalTokenClient.js +312 -0
- package/dist/auth/personalTokenClient.js.map +1 -0
- package/dist/auth/personalTokenStore.d.ts +16 -0
- package/dist/auth/personalTokenStore.d.ts.map +1 -0
- package/dist/auth/personalTokenStore.js +27 -0
- package/dist/auth/personalTokenStore.js.map +1 -0
- package/dist/commands/agentConfig.d.ts.map +1 -1
- package/dist/commands/agentConfig.js +8 -6
- package/dist/commands/agentConfig.js.map +1 -1
- package/dist/commands/auth.d.ts +97 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +245 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +16 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/convention.d.ts +11 -0
- package/dist/commands/convention.d.ts.map +1 -1
- package/dist/commands/convention.js +35 -11
- package/dist/commands/convention.js.map +1 -1
- package/dist/commands/dependency.d.ts.map +1 -1
- package/dist/commands/dependency.js +8 -6
- package/dist/commands/dependency.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +75 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +19 -14
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts +18 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +114 -20
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +101 -15
- package/dist/index.js.map +1 -1
- package/dist/mcp/context.d.ts +15 -1
- package/dist/mcp/context.d.ts.map +1 -1
- package/dist/mcp/context.js +24 -5
- package/dist/mcp/context.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +5 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +25 -22
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp-registration/atomicWrite.d.ts +9 -3
- package/dist/mcp-registration/atomicWrite.d.ts.map +1 -1
- package/dist/mcp-registration/atomicWrite.js +58 -15
- package/dist/mcp-registration/atomicWrite.js.map +1 -1
- package/dist/mcp-registration/clients.d.ts.map +1 -1
- package/dist/mcp-registration/clients.js +1 -9
- package/dist/mcp-registration/clients.js.map +1 -1
- package/dist/mcp-registration/doctor.d.ts +38 -0
- package/dist/mcp-registration/doctor.d.ts.map +1 -0
- package/dist/mcp-registration/doctor.js +379 -0
- package/dist/mcp-registration/doctor.js.map +1 -0
- package/dist/mcp-registration/index.d.ts +7 -5
- package/dist/mcp-registration/index.d.ts.map +1 -1
- package/dist/mcp-registration/index.js +46 -21
- package/dist/mcp-registration/index.js.map +1 -1
- package/dist/mcp-registration/install.d.ts.map +1 -1
- package/dist/mcp-registration/install.js +12 -15
- package/dist/mcp-registration/install.js.map +1 -1
- package/dist/mcp-registration/render.d.ts +8 -6
- package/dist/mcp-registration/render.d.ts.map +1 -1
- package/dist/mcp-registration/render.js +15 -36
- package/dist/mcp-registration/render.js.map +1 -1
- package/dist/mcp-registration/serverSpec.d.ts +31 -18
- package/dist/mcp-registration/serverSpec.d.ts.map +1 -1
- package/dist/mcp-registration/serverSpec.js +36 -20
- package/dist/mcp-registration/serverSpec.js.map +1 -1
- package/dist/mcp-registration/types.d.ts +0 -3
- package/dist/mcp-registration/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/apiKeyInput.d.ts +16 -0
- package/dist/utils/apiKeyInput.d.ts.map +1 -0
- package/dist/utils/apiKeyInput.js +47 -0
- package/dist/utils/apiKeyInput.js.map +1 -0
- package/dist/utils/authServer.d.ts +39 -4
- package/dist/utils/authServer.d.ts.map +1 -1
- package/dist/utils/authServer.js +146 -62
- package/dist/utils/authServer.js.map +1 -1
- package/dist/utils/config.d.ts +121 -10
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +204 -5
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +16 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/httpClient.d.ts.map +1 -1
- package/dist/utils/httpClient.js +25 -0
- package/dist/utils/httpClient.js.map +1 -1
- package/dist/utils/initOutput.d.ts.map +1 -1
- package/dist/utils/initOutput.js +30 -0
- package/dist/utils/initOutput.js.map +1 -1
- package/package.json +1 -1
- package/web-compatibility.json +4 -0
package/README.md
CHANGED
|
@@ -351,9 +351,10 @@ Configuration is merged in this priority order (highest first):
|
|
|
351
351
|
}
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
-
`apiUrl` is omitted for the default API. When `
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
`apiUrl` is omitted for the default API. When `AGENTTEAMS_API_URL` points somewhere else — a
|
|
355
|
+
local API or the dev environment — `init` persists that value so later commands keep using the
|
|
356
|
+
same API. The API destination comes from the CLI's own environment only; the browser callback
|
|
357
|
+
never supplies it.
|
|
357
358
|
|
|
358
359
|
### Environment Variable Example
|
|
359
360
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential this process is currently authenticating with, in a form the
|
|
3
|
+
* HTTP layer can act on.
|
|
4
|
+
*
|
|
5
|
+
* `utils/httpClient.ts` must be able to answer "can this 401 be fixed by a
|
|
6
|
+
* refresh?" without importing the config loader (which imports the token client,
|
|
7
|
+
* which would import the HTTP layer back). A one-slot registry keeps that
|
|
8
|
+
* dependency edge from existing at all.
|
|
9
|
+
*
|
|
10
|
+
* Only the personal-token path registers anything here. A legacy `key_` run
|
|
11
|
+
* leaves the slot empty, which is what makes "never attempt a refresh on the old
|
|
12
|
+
* path" a structural property rather than a conditional the caller must remember.
|
|
13
|
+
*/
|
|
14
|
+
export interface RefreshableCredential {
|
|
15
|
+
/** Discard the cached access token and fetch a new one. Null means "could not". */
|
|
16
|
+
refresh(): Promise<string | null>;
|
|
17
|
+
}
|
|
18
|
+
export declare function setActiveCredential(credential: RefreshableCredential | null): void;
|
|
19
|
+
export declare function getActiveCredential(): RefreshableCredential | null;
|
|
20
|
+
/** Test-only: return the process to the "no refreshable credential" state. */
|
|
21
|
+
export declare function resetActiveCredentialForTests(): void;
|
|
22
|
+
//# sourceMappingURL=activeCredential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activeCredential.d.ts","sourceRoot":"","sources":["../../src/auth/activeCredential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACnC;AAID,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAElF;AAED,wBAAgB,mBAAmB,IAAI,qBAAqB,GAAG,IAAI,CAElE;AAED,8EAA8E;AAC9E,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential this process is currently authenticating with, in a form the
|
|
3
|
+
* HTTP layer can act on.
|
|
4
|
+
*
|
|
5
|
+
* `utils/httpClient.ts` must be able to answer "can this 401 be fixed by a
|
|
6
|
+
* refresh?" without importing the config loader (which imports the token client,
|
|
7
|
+
* which would import the HTTP layer back). A one-slot registry keeps that
|
|
8
|
+
* dependency edge from existing at all.
|
|
9
|
+
*
|
|
10
|
+
* Only the personal-token path registers anything here. A legacy `key_` run
|
|
11
|
+
* leaves the slot empty, which is what makes "never attempt a refresh on the old
|
|
12
|
+
* path" a structural property rather than a conditional the caller must remember.
|
|
13
|
+
*/
|
|
14
|
+
let active = null;
|
|
15
|
+
export function setActiveCredential(credential) {
|
|
16
|
+
active = credential;
|
|
17
|
+
}
|
|
18
|
+
export function getActiveCredential() {
|
|
19
|
+
return active;
|
|
20
|
+
}
|
|
21
|
+
/** Test-only: return the process to the "no refreshable credential" state. */
|
|
22
|
+
export function resetActiveCredentialForTests() {
|
|
23
|
+
active = null;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=activeCredential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activeCredential.js","sourceRoot":"","sources":["../../src/auth/activeCredential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,IAAI,MAAM,GAAiC,IAAI,CAAC;AAEhD,MAAM,UAAU,mBAAmB,CAAC,UAAwC;IAC1E,MAAM,GAAG,UAAU,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,6BAA6B;IAC3C,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-protected credential storage for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* The CLI ships as a plain npm package, so a native addon (keytar and friends)
|
|
5
|
+
* would add a build toolchain requirement to every install. Instead each
|
|
6
|
+
* platform's own credential tool is driven as a subprocess:
|
|
7
|
+
*
|
|
8
|
+
* macOS `security` (login keychain)
|
|
9
|
+
* Windows `powershell` (Windows.Security.Credentials.PasswordVault)
|
|
10
|
+
* Linux `secret-tool` (libsecret / Secret Service)
|
|
11
|
+
*
|
|
12
|
+
* When none of them is usable the store **refuses to persist**: nothing is
|
|
13
|
+
* written to disk in plaintext, the secret is kept for this process only, and
|
|
14
|
+
* the caller is told so it can point CI users at the long-lived `key_` path
|
|
15
|
+
* instead. This mirrors `desktop/src/main/localAgent/credentialStore.ts`, which
|
|
16
|
+
* declines to write when `safeStorage` is unavailable — the two must not drift.
|
|
17
|
+
*/
|
|
18
|
+
/** One keychain "service" groups every CLI credential under a single name. */
|
|
19
|
+
export declare const CREDENTIAL_SERVICE = "agentteams-cli";
|
|
20
|
+
export type CredentialBackendId = 'macos-keychain' | 'windows-credential-manager' | 'libsecret' | 'none';
|
|
21
|
+
/**
|
|
22
|
+
* Why the store can (or cannot) persist.
|
|
23
|
+
*
|
|
24
|
+
* `WRITE_FAILED` is the one that is only knowable after trying: the probe below
|
|
25
|
+
* is deliberately cheap, so a locked macOS keychain ("User interaction is not
|
|
26
|
+
* allowed") or a Linux box with `secret-tool` installed but no Secret Service
|
|
27
|
+
* daemon running still reports `OK` until the first write is attempted.
|
|
28
|
+
*/
|
|
29
|
+
export type CredentialStoreReason = 'OK' | 'NO_BACKEND' | 'UNSUPPORTED_PLATFORM' | 'WRITE_FAILED';
|
|
30
|
+
export interface CredentialStoreStatus {
|
|
31
|
+
backend: CredentialBackendId;
|
|
32
|
+
/** false → nothing is ever written to disk; the secret lives in this process only. */
|
|
33
|
+
persisted: boolean;
|
|
34
|
+
reason: CredentialStoreReason;
|
|
35
|
+
/** Masked backend error, present only once a write has actually failed. */
|
|
36
|
+
detail?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CredentialSaveOutcome {
|
|
39
|
+
persisted: boolean;
|
|
40
|
+
reason: CredentialStoreReason;
|
|
41
|
+
/** Masked backend error, present only when `reason` is `WRITE_FAILED`. */
|
|
42
|
+
detail?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface CommandResult {
|
|
45
|
+
/** null when the executable could not be spawned at all. */
|
|
46
|
+
status: number | null;
|
|
47
|
+
stdout: string;
|
|
48
|
+
stderr: string;
|
|
49
|
+
}
|
|
50
|
+
export interface CredentialCommand {
|
|
51
|
+
command: string;
|
|
52
|
+
args: string[];
|
|
53
|
+
/** Written to the child's stdin. Never placed on argv — argv is world-readable via `ps`. */
|
|
54
|
+
input?: string;
|
|
55
|
+
/** Extra environment for the child. Used on Windows so the secret stays off argv. */
|
|
56
|
+
env?: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
export type CommandRunner = (command: CredentialCommand) => CommandResult;
|
|
59
|
+
export interface CredentialStore {
|
|
60
|
+
status(): CredentialStoreStatus;
|
|
61
|
+
read(account: string): string | null;
|
|
62
|
+
save(account: string, secret: string): CredentialSaveOutcome;
|
|
63
|
+
remove(account: string): void;
|
|
64
|
+
}
|
|
65
|
+
export interface CreateCredentialStoreOptions {
|
|
66
|
+
runner?: CommandRunner;
|
|
67
|
+
platform?: NodeJS.Platform;
|
|
68
|
+
service?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function resolveBackendId(platform: NodeJS.Platform): CredentialBackendId;
|
|
71
|
+
/** Cheap "is this backend usable at all" call. Exit code 0 means yes. */
|
|
72
|
+
export declare function buildProbeCommand(backend: CredentialBackendId): CredentialCommand | null;
|
|
73
|
+
export declare function buildReadCommand(backend: CredentialBackendId, service: string, account: string): CredentialCommand | null;
|
|
74
|
+
export declare function buildSaveCommand(backend: CredentialBackendId, service: string, account: string, secret: string): CredentialCommand | null;
|
|
75
|
+
export declare function buildRemoveCommand(backend: CredentialBackendId, service: string, account: string): CredentialCommand | null;
|
|
76
|
+
/**
|
|
77
|
+
* Strip a secret out of anything that is about to be surfaced.
|
|
78
|
+
*
|
|
79
|
+
* Backend tools do not normally echo the value back, but "normally" is not a
|
|
80
|
+
* guarantee worth betting a token on — and this store is the last boundary
|
|
81
|
+
* before text reaches a log or an error message.
|
|
82
|
+
*/
|
|
83
|
+
export declare function maskSecret(text: string, secret: string): string;
|
|
84
|
+
export declare function createCredentialStore(options?: CreateCredentialStoreOptions): CredentialStore;
|
|
85
|
+
/** Process-wide store. Tests build their own through {@link createCredentialStore}. */
|
|
86
|
+
export declare function getCredentialStore(): CredentialStore;
|
|
87
|
+
/** Test-only: drop the cached process-wide store. */
|
|
88
|
+
export declare function resetCredentialStoreForTests(): void;
|
|
89
|
+
//# sourceMappingURL=credentialStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialStore.d.ts","sourceRoot":"","sources":["../../src/auth/credentialStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-protected credential storage for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* The CLI ships as a plain npm package, so a native addon (keytar and friends)
|
|
5
|
+
* would add a build toolchain requirement to every install. Instead each
|
|
6
|
+
* platform's own credential tool is driven as a subprocess:
|
|
7
|
+
*
|
|
8
|
+
* macOS `security` (login keychain)
|
|
9
|
+
* Windows `powershell` (Windows.Security.Credentials.PasswordVault)
|
|
10
|
+
* Linux `secret-tool` (libsecret / Secret Service)
|
|
11
|
+
*
|
|
12
|
+
* When none of them is usable the store **refuses to persist**: nothing is
|
|
13
|
+
* written to disk in plaintext, the secret is kept for this process only, and
|
|
14
|
+
* the caller is told so it can point CI users at the long-lived `key_` path
|
|
15
|
+
* instead. This mirrors `desktop/src/main/localAgent/credentialStore.ts`, which
|
|
16
|
+
* declines to write when `safeStorage` is unavailable — the two must not drift.
|
|
17
|
+
*/
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
19
|
+
/** One keychain "service" groups every CLI credential under a single name. */
|
|
20
|
+
export const CREDENTIAL_SERVICE = 'agentteams-cli';
|
|
21
|
+
const WINDOWS_SERVICE_ENV = 'AGENTTEAMS_CREDENTIAL_SERVICE';
|
|
22
|
+
const WINDOWS_ACCOUNT_ENV = 'AGENTTEAMS_CREDENTIAL_ACCOUNT';
|
|
23
|
+
const WINDOWS_SECRET_ENV = 'AGENTTEAMS_CREDENTIAL_SECRET';
|
|
24
|
+
/**
|
|
25
|
+
* PasswordVault is WinRT, so it is projected into PowerShell rather than called
|
|
26
|
+
* directly. Every script starts from the same preamble; the caller appends the
|
|
27
|
+
* operation. Inputs arrive through the environment because a PowerShell command
|
|
28
|
+
* line is as visible as any other argv.
|
|
29
|
+
*/
|
|
30
|
+
const WINDOWS_PREAMBLE = [
|
|
31
|
+
'$ErrorActionPreference = "Stop"',
|
|
32
|
+
'[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]',
|
|
33
|
+
'$vault = New-Object Windows.Security.Credentials.PasswordVault',
|
|
34
|
+
].join('; ');
|
|
35
|
+
function powershellCommand(script, env) {
|
|
36
|
+
const command = {
|
|
37
|
+
command: 'powershell',
|
|
38
|
+
args: ['-NoProfile', '-NonInteractive', '-Command', `${WINDOWS_PREAMBLE}; ${script}`],
|
|
39
|
+
};
|
|
40
|
+
if (env)
|
|
41
|
+
command.env = env;
|
|
42
|
+
return command;
|
|
43
|
+
}
|
|
44
|
+
export function resolveBackendId(platform) {
|
|
45
|
+
switch (platform) {
|
|
46
|
+
case 'darwin':
|
|
47
|
+
return 'macos-keychain';
|
|
48
|
+
case 'win32':
|
|
49
|
+
return 'windows-credential-manager';
|
|
50
|
+
case 'linux':
|
|
51
|
+
return 'libsecret';
|
|
52
|
+
default:
|
|
53
|
+
return 'none';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Cheap "is this backend usable at all" call. Exit code 0 means yes. */
|
|
57
|
+
export function buildProbeCommand(backend) {
|
|
58
|
+
switch (backend) {
|
|
59
|
+
case 'macos-keychain':
|
|
60
|
+
return { command: 'security', args: ['list-keychains'] };
|
|
61
|
+
case 'libsecret':
|
|
62
|
+
return { command: 'secret-tool', args: ['--version'] };
|
|
63
|
+
case 'windows-credential-manager':
|
|
64
|
+
return powershellCommand('exit 0');
|
|
65
|
+
default:
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function buildReadCommand(backend, service, account) {
|
|
70
|
+
switch (backend) {
|
|
71
|
+
case 'macos-keychain':
|
|
72
|
+
return { command: 'security', args: ['find-generic-password', '-a', account, '-s', service, '-w'] };
|
|
73
|
+
case 'libsecret':
|
|
74
|
+
return { command: 'secret-tool', args: ['lookup', 'service', service, 'account', account] };
|
|
75
|
+
case 'windows-credential-manager':
|
|
76
|
+
return powershellCommand(`$c = $vault.Retrieve($env:${WINDOWS_SERVICE_ENV}, $env:${WINDOWS_ACCOUNT_ENV}); ` +
|
|
77
|
+
'$c.RetrievePassword(); [Console]::Out.Write($c.Password)', { [WINDOWS_SERVICE_ENV]: service, [WINDOWS_ACCOUNT_ENV]: account });
|
|
78
|
+
default:
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function buildSaveCommand(backend, service, account, secret) {
|
|
83
|
+
switch (backend) {
|
|
84
|
+
case 'macos-keychain':
|
|
85
|
+
// `-w` without a value makes `security` read the password from stdin, and
|
|
86
|
+
// it asks for it twice ("retype password"), so the value is written twice.
|
|
87
|
+
// Passing `-w <secret>` instead would expose the token on argv.
|
|
88
|
+
return {
|
|
89
|
+
command: 'security',
|
|
90
|
+
args: ['add-generic-password', '-a', account, '-s', service, '-U', '-w'],
|
|
91
|
+
input: `${secret}\n${secret}\n`,
|
|
92
|
+
};
|
|
93
|
+
case 'libsecret':
|
|
94
|
+
return {
|
|
95
|
+
command: 'secret-tool',
|
|
96
|
+
args: ['store', '--label', `${service} (${account})`, 'service', service, 'account', account],
|
|
97
|
+
input: secret,
|
|
98
|
+
};
|
|
99
|
+
case 'windows-credential-manager':
|
|
100
|
+
return powershellCommand('$vault.Add((New-Object Windows.Security.Credentials.PasswordCredential(' +
|
|
101
|
+
`$env:${WINDOWS_SERVICE_ENV}, $env:${WINDOWS_ACCOUNT_ENV}, $env:${WINDOWS_SECRET_ENV})))`, { [WINDOWS_SERVICE_ENV]: service, [WINDOWS_ACCOUNT_ENV]: account, [WINDOWS_SECRET_ENV]: secret });
|
|
102
|
+
default:
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export function buildRemoveCommand(backend, service, account) {
|
|
107
|
+
switch (backend) {
|
|
108
|
+
case 'macos-keychain':
|
|
109
|
+
return { command: 'security', args: ['delete-generic-password', '-a', account, '-s', service] };
|
|
110
|
+
case 'libsecret':
|
|
111
|
+
return { command: 'secret-tool', args: ['clear', 'service', service, 'account', account] };
|
|
112
|
+
case 'windows-credential-manager':
|
|
113
|
+
return powershellCommand(`$c = $vault.Retrieve($env:${WINDOWS_SERVICE_ENV}, $env:${WINDOWS_ACCOUNT_ENV}); $vault.Remove($c)`, { [WINDOWS_SERVICE_ENV]: service, [WINDOWS_ACCOUNT_ENV]: account });
|
|
114
|
+
default:
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const defaultRunner = (command) => {
|
|
119
|
+
const result = spawnSync(command.command, command.args, {
|
|
120
|
+
encoding: 'utf-8',
|
|
121
|
+
input: command.input,
|
|
122
|
+
windowsHide: true,
|
|
123
|
+
env: command.env ? { ...process.env, ...command.env } : process.env,
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
status: result.error ? null : result.status,
|
|
127
|
+
stdout: result.stdout ?? '',
|
|
128
|
+
stderr: result.stderr ?? '',
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Strip a secret out of anything that is about to be surfaced.
|
|
133
|
+
*
|
|
134
|
+
* Backend tools do not normally echo the value back, but "normally" is not a
|
|
135
|
+
* guarantee worth betting a token on — and this store is the last boundary
|
|
136
|
+
* before text reaches a log or an error message.
|
|
137
|
+
*/
|
|
138
|
+
export function maskSecret(text, secret) {
|
|
139
|
+
if (!secret)
|
|
140
|
+
return text;
|
|
141
|
+
return text.split(secret).join('***');
|
|
142
|
+
}
|
|
143
|
+
export function createCredentialStore(options = {}) {
|
|
144
|
+
const runner = options.runner ?? defaultRunner;
|
|
145
|
+
const platform = options.platform ?? process.platform;
|
|
146
|
+
const service = options.service ?? CREDENTIAL_SERVICE;
|
|
147
|
+
const backend = resolveBackendId(platform);
|
|
148
|
+
/**
|
|
149
|
+
* Session-only fallback, and a read cache on top of a working backend.
|
|
150
|
+
* A one-shot command barely benefits, but `agentteams mcp` runs for hours and
|
|
151
|
+
* would otherwise re-shell out on every credential resolution.
|
|
152
|
+
*/
|
|
153
|
+
const memory = new Map();
|
|
154
|
+
let availability = null;
|
|
155
|
+
/**
|
|
156
|
+
* Set once a write is rejected by a backend that passed the probe. From then
|
|
157
|
+
* on `status()` tells the truth — "this store looks present but will not keep
|
|
158
|
+
* anything" — instead of promising a durability it has already failed to give.
|
|
159
|
+
*/
|
|
160
|
+
let writeFailureDetail = null;
|
|
161
|
+
const isAvailable = () => {
|
|
162
|
+
if (availability !== null)
|
|
163
|
+
return availability;
|
|
164
|
+
const probe = buildProbeCommand(backend);
|
|
165
|
+
if (!probe) {
|
|
166
|
+
availability = false;
|
|
167
|
+
return availability;
|
|
168
|
+
}
|
|
169
|
+
// A backend that cannot even be probed is treated as absent rather than as
|
|
170
|
+
// an error: the caller's job is to fall back, not to fail the command.
|
|
171
|
+
availability = runner(probe).status === 0;
|
|
172
|
+
return availability;
|
|
173
|
+
};
|
|
174
|
+
const status = () => {
|
|
175
|
+
if (backend === 'none') {
|
|
176
|
+
return { backend, persisted: false, reason: 'UNSUPPORTED_PLATFORM' };
|
|
177
|
+
}
|
|
178
|
+
if (writeFailureDetail !== null) {
|
|
179
|
+
return { backend, persisted: false, reason: 'WRITE_FAILED', detail: writeFailureDetail };
|
|
180
|
+
}
|
|
181
|
+
const available = isAvailable();
|
|
182
|
+
return { backend, persisted: available, reason: available ? 'OK' : 'NO_BACKEND' };
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
status,
|
|
186
|
+
read(account) {
|
|
187
|
+
const cached = memory.get(account);
|
|
188
|
+
if (cached !== undefined)
|
|
189
|
+
return cached;
|
|
190
|
+
if (!isAvailable())
|
|
191
|
+
return null;
|
|
192
|
+
const command = buildReadCommand(backend, service, account);
|
|
193
|
+
if (!command)
|
|
194
|
+
return null;
|
|
195
|
+
const result = runner(command);
|
|
196
|
+
// A missing item (`security` 44, `secret-tool` 1) and a broken backend
|
|
197
|
+
// both mean the same thing to the caller: there is no credential to use.
|
|
198
|
+
if (result.status !== 0)
|
|
199
|
+
return null;
|
|
200
|
+
// `security -w` and `secret-tool lookup` both terminate the value with a
|
|
201
|
+
// newline; a token never legitimately ends in whitespace.
|
|
202
|
+
const secret = result.stdout.replace(/\r?\n$/, '');
|
|
203
|
+
if (secret.length === 0)
|
|
204
|
+
return null;
|
|
205
|
+
memory.set(account, secret);
|
|
206
|
+
return secret;
|
|
207
|
+
},
|
|
208
|
+
save(account, secret) {
|
|
209
|
+
memory.set(account, secret);
|
|
210
|
+
if (!isAvailable()) {
|
|
211
|
+
return { persisted: false, reason: backend === 'none' ? 'UNSUPPORTED_PLATFORM' : 'NO_BACKEND' };
|
|
212
|
+
}
|
|
213
|
+
const command = buildSaveCommand(backend, service, account, secret);
|
|
214
|
+
if (!command) {
|
|
215
|
+
return { persisted: false, reason: 'NO_BACKEND' };
|
|
216
|
+
}
|
|
217
|
+
const result = runner(command);
|
|
218
|
+
if (result.status !== 0) {
|
|
219
|
+
// Never thrown: the caller's contract is "you may not get persistence",
|
|
220
|
+
// and a rejected write is just another way of not getting it. Throwing
|
|
221
|
+
// here would kill the documented session-only fallback at the exact
|
|
222
|
+
// moment it is needed.
|
|
223
|
+
writeFailureDetail = maskSecret(`${result.stderr}`.trim(), secret) || 'the credential store rejected the write';
|
|
224
|
+
return { persisted: false, reason: 'WRITE_FAILED', detail: writeFailureDetail };
|
|
225
|
+
}
|
|
226
|
+
writeFailureDetail = null;
|
|
227
|
+
return { persisted: true, reason: 'OK' };
|
|
228
|
+
},
|
|
229
|
+
remove(account) {
|
|
230
|
+
memory.delete(account);
|
|
231
|
+
if (!isAvailable())
|
|
232
|
+
return;
|
|
233
|
+
const command = buildRemoveCommand(backend, service, account);
|
|
234
|
+
if (!command)
|
|
235
|
+
return;
|
|
236
|
+
// Removing something that is already gone is success as far as the caller
|
|
237
|
+
// is concerned, so a non-zero status is not escalated.
|
|
238
|
+
runner(command);
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
let sharedStore = null;
|
|
243
|
+
/** Process-wide store. Tests build their own through {@link createCredentialStore}. */
|
|
244
|
+
export function getCredentialStore() {
|
|
245
|
+
if (!sharedStore) {
|
|
246
|
+
sharedStore = createCredentialStore();
|
|
247
|
+
}
|
|
248
|
+
return sharedStore;
|
|
249
|
+
}
|
|
250
|
+
/** Test-only: drop the cached process-wide store. */
|
|
251
|
+
export function resetCredentialStoreForTests() {
|
|
252
|
+
sharedStore = null;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=credentialStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialStore.js","sourceRoot":"","sources":["../../src/auth/credentialStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Personal opaque-token client for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `desktop/src/main/localAgent/personalTokenClient.ts`, which drives
|
|
5
|
+
* the same server endpoints (`/api/auth/desktop/{authorize,token,revoke}`). The
|
|
6
|
+
* two clients must keep the same three guarantees, because each one exists to
|
|
7
|
+
* stop a specific failure that has bitten this flow before:
|
|
8
|
+
*
|
|
9
|
+
* 1. Refresh ahead of expiry (60s skew) so a request never races the clock.
|
|
10
|
+
* 2. Collapse concurrent refreshes into one in-flight promise, so five
|
|
11
|
+
* parallel calls rotate the refresh token once rather than five times.
|
|
12
|
+
* 3. Delete the stored refresh token **only** on an explicit `invalid_grant`.
|
|
13
|
+
* A flaky network must not log the user out — otherwise one offline moment
|
|
14
|
+
* costs a full re-login even after connectivity returns.
|
|
15
|
+
*/
|
|
16
|
+
import type { CredentialBackendId, CredentialStoreReason } from './credentialStore.js';
|
|
17
|
+
import { type PersonalTokenStore } from './personalTokenStore.js';
|
|
18
|
+
/** Registered in `api/src/services/personalTokenClients.ts`; the server rejects anything else. */
|
|
19
|
+
export declare const CLI_OAUTH_CLIENT_ID = "agentteams-cli";
|
|
20
|
+
/** Refresh this far before the server-declared expiry rather than at it. */
|
|
21
|
+
export declare const ACCESS_REFRESH_SKEW_MS = 60000;
|
|
22
|
+
export type PersonalTokenErrorCode = 'INVALID_GRANT' | 'TRANSIENT' | 'NOT_LOGGED_IN' | 'MALFORMED_RESPONSE' | 'REVOKE_FAILED'
|
|
23
|
+
/** A fresh login succeeded but the credential could not be kept anywhere durable. */
|
|
24
|
+
| 'STORE_UNAVAILABLE';
|
|
25
|
+
export declare class PersonalTokenError extends Error {
|
|
26
|
+
readonly code: PersonalTokenErrorCode;
|
|
27
|
+
constructor(code: PersonalTokenErrorCode, message: string);
|
|
28
|
+
}
|
|
29
|
+
export interface PersonalTokenIdentity {
|
|
30
|
+
memberId: string;
|
|
31
|
+
email: string;
|
|
32
|
+
nickname: string;
|
|
33
|
+
}
|
|
34
|
+
export interface PersonalTokenSession {
|
|
35
|
+
accessToken: string;
|
|
36
|
+
/** Epoch milliseconds, derived from the server's `expiresIn`. */
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
identity: PersonalTokenIdentity;
|
|
39
|
+
}
|
|
40
|
+
export interface PersonalTokenState {
|
|
41
|
+
/** A refresh token exists locally. It may still turn out to be revoked. */
|
|
42
|
+
connected: boolean;
|
|
43
|
+
/** false → the refresh token is session-memory only and disappears with this process. */
|
|
44
|
+
persisted: boolean;
|
|
45
|
+
/** Which OS store is in play, regardless of whether it currently works. */
|
|
46
|
+
storeBackend: CredentialBackendId;
|
|
47
|
+
storeReason: CredentialStoreReason;
|
|
48
|
+
identity: PersonalTokenIdentity | null;
|
|
49
|
+
expiresAt: number | null;
|
|
50
|
+
/** The server rejected the refresh token; only a fresh login recovers. */
|
|
51
|
+
reconnectRequired: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface AuthorizationCodeGrant {
|
|
54
|
+
code: string;
|
|
55
|
+
codeVerifier: string;
|
|
56
|
+
redirectUri: string;
|
|
57
|
+
}
|
|
58
|
+
export interface PersonalTokenClientDeps {
|
|
59
|
+
apiUrl: string;
|
|
60
|
+
store: PersonalTokenStore;
|
|
61
|
+
fetch?: typeof fetch;
|
|
62
|
+
now?: () => number;
|
|
63
|
+
}
|
|
64
|
+
interface TokenResponse {
|
|
65
|
+
accessToken: string;
|
|
66
|
+
refreshToken: string;
|
|
67
|
+
expiresIn: number;
|
|
68
|
+
identity: PersonalTokenIdentity;
|
|
69
|
+
}
|
|
70
|
+
type ParsedTokenResponse = {
|
|
71
|
+
kind: 'success';
|
|
72
|
+
tokens: TokenResponse;
|
|
73
|
+
} | {
|
|
74
|
+
kind: 'invalidGrant';
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'transient';
|
|
77
|
+
detail: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Classify a token-endpoint response.
|
|
81
|
+
*
|
|
82
|
+
* The split that matters is `invalid_grant` (the server has definitively
|
|
83
|
+
* refused this refresh token) versus everything else (5xx, a proxy error page,
|
|
84
|
+
* a truncated body). Only the first justifies destroying local credentials.
|
|
85
|
+
*/
|
|
86
|
+
export declare function parseTokenResponse(response: Response): Promise<ParsedTokenResponse>;
|
|
87
|
+
export declare class PersonalTokenClient {
|
|
88
|
+
private readonly deps;
|
|
89
|
+
private accessToken;
|
|
90
|
+
private accessExpiresAt;
|
|
91
|
+
private identity;
|
|
92
|
+
private reconnectRequired;
|
|
93
|
+
private refreshInFlight;
|
|
94
|
+
constructor(deps: PersonalTokenClientDeps);
|
|
95
|
+
state(): PersonalTokenState;
|
|
96
|
+
hasCredential(): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Trade a PKCE authorization code for the first token pair.
|
|
99
|
+
*
|
|
100
|
+
* A failure here leaves nothing behind: there is no prior credential to
|
|
101
|
+
* protect, and a half-finished login must not look like a connected one.
|
|
102
|
+
* That includes the case where the store refuses to keep the credential — a
|
|
103
|
+
* fresh login the next command cannot see is not a login, and the refresh
|
|
104
|
+
* token the server just minted would outlive this process with nobody able to
|
|
105
|
+
* use or cancel it. It is revoked rather than left orphaned.
|
|
106
|
+
*/
|
|
107
|
+
exchangeAuthorizationCode(grant: AuthorizationCodeGrant): Promise<PersonalTokenSession>;
|
|
108
|
+
/**
|
|
109
|
+
* Undo a login whose credential could not be stored.
|
|
110
|
+
*
|
|
111
|
+
* Revoking is best-effort — an unreachable server is not a reason to keep a
|
|
112
|
+
* dangling local state — but the local copy always goes, so `hasCredential()`
|
|
113
|
+
* never reports a login the next process will not find.
|
|
114
|
+
*/
|
|
115
|
+
private discardUnstorableTokens;
|
|
116
|
+
/**
|
|
117
|
+
* Current access token, refreshing pre-emptively when it is close to expiry.
|
|
118
|
+
* Returns null when there is nothing usable — the caller decides whether that
|
|
119
|
+
* is a hard failure or a reason to fall back to another credential.
|
|
120
|
+
*/
|
|
121
|
+
getAccessToken(): Promise<string | null>;
|
|
122
|
+
/** Force the next {@link getAccessToken} to refresh even if the cached token looks fresh. */
|
|
123
|
+
invalidateAccessToken(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Revoke the token family server-side, then drop the local copy.
|
|
126
|
+
*
|
|
127
|
+
* The order is not negotiable: clearing first and failing the revoke would
|
|
128
|
+
* leave a live refresh token on the server that the user can no longer see or
|
|
129
|
+
* cancel.
|
|
130
|
+
*/
|
|
131
|
+
revoke(): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Drop the local credential without asking the server.
|
|
134
|
+
*
|
|
135
|
+
* The offline escape hatch behind `auth logout --local`: the token stays valid
|
|
136
|
+
* server-side, so the caller owes the user a "revoke it in the web app" note.
|
|
137
|
+
*/
|
|
138
|
+
forgetLocalCredential(): void;
|
|
139
|
+
private refresh;
|
|
140
|
+
private postToken;
|
|
141
|
+
/**
|
|
142
|
+
* A failed save is reported, never thrown: a long-running `agentteams mcp`
|
|
143
|
+
* that rotates its refresh token must keep running on the in-memory copy
|
|
144
|
+
* rather than die at the rotation.
|
|
145
|
+
*/
|
|
146
|
+
private acceptTokens;
|
|
147
|
+
private clear;
|
|
148
|
+
private now;
|
|
149
|
+
private baseUrl;
|
|
150
|
+
}
|
|
151
|
+
export declare function getPersonalTokenClient(apiUrl: string): PersonalTokenClient;
|
|
152
|
+
/** Test-only: drop every cached client so a test can inject its own deps. */
|
|
153
|
+
export declare function resetPersonalTokenClientsForTests(): void;
|
|
154
|
+
export {};
|
|
155
|
+
//# sourceMappingURL=personalTokenClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personalTokenClient.d.ts","sourceRoot":"","sources":["../../src/auth/personalTokenClient.ts"],"names":[],"mappings":"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"}
|