@projectctx/agent 0.1.0-alpha.5 → 0.1.0-alpha.6
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 +27 -0
- package/assets/instructions/README.md +35 -0
- package/assets/instructions/chatgpt-claudeai.v1.md +6 -0
- package/assets/instructions/chatgpt-claudeai.v2.md +6 -0
- package/assets/instructions/claude-code.v1.md +15 -0
- package/assets/instructions/claude-code.v2.md +15 -0
- package/assets/instructions/codex.v1.md +10 -0
- package/assets/instructions/codex.v2.md +10 -0
- package/assets/instructions/cursor.v1.md +10 -0
- package/assets/instructions/cursor.v2.md +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.js +117 -0
- package/dist/connect.d.ts +101 -0
- package/dist/connect.js +544 -0
- package/dist/doctor.js +19 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp.js +2 -2
- package/dist/token-commands.d.ts +40 -0
- package/dist/token-commands.js +127 -0
- package/package.json +13 -12
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DebugLogger } from "./log.js";
|
|
2
|
+
export declare const REMOTE_MCP_SCOPES: readonly ["memory:read", "memory:write", "workspace:schema"];
|
|
3
|
+
export type TokenScope = (typeof REMOTE_MCP_SCOPES)[number];
|
|
4
|
+
export interface TokenCreateInput {
|
|
5
|
+
scopes: string[];
|
|
6
|
+
name?: string;
|
|
7
|
+
authToken?: string;
|
|
8
|
+
expiresInSeconds?: number;
|
|
9
|
+
workspaceId?: string;
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
store?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function runTokenCreate(input: TokenCreateInput, logger?: DebugLogger): Promise<{
|
|
14
|
+
message: string;
|
|
15
|
+
token?: string | undefined;
|
|
16
|
+
ok: true;
|
|
17
|
+
credentialId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
scopes: string[];
|
|
20
|
+
expiresAt: string | null;
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
storedInKeychain: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function runTokenList(input: {
|
|
25
|
+
authToken?: string;
|
|
26
|
+
workspaceId?: string;
|
|
27
|
+
apiUrl?: string;
|
|
28
|
+
}, logger?: DebugLogger): Promise<{
|
|
29
|
+
ok: true;
|
|
30
|
+
credentials: unknown[];
|
|
31
|
+
}>;
|
|
32
|
+
export declare function runTokenRevoke(input: {
|
|
33
|
+
credentialId: string;
|
|
34
|
+
authToken?: string;
|
|
35
|
+
workspaceId?: string;
|
|
36
|
+
apiUrl?: string;
|
|
37
|
+
}, logger?: DebugLogger): Promise<{
|
|
38
|
+
ok: true;
|
|
39
|
+
credentialId: string;
|
|
40
|
+
}>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readAgentConfig, readEnvOverrides, writeAgentConfig } from "./config.js";
|
|
2
|
+
import { getCredentialStore } from "./credentials.js";
|
|
3
|
+
import { AgentError } from "./errors.js";
|
|
4
|
+
// Self-serve scoped headless credential (T-004). Minted by the user's own OAuth
|
|
5
|
+
// identity: the caller presents a human bearer (the Supabase session token from
|
|
6
|
+
// web login) as --auth-token / PROJECTCTX_TOKEN, and the server hands back a
|
|
7
|
+
// scoped, expiring `pctx_ct_` credential for hooks / scan / headless agents.
|
|
8
|
+
//
|
|
9
|
+
// Interactive clients (Claude/Cursor/ChatGPT) never need this — it exists solely
|
|
10
|
+
// for non-interactive callers that cannot run browser consent every hour.
|
|
11
|
+
export const REMOTE_MCP_SCOPES = ["memory:read", "memory:write", "workspace:schema"];
|
|
12
|
+
function assertScopes(scopes) {
|
|
13
|
+
if (scopes.length === 0) {
|
|
14
|
+
throw new AgentError("CLOUD_ERROR", "token create", "At least one --scope is required.");
|
|
15
|
+
}
|
|
16
|
+
const invalid = scopes.filter((scope) => !REMOTE_MCP_SCOPES.includes(scope));
|
|
17
|
+
if (invalid.length) {
|
|
18
|
+
throw new AgentError("CLOUD_ERROR", "token create", `Unknown scope(s): ${invalid.join(", ")}. Valid scopes: ${REMOTE_MCP_SCOPES.join(", ")}.`);
|
|
19
|
+
}
|
|
20
|
+
return scopes;
|
|
21
|
+
}
|
|
22
|
+
// Minimal fetch against a human-authenticated endpoint. The stored agent
|
|
23
|
+
// credential is NOT used here — token administration is an OAuth-identity action,
|
|
24
|
+
// so it takes an explicit human bearer.
|
|
25
|
+
async function adminRequest(apiUrl, authToken, method, path, body, logger) {
|
|
26
|
+
logger?.debug(`calling ProjectCtx Cloud ${method} ${path}`);
|
|
27
|
+
let response;
|
|
28
|
+
try {
|
|
29
|
+
response = await fetch(`${apiUrl}${path}`, {
|
|
30
|
+
method,
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: `Bearer ${authToken}`,
|
|
33
|
+
...(body !== undefined ? { "content-type": "application/json" } : {})
|
|
34
|
+
},
|
|
35
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new AgentError("API_UNREACHABLE", path, `Cannot reach ProjectCtx Cloud at ${apiUrl}`, true);
|
|
40
|
+
}
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
let parsed = {};
|
|
43
|
+
if (text) {
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(text);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
parsed = {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const errorBody = typeof parsed === "object" && parsed ? parsed : {};
|
|
53
|
+
const serverMessage = typeof errorBody.message === "string" ? errorBody.message : undefined;
|
|
54
|
+
if (response.status === 401) {
|
|
55
|
+
throw new AgentError("TOKEN_INVALID", path, serverMessage ?? "Admin token was rejected. Provide a valid --auth-token from web login.");
|
|
56
|
+
}
|
|
57
|
+
if (response.status === 403) {
|
|
58
|
+
throw new AgentError("WORKSPACE_FORBIDDEN", path, serverMessage ?? "Not permitted for this workspace.");
|
|
59
|
+
}
|
|
60
|
+
throw new AgentError("CLOUD_ERROR", path, serverMessage ?? `ProjectCtx Cloud returned ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
function resolveAdminToken(input) {
|
|
65
|
+
const token = input.authToken ?? readEnvOverrides().token;
|
|
66
|
+
if (!token) {
|
|
67
|
+
throw new AgentError("NOT_LOGGED_IN", "token create", "Provide your OAuth/web session token with --auth-token <token> or PROJECTCTX_TOKEN to mint a scoped credential.");
|
|
68
|
+
}
|
|
69
|
+
return token;
|
|
70
|
+
}
|
|
71
|
+
function apiUrlFor(input) {
|
|
72
|
+
const existing = readAgentConfig();
|
|
73
|
+
return (input.apiUrl ?? readEnvOverrides().apiUrl ?? existing?.apiUrl ?? "https://api.projectctx.com").replace(/\/+$/, "");
|
|
74
|
+
}
|
|
75
|
+
export async function runTokenCreate(input, logger) {
|
|
76
|
+
const scopes = assertScopes(input.scopes);
|
|
77
|
+
const authToken = resolveAdminToken(input);
|
|
78
|
+
const apiUrl = apiUrlFor(input);
|
|
79
|
+
const result = (await adminRequest(apiUrl, authToken, "POST", "/v1/oauth/tokens", {
|
|
80
|
+
scopes,
|
|
81
|
+
...(input.name ? { name: input.name } : {}),
|
|
82
|
+
...(input.expiresInSeconds ? { expiresInSeconds: input.expiresInSeconds } : {}),
|
|
83
|
+
...(input.workspaceId ? { workspaceId: input.workspaceId } : {})
|
|
84
|
+
}, logger));
|
|
85
|
+
const store = input.store ?? true;
|
|
86
|
+
let stored = false;
|
|
87
|
+
if (store) {
|
|
88
|
+
const credentialStore = getCredentialStore();
|
|
89
|
+
if (await credentialStore.isAvailable()) {
|
|
90
|
+
await credentialStore.setToken(result.token);
|
|
91
|
+
const existing = readAgentConfig();
|
|
92
|
+
const baseConfig = { ...existing, apiUrl, workspaceId: result.workspaceId, credentialStore: "keychain" };
|
|
93
|
+
delete baseConfig.token;
|
|
94
|
+
writeAgentConfig(baseConfig);
|
|
95
|
+
stored = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// The plaintext credential is returned ONCE. It is echoed here only when it was
|
|
99
|
+
// not persisted to the keyring, so the caller can save it themselves.
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
credentialId: result.credentialId,
|
|
103
|
+
name: result.name,
|
|
104
|
+
scopes: result.scopes,
|
|
105
|
+
expiresAt: result.expiresAt,
|
|
106
|
+
workspaceId: result.workspaceId,
|
|
107
|
+
storedInKeychain: stored,
|
|
108
|
+
...(stored ? {} : { token: result.token }),
|
|
109
|
+
message: stored
|
|
110
|
+
? "Scoped headless credential minted and saved to the system credential store."
|
|
111
|
+
: "Scoped headless credential minted. Save the token now — it will NOT be shown again."
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export async function runTokenList(input, logger) {
|
|
115
|
+
const authToken = resolveAdminToken({ authToken: input.authToken });
|
|
116
|
+
const apiUrl = apiUrlFor(input);
|
|
117
|
+
const query = input.workspaceId ? `?workspaceId=${encodeURIComponent(input.workspaceId)}` : "";
|
|
118
|
+
const result = (await adminRequest(apiUrl, authToken, "GET", `/v1/oauth/tokens${query}`, undefined, logger));
|
|
119
|
+
return { ok: true, credentials: result.credentials };
|
|
120
|
+
}
|
|
121
|
+
export async function runTokenRevoke(input, logger) {
|
|
122
|
+
const authToken = resolveAdminToken({ authToken: input.authToken });
|
|
123
|
+
const apiUrl = apiUrlFor(input);
|
|
124
|
+
const query = input.workspaceId ? `?workspaceId=${encodeURIComponent(input.workspaceId)}` : "";
|
|
125
|
+
const result = (await adminRequest(apiUrl, authToken, "DELETE", `/v1/oauth/tokens/${encodeURIComponent(input.credentialId)}${query}`, undefined, logger));
|
|
126
|
+
return { ok: true, credentialId: result.credentialId };
|
|
127
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projectctx/agent",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
|
+
"assets",
|
|
7
8
|
"README.md"
|
|
8
9
|
],
|
|
9
10
|
"publishConfig": {
|
|
@@ -20,17 +21,9 @@
|
|
|
20
21
|
"default": "./dist/index.js"
|
|
21
22
|
}
|
|
22
23
|
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "tsc -p tsconfig.json",
|
|
25
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
26
|
-
"test": "vitest run",
|
|
27
|
-
"dev": "tsx src/cli.ts",
|
|
28
|
-
"alpha:smoke": "tsx scripts/alpha-smoke.ts",
|
|
29
|
-
"pack:verify": "node scripts/verify-package.mjs"
|
|
30
|
-
},
|
|
31
24
|
"dependencies": {
|
|
32
|
-
"@projectctx/contracts": "0.1.0-alpha.
|
|
33
|
-
"@projectctx/indexer": "0.1.0-alpha.
|
|
25
|
+
"@projectctx/contracts": "0.1.0-alpha.6",
|
|
26
|
+
"@projectctx/indexer": "0.1.0-alpha.6",
|
|
34
27
|
"@modelcontextprotocol/sdk": "^1.13.1",
|
|
35
28
|
"@napi-rs/keyring": "^1.3.0",
|
|
36
29
|
"zod": "^3.25.67"
|
|
@@ -41,5 +34,13 @@
|
|
|
41
34
|
"tsx": "^4.19.4",
|
|
42
35
|
"typescript": "^5.8.3",
|
|
43
36
|
"vitest": "^3.2.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"dev": "tsx src/cli.ts",
|
|
43
|
+
"alpha:smoke": "tsx scripts/alpha-smoke.ts",
|
|
44
|
+
"pack:verify": "node scripts/verify-package.mjs"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|