@inneranimalmedia/agentsam-sdk 2.6.0 → 2.6.2
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/docs/AUTH_IDENTITY_CONTRACT.md +25 -27
- package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +7 -7
- package/docs/SDK_WORKER.md +4 -3
- package/docs/SOURCE_ARCHITECTURE.md +58 -0
- package/docs/TEST_TIERS.md +26 -0
- package/migrations/runtime/0001_cli_runtime.sql +298 -0
- package/package.json +28 -7
- package/packages/agentsam-repository/README.md +15 -0
- package/packages/agentsam-repository/package.json +25 -0
- package/packages/agentsam-repository/src/contracts.js +113 -0
- package/packages/agentsam-repository/src/index.js +3 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
- package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
- package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
- package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
- package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
- package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
- package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +23 -40
- package/packages/identity/tests/auth-config.test.mjs +14 -23
- package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
- package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
- package/protocol/repository/repository-contract.schema.json +24 -0
- package/protocol/repository/repository-dependency.schema.json +24 -0
- package/protocol/repository/repository-identity.schema.json +17 -0
- package/protocol/rpc/v1/common.proto +16 -0
- package/protocol/rpc/v1/errors.proto +35 -0
- package/protocol/rpc/v1/knowledge.proto +77 -0
- package/services/knowledge/package-lock.json +333 -0
- package/services/knowledge/package.json +5 -1
- package/src/agent/responses-runner.js +63 -35
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +36 -29
- package/src/commands/account-auth.js +1 -1
- package/src/commands/context-economics.js +17 -2
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/deploy.js +2 -2
- package/src/commands/env.js +90 -0
- package/src/commands/knowledge.js +12 -4
- package/src/commands/merkle-persist.js +30 -11
- package/src/commands/merkle.js +1 -1
- package/src/commands/models.js +123 -65
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +53 -26
- package/src/commands/providers.js +308 -0
- package/src/commands/shell.js +240 -48
- package/src/commands/tunnel.js +8 -8
- package/src/commands/whoami.js +42 -21
- package/src/errors/contract.js +236 -0
- package/src/errors/diagnostic.js +1 -0
- package/src/errors/index.js +14 -0
- package/src/index.js +13 -1
- package/src/knowledge/service/auth.js +13 -0
- package/src/knowledge/service/grpc-client.js +115 -0
- package/src/knowledge/service/grpc-codec.js +237 -0
- package/src/knowledge/service/grpc-server.js +83 -0
- package/src/knowledge/service/job-engine.js +248 -0
- package/src/knowledge/service/server.js +87 -135
- package/src/knowledge/source.js +1 -1
- package/src/lib/account-session.js +94 -25
- package/src/lib/auth.js +314 -51
- package/src/lib/cli-preferences.js +31 -4
- package/src/lib/core-client.js +75 -30
- package/src/lib/deploy-receipt/index.js +2 -2
- package/src/lib/detect-context.js +14 -16
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +23 -2
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +230 -14
- package/src/lib/slash-commands.js +5 -3
- package/src/local/migrations.js +93 -0
- package/src/local/runtime-store.js +141 -0
- package/src/local/sqlite.js +2 -0
- package/src/local-pty/server.js +113 -51
- package/src/models/discovery.js +327 -0
- package/src/providers/anthropic-messages.js +192 -0
- package/src/providers/cloudflare-chat.js +183 -0
- package/src/providers/factory.js +69 -0
- package/src/providers/gemini-generate-content.js +208 -0
- package/src/providers/index.js +5 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +226 -75
- package/src/repository/index.js +14 -2
- package/src/rpc/generated/common_grpc_pb.js +1 -0
- package/src/rpc/generated/common_pb.js +536 -0
- package/src/rpc/generated/errors_grpc_pb.js +1 -0
- package/src/rpc/generated/errors_pb.js +482 -0
- package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
- package/src/rpc/generated/knowledge_pb.js +2168 -0
- package/src/rpc/generated/package.json +3 -0
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/events.js +4 -1
- package/src/ui/cli/activity.js +76 -0
- package/src/ui/cli/compaction.js +15 -0
- package/src/ui/cli/footer.js +39 -0
- package/src/ui/cli/help.js +194 -0
- package/src/ui/cli/plan.js +20 -0
- package/src/ui/cli/runtime-events.js +110 -0
- package/src/ui/cli/waiting.js +16 -0
- package/src/ui/merkle/render.js +1 -1
- package/test/account-session.test.mjs +48 -11
- package/test/apps-scaffold-contract.test.mjs +43 -0
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -0
- package/test/error-diagnostics.test.mjs +57 -1
- package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
- package/test/integration/cli-help.test.mjs +37 -0
- package/test/integration/knowledge-rpc.test.mjs +112 -0
- package/test/integration/merkle-cli.test.mjs +61 -0
- package/test/integration/merkle-persistence-identity.test.mjs +48 -0
- package/test/integration/provider-env-cli.test.mjs +49 -0
- package/test/integration/provider-factory.test.mjs +197 -0
- package/test/integration/repository-company-graph.test.mjs +90 -0
- package/test/integration/runtime-migrations.test.mjs +82 -0
- package/test/knowledge-service.test.mjs +5 -0
- package/test/knowledge.test.mjs +16 -0
- package/test/live/terminal-transport.live.test.mjs +24 -0
- package/test/local-sessions.test.mjs +7 -1
- package/test/models.test.mjs +101 -4
- package/test/ollama.test.mjs +21 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +45 -1
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +3 -1
- package/test/sdk-worker-contract.test.mjs +5 -1
- package/test/shell.test.mjs +50 -8
- package/test/smoke.mjs +2 -1
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- package/test/whoami-resume.test.mjs +6 -5
- package/src/lib/prompt-byok.js +0 -57
- package/src/lib/save-sdk-token.js +0 -19
- /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
package/src/lib/core-client.js
CHANGED
|
@@ -1,27 +1,67 @@
|
|
|
1
1
|
import { resolveIamOrigin } from '../../packages/identity/src/contracts/auth-config.js';
|
|
2
|
+
import { resolveAccountAuthority } from './auth.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* IAM CORE client — SDK is a delivery mechanism; intelligence lives server-side.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* fallbacks. Without a CORE backend, consumers get scaffold/local CLI only.
|
|
7
|
+
* IAM_OAUTH_ISSUER is the canonical platform authority/API origin. IAM_ORIGIN,
|
|
8
|
+
* IAM_CORE_URL and AGENTSAM_CORE_URL remain compatibility fallbacks.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
10
|
export function coreBaseUrl(env = process.env) {
|
|
12
|
-
const explicit = env?.IAM_ORIGIN || env?.IAM_CORE_URL || env?.AGENTSAM_CORE_URL || '';
|
|
11
|
+
const explicit = env?.IAM_OAUTH_ISSUER || env?.IAM_ORIGIN || env?.IAM_CORE_URL || env?.AGENTSAM_CORE_URL || '';
|
|
13
12
|
return resolveIamOrigin(env, explicit);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
function authOptions(options) {
|
|
16
|
+
if (typeof options === 'string') return { explicit: options };
|
|
17
|
+
return options && typeof options === 'object' ? options : {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function resolveCoreAuthority(options = {}) {
|
|
21
|
+
const resolvedOptions = authOptions(options);
|
|
22
|
+
const resolver = resolvedOptions.resolveAuthorityImpl || resolveAccountAuthority;
|
|
23
|
+
const authority = await resolver({
|
|
24
|
+
env: resolvedOptions.env || process.env,
|
|
25
|
+
home: resolvedOptions.home,
|
|
26
|
+
explicit: resolvedOptions.explicit || resolvedOptions.token || '',
|
|
27
|
+
fetchImpl: resolvedOptions.fetchImpl,
|
|
28
|
+
issuer: resolvedOptions.issuer,
|
|
29
|
+
signal: resolvedOptions.signal,
|
|
30
|
+
nowMs: resolvedOptions.nowMs,
|
|
31
|
+
skewMs: resolvedOptions.skewMs,
|
|
32
|
+
refreshImpl: resolvedOptions.refreshImpl,
|
|
33
|
+
});
|
|
34
|
+
if (authority?.error) throw new Error(authority.error);
|
|
35
|
+
if (!authority?.value) throw new Error('account_auth_required');
|
|
36
|
+
return authority;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function authorizedHeaders(headers, options = {}) {
|
|
40
|
+
const authority = await resolveCoreAuthority(options);
|
|
41
|
+
return {
|
|
42
|
+
...headers,
|
|
43
|
+
Authorization: `Bearer ${authority.value}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function responseJson(res) {
|
|
48
|
+
return res.json().catch(() => ({}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function postJson(path, body, options = {}) {
|
|
52
|
+
const resolvedOptions = authOptions(options);
|
|
53
|
+
const fetchImpl = resolvedOptions.fetchImpl || fetch;
|
|
54
|
+
const headers = await authorizedHeaders({
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
Accept: 'application/json',
|
|
57
|
+
}, resolvedOptions);
|
|
58
|
+
const res = await fetchImpl(`${coreBaseUrl(resolvedOptions.env || process.env)}${path}`, {
|
|
20
59
|
method: 'POST',
|
|
21
60
|
headers,
|
|
22
61
|
body: JSON.stringify(body ?? {}),
|
|
62
|
+
signal: resolvedOptions.signal,
|
|
23
63
|
});
|
|
24
|
-
const data = await res
|
|
64
|
+
const data = await responseJson(res);
|
|
25
65
|
if (!res.ok) {
|
|
26
66
|
const msg = data?.error || data?.message || `HTTP ${res.status}`;
|
|
27
67
|
throw new Error(String(msg));
|
|
@@ -29,11 +69,15 @@ export async function postJson(path, body, token) {
|
|
|
29
69
|
return data;
|
|
30
70
|
}
|
|
31
71
|
|
|
32
|
-
export async function getJson(path,
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
72
|
+
export async function getJson(path, options = {}) {
|
|
73
|
+
const resolvedOptions = authOptions(options);
|
|
74
|
+
const fetchImpl = resolvedOptions.fetchImpl || fetch;
|
|
75
|
+
const headers = await authorizedHeaders({ Accept: 'application/json' }, resolvedOptions);
|
|
76
|
+
const res = await fetchImpl(`${coreBaseUrl(resolvedOptions.env || process.env)}${path}`, {
|
|
77
|
+
headers,
|
|
78
|
+
signal: resolvedOptions.signal,
|
|
79
|
+
});
|
|
80
|
+
const data = await responseJson(res);
|
|
37
81
|
if (!res.ok) {
|
|
38
82
|
const msg = data?.error || data?.message || `HTTP ${res.status}`;
|
|
39
83
|
throw new Error(String(msg));
|
|
@@ -44,18 +88,21 @@ export async function getJson(path, token) {
|
|
|
44
88
|
/**
|
|
45
89
|
* Stream NDJSON from POST /api/sdk/scaffold — calls onEvent for each line.
|
|
46
90
|
*/
|
|
47
|
-
export async function streamScaffold(body,
|
|
48
|
-
const
|
|
91
|
+
export async function streamScaffold(body, onEvent, options = {}) {
|
|
92
|
+
const resolvedOptions = authOptions(options);
|
|
93
|
+
const fetchImpl = resolvedOptions.fetchImpl || fetch;
|
|
94
|
+
const headers = await authorizedHeaders({
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
Accept: 'application/x-ndjson',
|
|
97
|
+
}, resolvedOptions);
|
|
98
|
+
const res = await fetchImpl(`${coreBaseUrl(resolvedOptions.env || process.env)}/api/sdk/scaffold`, {
|
|
49
99
|
method: 'POST',
|
|
50
|
-
headers
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
Accept: 'application/x-ndjson',
|
|
53
|
-
Authorization: `Bearer ${token}`,
|
|
54
|
-
},
|
|
100
|
+
headers,
|
|
55
101
|
body: JSON.stringify(body),
|
|
102
|
+
signal: resolvedOptions.signal,
|
|
56
103
|
});
|
|
57
104
|
if (!res.ok) {
|
|
58
|
-
const data = await res
|
|
105
|
+
const data = await responseJson(res);
|
|
59
106
|
throw new Error(data?.error || `scaffold HTTP ${res.status}`);
|
|
60
107
|
}
|
|
61
108
|
if (!res.body) throw new Error('scaffold stream missing');
|
|
@@ -71,15 +118,13 @@ export async function streamScaffold(body, token, onEvent) {
|
|
|
71
118
|
const lines = buf.split('\n');
|
|
72
119
|
buf = lines.pop() || '';
|
|
73
120
|
for (const line of lines) {
|
|
74
|
-
const
|
|
75
|
-
if (!
|
|
76
|
-
let evt;
|
|
121
|
+
const text = line.trim();
|
|
122
|
+
if (!text) continue;
|
|
77
123
|
try {
|
|
78
|
-
|
|
124
|
+
await onEvent(JSON.parse(text));
|
|
79
125
|
} catch {
|
|
80
|
-
|
|
126
|
+
/* ignore malformed stream lines */
|
|
81
127
|
}
|
|
82
|
-
await onEvent(evt);
|
|
83
128
|
}
|
|
84
129
|
}
|
|
85
130
|
const tail = buf.trim();
|
|
@@ -87,7 +132,7 @@ export async function streamScaffold(body, token, onEvent) {
|
|
|
87
132
|
try {
|
|
88
133
|
await onEvent(JSON.parse(tail));
|
|
89
134
|
} catch {
|
|
90
|
-
/* ignore */
|
|
135
|
+
/* ignore malformed trailing line */
|
|
91
136
|
}
|
|
92
137
|
}
|
|
93
138
|
}
|
|
@@ -2,8 +2,8 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
|
-
import { buildMerkleTree, diffTrees, readSnapshot, validateSnapshot } from '
|
|
6
|
-
import { normalizePolicy } from '
|
|
5
|
+
import { buildMerkleTree, diffTrees, readSnapshot, validateSnapshot } from '../../../packages/agentsam-repository/src/merkle/index.js';
|
|
6
|
+
import { normalizePolicy } from '../../../packages/agentsam-repository/src/merkle/policy.js';
|
|
7
7
|
|
|
8
8
|
export const DEFAULT_DEPLOY_EXCLUDES = Object.freeze([
|
|
9
9
|
'.agentsam/deploy-merkle',
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { execFile } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { coreBaseUrl } from './core-client.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveAccountAuth } from './account-session.js';
|
|
9
9
|
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
|
|
@@ -167,11 +167,9 @@ async function detectCloudflare() {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
async function
|
|
170
|
+
async function probeAccountBearer(token) {
|
|
171
171
|
const t = String(token || '').trim();
|
|
172
|
-
if (!t
|
|
173
|
-
return { valid: false, error: 'not_sdk_bearer' };
|
|
174
|
-
}
|
|
172
|
+
if (!t) return { valid: false, error: 'account_auth_required' };
|
|
175
173
|
try {
|
|
176
174
|
const res = await fetch(`${coreBaseUrl()}/api/sdk/context`, {
|
|
177
175
|
headers: { Accept: 'application/json', Authorization: `Bearer ${t}` },
|
|
@@ -197,7 +195,7 @@ function envPresent(name) {
|
|
|
197
195
|
return v != null && String(v).trim() !== '';
|
|
198
196
|
}
|
|
199
197
|
|
|
200
|
-
/** IAM auth for SDK init —
|
|
198
|
+
/** IAM auth for SDK init — accepts a verified account API key or browser session credential. */
|
|
201
199
|
async function detectIam(explicitToken = '') {
|
|
202
200
|
const aux = [];
|
|
203
201
|
|
|
@@ -210,9 +208,9 @@ async function detectIam(explicitToken = '') {
|
|
|
210
208
|
});
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
const workerKey = envPresent('IAM_API_KEY')
|
|
211
|
+
const workerKey = envPresent('IAM_API_KEY');
|
|
214
212
|
if (workerKey) {
|
|
215
|
-
const name =
|
|
213
|
+
const name = 'IAM_API_KEY';
|
|
216
214
|
aux.push({
|
|
217
215
|
var: name,
|
|
218
216
|
role: 'Worker runtime secret',
|
|
@@ -221,22 +219,22 @@ async function detectIam(explicitToken = '') {
|
|
|
221
219
|
});
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
const probe = await
|
|
222
|
+
const accountAuth = resolveAccountAuth({ env: process.env, explicit: explicitToken });
|
|
223
|
+
if (accountAuth.value) {
|
|
224
|
+
const probe = await probeAccountBearer(accountAuth.value);
|
|
227
225
|
if (probe.valid) {
|
|
228
226
|
return {
|
|
229
|
-
source: '
|
|
227
|
+
source: accountAuth.kind || 'account-auth',
|
|
230
228
|
ready: true,
|
|
231
|
-
detail:
|
|
229
|
+
detail: `${accountAuth.kind === 'api_key' ? 'AGENTSAM_API_KEY' : 'browser session'} · user ${probe.user_id || '?'}`,
|
|
232
230
|
probe,
|
|
233
231
|
aux,
|
|
234
232
|
};
|
|
235
233
|
}
|
|
236
234
|
return {
|
|
237
|
-
source: '
|
|
235
|
+
source: accountAuth.kind || 'account-auth',
|
|
238
236
|
ready: false,
|
|
239
|
-
detail:
|
|
237
|
+
detail: `${accountAuth.kind === 'api_key' ? 'AGENTSAM_API_KEY' : 'browser session'} invalid (${probe.error}) → will open browser`,
|
|
240
238
|
probe,
|
|
241
239
|
aux,
|
|
242
240
|
};
|
|
@@ -247,7 +245,7 @@ async function detectIam(explicitToken = '') {
|
|
|
247
245
|
return {
|
|
248
246
|
source: 'execos-env',
|
|
249
247
|
ready: false,
|
|
250
|
-
detail: `IAM_PTY_USER_ID set (ExecOS identity —
|
|
248
|
+
detail: `IAM_PTY_USER_ID set (ExecOS identity — account auth still needed)`,
|
|
251
249
|
aux,
|
|
252
250
|
};
|
|
253
251
|
}
|
|
@@ -7,12 +7,15 @@ import { CONFIG_PATH, defaultConfig, readConfig } from '../knowledge/config.js';
|
|
|
7
7
|
|
|
8
8
|
const sdkRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
9
9
|
const runtimeFiles = () => {
|
|
10
|
-
const files = ['services/knowledge/package.json', 'services/knowledge/package-lock.json', 'src/
|
|
10
|
+
const files = ['services/knowledge/package.json', 'services/knowledge/package-lock.json', 'packages/agentsam-repository/src/merkle/hash.js', 'src/errors/contract.js'];
|
|
11
11
|
const walk = dir => { for (const entry of fs.readdirSync(path.join(sdkRoot, dir), { withFileTypes: true })) {
|
|
12
12
|
const file = `${dir}/${entry.name}`;
|
|
13
|
-
if (entry.isDirectory()) walk(file);
|
|
13
|
+
if (entry.isDirectory()) walk(file);
|
|
14
|
+
else if (entry.isFile() && (/\.(js|sql)$/.test(entry.name) || file === 'src/rpc/generated/package.json')) files.push(file);
|
|
14
15
|
} };
|
|
15
|
-
walk('src/knowledge');
|
|
16
|
+
walk('src/knowledge');
|
|
17
|
+
walk('src/rpc/generated');
|
|
18
|
+
return files.sort();
|
|
16
19
|
};
|
|
17
20
|
|
|
18
21
|
export function generateKnowledgeDocker(opts = {}) {
|
|
@@ -52,17 +52,37 @@ function normalizeUsage(value = {}) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function normalizeCostBreakdown(value = {}) {
|
|
56
|
+
return {
|
|
57
|
+
input: Number(value.input || 0),
|
|
58
|
+
cached_input: Number(value.cached_input || 0),
|
|
59
|
+
cache_write: Number(value.cache_write || 0),
|
|
60
|
+
output: Number(value.output || 0),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function localSessionElapsedMs(session = {}, at = Date.now()) {
|
|
65
|
+
const accumulated = Math.max(0, Number(session.active_elapsed_ms || 0));
|
|
66
|
+
const startedAt = Date.parse(clean(session.active_started_at));
|
|
67
|
+
if (clean(session.status) !== 'active' || !Number.isFinite(startedAt)) return accumulated;
|
|
68
|
+
return accumulated + Math.max(0, Number(at) - startedAt);
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
export function normalizeLocalSession(value = {}) {
|
|
56
72
|
const createdAt = clean(value.created_at) || now();
|
|
73
|
+
const status = clean(value.status) || 'active';
|
|
74
|
+
const updatedAt = clean(value.updated_at) || createdAt;
|
|
57
75
|
return {
|
|
58
76
|
schema_version: LOCAL_SESSION_SCHEMA,
|
|
59
77
|
id: validateSessionId(value.id || createLocalSessionId()),
|
|
60
|
-
status
|
|
78
|
+
status,
|
|
61
79
|
cwd: path.resolve(clean(value.cwd) || process.cwd()),
|
|
62
80
|
title: clean(value.title) || sessionTitleFromInput(value.last_input),
|
|
63
81
|
last_input: clean(value.last_input) || null,
|
|
64
82
|
created_at: createdAt,
|
|
65
|
-
updated_at:
|
|
83
|
+
updated_at: updatedAt,
|
|
84
|
+
active_elapsed_ms: Math.max(0, Number(value.active_elapsed_ms || 0)),
|
|
85
|
+
active_started_at: clean(value.active_started_at) || (status === 'active' ? updatedAt : null),
|
|
66
86
|
model_key: clean(value.model_key) || null,
|
|
67
87
|
provider_model_id: clean(value.provider_model_id) || null,
|
|
68
88
|
reasoning_effort: clean(value.reasoning_effort) || null,
|
|
@@ -72,6 +92,7 @@ export function normalizeLocalSession(value = {}) {
|
|
|
72
92
|
usage_snapshot: value.usage_snapshot && typeof value.usage_snapshot === 'object' ? structuredClone(value.usage_snapshot) : null,
|
|
73
93
|
cumulative_usage: normalizeUsage(value.cumulative_usage || {}),
|
|
74
94
|
total_cost_usd: Number(value.total_cost_usd || 0),
|
|
95
|
+
cost_breakdown_usd: normalizeCostBreakdown(value.cost_breakdown_usd || {}),
|
|
75
96
|
approved_projected_call_cost_usd: Number(value.approved_projected_call_cost_usd || 0),
|
|
76
97
|
last_error: value.last_error && typeof value.last_error === 'object' ? structuredClone(value.last_error) : null,
|
|
77
98
|
};
|
package/src/lib/local-status.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { tryResolveGitContext } from '
|
|
3
|
+
import { tryResolveGitContext } from '../../packages/agentsam-repository/src/git-context.js';
|
|
4
4
|
import { inspectLocalSqlite } from '../local/sqlite.js';
|
|
5
5
|
import { getCreatedWithVersion, getDefaultProfile, getDeployTarget, getLocalDatabasePath, getProjectName, getProjectPreset, getRepositoryId, tryReadProjectConfig } from './project-config.js';
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { tryResolveGitContext } from '
|
|
4
|
+
import { tryResolveGitContext } from '../../packages/agentsam-repository/src/git-context.js';
|
|
5
5
|
|
|
6
6
|
export const PROJECT_CONFIG_PATH = '.agentsam/config.json';
|
|
7
7
|
export const PROJECT_CONFIG_SCHEMA_VERSION = 2;
|
|
@@ -2,16 +2,58 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
const PROVIDER_CREDENTIALS = Object.freeze({
|
|
6
|
-
openai: Object.freeze({ env: 'OPENAI_API_KEY', files: ['openai.env'] }),
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
export const PROVIDER_CREDENTIALS = Object.freeze({
|
|
6
|
+
openai: Object.freeze({ label: 'OpenAI', env: 'OPENAI_API_KEY', files: ['openai.env'], modelProvider: 'openai' }),
|
|
7
|
+
anthropic: Object.freeze({ label: 'Anthropic', env: 'ANTHROPIC_API_KEY', files: ['anthropic.env'], modelProvider: 'anthropic' }),
|
|
8
|
+
gemini: Object.freeze({ label: 'Gemini', env: 'GEMINI_API_KEY', files: ['gemini.env'], modelProvider: 'gemini' }),
|
|
9
|
+
cursor: Object.freeze({ label: 'Cursor', env: 'CURSOR_API_KEY', files: ['cursor.env'], modelProvider: 'cursor' }),
|
|
10
|
+
xai: Object.freeze({ label: 'xAI', env: 'XAI_API_KEY', files: ['xai.env', 'grok.env'], modelProvider: 'xai' }),
|
|
11
|
+
cloudflare: Object.freeze({
|
|
12
|
+
label: 'Cloudflare',
|
|
13
|
+
env: 'CLOUDFLARE_API_TOKEN',
|
|
14
|
+
files: ['cloudflare.env'],
|
|
15
|
+
modelProvider: 'cloudflare',
|
|
16
|
+
accountEnv: ['ACCOUNT_ID', 'CLOUDFLARE_ACCOUNT_ID'],
|
|
17
|
+
}),
|
|
18
|
+
inneranimalmedia: Object.freeze({
|
|
19
|
+
label: 'InnerAnimalMedia',
|
|
20
|
+
env: 'AGENTSAM_API_KEY',
|
|
21
|
+
files: ['inneranimalmedia.env'],
|
|
22
|
+
tokenPrefix: 'aak_',
|
|
23
|
+
platformCredential: true,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const PROVIDER_ALIASES = Object.freeze({
|
|
28
|
+
grok: 'xai',
|
|
29
|
+
iam: 'inneranimalmedia',
|
|
30
|
+
inneranimal: 'inneranimalmedia',
|
|
11
31
|
});
|
|
12
32
|
|
|
13
33
|
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
14
34
|
|
|
35
|
+
export function normalizeProviderId(provider) {
|
|
36
|
+
const id = clean(provider).toLowerCase();
|
|
37
|
+
return PROVIDER_ALIASES[id] || id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeCloudflareAccountId(value) {
|
|
41
|
+
const id = clean(value);
|
|
42
|
+
if (!id) return '';
|
|
43
|
+
if (!/^[a-f0-9]{32}$/i.test(id)) throw new Error('invalid_cloudflare_account_id');
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateCredentialValue(spec, value) {
|
|
48
|
+
const secret = clean(value);
|
|
49
|
+
if (!secret) throw new Error('credential_required');
|
|
50
|
+
if (/[\r\n\0]/.test(secret)) throw new Error('credential_must_be_single_line');
|
|
51
|
+
if (spec.tokenPrefix && !secret.startsWith(spec.tokenPrefix)) {
|
|
52
|
+
throw new Error(`credential_prefix_required:${spec.tokenPrefix}`);
|
|
53
|
+
}
|
|
54
|
+
return secret;
|
|
55
|
+
}
|
|
56
|
+
|
|
15
57
|
function homeDirectory(options = {}) {
|
|
16
58
|
return path.resolve(clean(options.home) || clean(options.env?.HOME) || clean(options.env?.USERPROFILE) || os.homedir());
|
|
17
59
|
}
|
|
@@ -36,45 +78,219 @@ function secureFile(filename) {
|
|
|
36
78
|
return { ok: true, mode: stat.mode & 0o777 };
|
|
37
79
|
}
|
|
38
80
|
|
|
81
|
+
function firstEnvValue(source, names = []) {
|
|
82
|
+
for (const name of names) {
|
|
83
|
+
const value = clean(parseEnvValue(source, name));
|
|
84
|
+
if (value) return value;
|
|
85
|
+
}
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function firstRuntimeValue(env, names = []) {
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
const value = clean(env?.[name]);
|
|
92
|
+
if (value) return value;
|
|
93
|
+
}
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function atomicWrite(filename, source, mode = 0o600) {
|
|
98
|
+
const dir = path.dirname(filename);
|
|
99
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
100
|
+
if (process.platform !== 'win32') fs.chmodSync(dir, 0o700);
|
|
101
|
+
const temp = `${filename}.${process.pid}.${Date.now()}.tmp`;
|
|
102
|
+
fs.writeFileSync(temp, source, { mode });
|
|
103
|
+
if (process.platform !== 'win32') fs.chmodSync(temp, mode);
|
|
104
|
+
fs.renameSync(temp, filename);
|
|
105
|
+
if (process.platform !== 'win32') fs.chmodSync(filename, mode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function envLiteral(value) {
|
|
109
|
+
const text = String(value ?? '');
|
|
110
|
+
if (/['"\r\n\0]/.test(text)) throw new Error('credential_contains_unsupported_profile_character');
|
|
111
|
+
return `"${text}"`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function agentEnvDirectory(options = {}) {
|
|
115
|
+
return path.join(homeDirectory(options), '.agentsam', 'env.d');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function agentEnvLoaderPath(options = {}) {
|
|
119
|
+
return path.join(homeDirectory(options), '.agentsam', 'load-agent-env.sh');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function ensureAgentEnvLoader(options = {}) {
|
|
123
|
+
const filename = agentEnvLoaderPath(options);
|
|
124
|
+
const supported = Object.keys(PROVIDER_CREDENTIALS).join('|');
|
|
125
|
+
const source = `# AgentSam provider environment loader. Source this file; do not execute it.
|
|
126
|
+
if [ "$#" -eq 0 ]; then
|
|
127
|
+
echo "usage: source ~/.agentsam/load-agent-env.sh <profile> [profile ...]" >&2
|
|
128
|
+
return 2 2>/dev/null || exit 2
|
|
129
|
+
fi
|
|
130
|
+
for _agentsam_profile in "$@"; do
|
|
131
|
+
case "$_agentsam_profile" in
|
|
132
|
+
grok) _agentsam_profile="xai" ;;
|
|
133
|
+
${supported}) ;;
|
|
134
|
+
*) echo "unknown AgentSam provider profile: $_agentsam_profile" >&2; return 2 2>/dev/null || exit 2 ;;
|
|
135
|
+
esac
|
|
136
|
+
_agentsam_file="\${HOME}/.agentsam/env.d/\${_agentsam_profile}.env"
|
|
137
|
+
if [ ! -f "$_agentsam_file" ]; then
|
|
138
|
+
echo "AgentSam provider profile not found: $_agentsam_file" >&2
|
|
139
|
+
return 1 2>/dev/null || exit 1
|
|
140
|
+
fi
|
|
141
|
+
set -a
|
|
142
|
+
. "$_agentsam_file"
|
|
143
|
+
set +a
|
|
144
|
+
if [ "$_agentsam_profile" = cloudflare ]; then
|
|
145
|
+
if [ -z "\${ACCOUNT_ID:-}" ] && [ -n "\${CLOUDFLARE_ACCOUNT_ID:-}" ]; then export ACCOUNT_ID="$CLOUDFLARE_ACCOUNT_ID"; fi
|
|
146
|
+
if [ -z "\${CLOUDFLARE_ACCOUNT_ID:-}" ] && [ -n "\${ACCOUNT_ID:-}" ]; then export CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID"; fi
|
|
147
|
+
fi
|
|
148
|
+
done
|
|
149
|
+
unset _agentsam_file _agentsam_profile
|
|
150
|
+
`;
|
|
151
|
+
atomicWrite(filename, source, 0o700);
|
|
152
|
+
return filename;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function profileSource(provider, credential = '', options = {}) {
|
|
156
|
+
const spec = providerCredentialSpec(provider);
|
|
157
|
+
if (!spec) throw new Error(`unsupported_provider:${normalizeProviderId(provider)}`);
|
|
158
|
+
const lines = [`# AgentSam ${spec.label} provider profile`];
|
|
159
|
+
if (spec.provider === 'cloudflare') {
|
|
160
|
+
lines.push('# ACCOUNT_ID is your Cloudflare account identifier; it is not a secret.');
|
|
161
|
+
lines.push(`export ACCOUNT_ID=${envLiteral(normalizeCloudflareAccountId(options.accountId))}`);
|
|
162
|
+
}
|
|
163
|
+
lines.push(`export ${spec.env}=${envLiteral(credential)}`);
|
|
164
|
+
return `${lines.join('\n')}\n`;
|
|
165
|
+
}
|
|
166
|
+
|
|
39
167
|
export function providerCredentialSpec(provider) {
|
|
40
|
-
const id =
|
|
168
|
+
const id = normalizeProviderId(provider);
|
|
41
169
|
const spec = PROVIDER_CREDENTIALS[id];
|
|
42
170
|
return spec ? Object.freeze({ provider: id, ...spec }) : null;
|
|
43
171
|
}
|
|
44
172
|
|
|
173
|
+
export function ensureProviderEnvProfile(provider, options = {}) {
|
|
174
|
+
const spec = providerCredentialSpec(provider);
|
|
175
|
+
if (!spec) throw new Error(`unsupported_provider:${normalizeProviderId(provider)}`);
|
|
176
|
+
const dir = agentEnvDirectory(options);
|
|
177
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
178
|
+
if (process.platform !== 'win32') fs.chmodSync(dir, 0o700);
|
|
179
|
+
const filename = path.join(dir, spec.files[0]);
|
|
180
|
+
let created = false;
|
|
181
|
+
if (!fs.existsSync(filename)) {
|
|
182
|
+
atomicWrite(filename, profileSource(spec.provider, '', options), 0o600);
|
|
183
|
+
created = true;
|
|
184
|
+
} else {
|
|
185
|
+
const safety = secureFile(filename);
|
|
186
|
+
if (!safety.ok) throw new Error(safety.error);
|
|
187
|
+
if (process.platform !== 'win32') fs.chmodSync(filename, 0o600);
|
|
188
|
+
if (spec.provider === 'cloudflare' && clean(options.accountId)) {
|
|
189
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
190
|
+
const current = firstEnvValue(source, spec.accountEnv || []);
|
|
191
|
+
if (!current) {
|
|
192
|
+
const accountId = normalizeCloudflareAccountId(options.accountId);
|
|
193
|
+
const line = `export ACCOUNT_ID=${envLiteral(accountId)}`;
|
|
194
|
+
const next = /^(?:export\s+)?ACCOUNT_ID=.*$/m.test(source)
|
|
195
|
+
? source.replace(/^(?:export\s+)?ACCOUNT_ID=.*$/m, line)
|
|
196
|
+
: `${line}\n${source}`;
|
|
197
|
+
atomicWrite(filename, next, 0o600);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const loader = ensureAgentEnvLoader(options);
|
|
202
|
+
return Object.freeze({
|
|
203
|
+
provider: spec.provider,
|
|
204
|
+
file: filename,
|
|
205
|
+
loader,
|
|
206
|
+
created,
|
|
207
|
+
source_command: `source ~/.agentsam/load-agent-env.sh ${spec.provider}`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function setProviderCredential(provider, credential, options = {}) {
|
|
212
|
+
const spec = providerCredentialSpec(provider);
|
|
213
|
+
if (!spec) throw new Error(`unsupported_provider:${normalizeProviderId(provider)}`);
|
|
214
|
+
const value = validateCredentialValue(spec, credential);
|
|
215
|
+
const filename = path.join(agentEnvDirectory(options), spec.files[0]);
|
|
216
|
+
atomicWrite(filename, profileSource(spec.provider, value, options), 0o600);
|
|
217
|
+
const loader = ensureAgentEnvLoader(options);
|
|
218
|
+
return Object.freeze({
|
|
219
|
+
provider: spec.provider,
|
|
220
|
+
file: filename,
|
|
221
|
+
loader,
|
|
222
|
+
source_command: `source ~/.agentsam/load-agent-env.sh ${spec.provider}`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function removeProviderCredential(provider, options = {}) {
|
|
227
|
+
const spec = providerCredentialSpec(provider);
|
|
228
|
+
if (!spec) throw new Error(`unsupported_provider:${normalizeProviderId(provider)}`);
|
|
229
|
+
const dir = agentEnvDirectory(options);
|
|
230
|
+
let removed = false;
|
|
231
|
+
for (const basename of spec.files) {
|
|
232
|
+
const filename = path.join(dir, basename);
|
|
233
|
+
if (!fs.existsSync(filename)) continue;
|
|
234
|
+
const safety = secureFile(filename);
|
|
235
|
+
if (!safety.ok) throw new Error(safety.error);
|
|
236
|
+
fs.rmSync(filename, { force: true });
|
|
237
|
+
removed = true;
|
|
238
|
+
}
|
|
239
|
+
return Object.freeze({ provider: spec.provider, removed });
|
|
240
|
+
}
|
|
241
|
+
|
|
45
242
|
export function resolveProviderCredential(provider, options = {}) {
|
|
46
243
|
const spec = providerCredentialSpec(provider);
|
|
47
|
-
if (!spec) return Object.freeze({ provider:
|
|
244
|
+
if (!spec) return Object.freeze({ provider: normalizeProviderId(provider), configured: false, source: null, error: 'unsupported_provider', value: '' });
|
|
48
245
|
const env = options.env || process.env;
|
|
246
|
+
const accountIdFromEnv = firstRuntimeValue(env, spec.accountEnv || []);
|
|
49
247
|
const fromEnv = clean(env?.[spec.env]);
|
|
50
|
-
if (fromEnv)
|
|
248
|
+
if (fromEnv) {
|
|
249
|
+
const prefixError = spec.tokenPrefix && !fromEnv.startsWith(spec.tokenPrefix) ? `credential_prefix_required:${spec.tokenPrefix}` : null;
|
|
250
|
+
return Object.freeze({
|
|
251
|
+
provider: spec.provider,
|
|
252
|
+
configured: !prefixError,
|
|
253
|
+
source: 'environment',
|
|
254
|
+
env: spec.env,
|
|
255
|
+
file: null,
|
|
256
|
+
error: prefixError,
|
|
257
|
+
value: prefixError ? '' : fromEnv,
|
|
258
|
+
account_id: accountIdFromEnv || null,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
51
261
|
|
|
52
|
-
const dir =
|
|
262
|
+
const dir = agentEnvDirectory({ ...options, env });
|
|
53
263
|
for (const basename of spec.files) {
|
|
54
264
|
const filename = path.join(dir, basename);
|
|
55
265
|
if (!fs.existsSync(filename)) continue;
|
|
56
266
|
try {
|
|
57
267
|
const safety = secureFile(filename);
|
|
58
268
|
if (!safety.ok) return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: safety.error, value: '' });
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
269
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
270
|
+
const value = clean(parseEnvValue(source, spec.env));
|
|
271
|
+
const accountId = accountIdFromEnv || firstEnvValue(source, spec.accountEnv || []);
|
|
272
|
+
const prefixError = spec.tokenPrefix && value && !value.startsWith(spec.tokenPrefix) ? `credential_prefix_required:${spec.tokenPrefix}` : null;
|
|
273
|
+
if (value && !prefixError) return Object.freeze({ provider: spec.provider, configured: true, source: 'agentsam_env_file', env: spec.env, file: filename, error: null, value, account_id: accountId || null });
|
|
274
|
+
return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: prefixError || 'credential_variable_missing', value: '' });
|
|
62
275
|
} catch (error) {
|
|
63
276
|
return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: error?.message || String(error), value: '' });
|
|
64
277
|
}
|
|
65
278
|
}
|
|
66
|
-
return Object.freeze({ provider: spec.provider, configured: false, source: null, env: spec.env, file: null, error: null, value: '' });
|
|
279
|
+
return Object.freeze({ provider: spec.provider, configured: false, source: null, env: spec.env, file: null, error: null, value: '', account_id: accountIdFromEnv || null });
|
|
67
280
|
}
|
|
68
281
|
|
|
69
282
|
export function describeProviderCredential(provider, options = {}) {
|
|
70
283
|
const resolved = resolveProviderCredential(provider, options);
|
|
284
|
+
const spec = providerCredentialSpec(provider);
|
|
71
285
|
return Object.freeze({
|
|
72
286
|
provider: resolved.provider,
|
|
287
|
+
label: spec?.label || resolved.provider,
|
|
73
288
|
configured: resolved.configured,
|
|
74
289
|
source: resolved.source,
|
|
75
290
|
env: resolved.env || null,
|
|
76
291
|
file: resolved.file || null,
|
|
77
292
|
error: resolved.error || null,
|
|
293
|
+
account_id: resolved.account_id || null,
|
|
78
294
|
});
|
|
79
295
|
}
|
|
80
296
|
|
|
@@ -11,10 +11,12 @@ export const SLASH_COMMANDS = [
|
|
|
11
11
|
{ cmd: '/context', description: 'Show model context economics; add repo for Git bridge context', lane: 'context' },
|
|
12
12
|
{ cmd: '/status', description: 'Local project, DB, Git, and PTY health', lane: 'local' },
|
|
13
13
|
{ cmd: '/models', description: 'Probe providers and provider-verified known models', lane: 'model' },
|
|
14
|
-
{ cmd: '/
|
|
15
|
-
{ cmd: '/
|
|
16
|
-
{ cmd: '/
|
|
14
|
+
{ cmd: '/providers', description: 'Configure and verify machine provider credentials', lane: 'model' },
|
|
15
|
+
{ cmd: '/login', description: 'Sign in to Inner Animal Media and save the machine-local Agent Sam session', lane: 'identity' },
|
|
16
|
+
{ cmd: '/logout', description: 'Sign out locally without deleting provider credentials', lane: 'identity' },
|
|
17
|
+
{ cmd: '/whoami', description: 'Show authenticated account identity and safe credential status', lane: 'identity' },
|
|
17
18
|
{ cmd: '/session', description: 'Show current session usage, cost, and resume receipt', lane: 'observability' },
|
|
19
|
+
{ cmd: '/usage', description: 'Show token usage, spend breakdown, and resume command', lane: 'observability' },
|
|
18
20
|
{ cmd: '/cf', description: 'Cloudflare native reads, Wrangler status, and CPU profile analysis', lane: 'cloudflare' },
|
|
19
21
|
{ cmd: '/settings', description: 'Choose project, runtime, terminal, and model policy' },
|
|
20
22
|
{ cmd: '/pwd', description: 'Print working directory', lane: 'terminal' },
|