@cortexlabs/forge 0.1.0-alpha.1

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.
Files changed (57) hide show
  1. package/README.md +33 -0
  2. package/SECURITY.md +5 -0
  3. package/dist/api-client.d.ts +17 -0
  4. package/dist/api-client.js +71 -0
  5. package/dist/api-client.js.map +1 -0
  6. package/dist/audit.d.ts +1 -0
  7. package/dist/audit.js +19 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +38 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands.d.ts +3 -0
  13. package/dist/commands.js +391 -0
  14. package/dist/commands.js.map +1 -0
  15. package/dist/config.d.ts +8 -0
  16. package/dist/config.js +81 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credentials.d.ts +5 -0
  19. package/dist/credentials.js +72 -0
  20. package/dist/credentials.js.map +1 -0
  21. package/dist/extensions.d.ts +7 -0
  22. package/dist/extensions.js +32 -0
  23. package/dist/extensions.js.map +1 -0
  24. package/dist/interactive.d.ts +9 -0
  25. package/dist/interactive.js +59 -0
  26. package/dist/interactive.js.map +1 -0
  27. package/dist/output.d.ts +2 -0
  28. package/dist/output.js +46 -0
  29. package/dist/output.js.map +1 -0
  30. package/dist/parser.d.ts +7 -0
  31. package/dist/parser.js +65 -0
  32. package/dist/parser.js.map +1 -0
  33. package/dist/paths.d.ts +14 -0
  34. package/dist/paths.js +31 -0
  35. package/dist/paths.js.map +1 -0
  36. package/dist/permissions.d.ts +4 -0
  37. package/dist/permissions.js +16 -0
  38. package/dist/permissions.js.map +1 -0
  39. package/dist/project.d.ts +6 -0
  40. package/dist/project.js +40 -0
  41. package/dist/project.js.map +1 -0
  42. package/dist/sessions.d.ts +13 -0
  43. package/dist/sessions.js +83 -0
  44. package/dist/sessions.js.map +1 -0
  45. package/dist/storage.d.ts +5 -0
  46. package/dist/storage.js +41 -0
  47. package/dist/storage.js.map +1 -0
  48. package/dist/trust.d.ts +10 -0
  49. package/dist/trust.js +91 -0
  50. package/dist/trust.js.map +1 -0
  51. package/dist/types.d.ts +79 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/usage.d.ts +15 -0
  55. package/dist/usage.js +40 -0
  56. package/dist/usage.js.map +1 -0
  57. package/package.json +34 -0
@@ -0,0 +1,40 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readdir } from "node:fs/promises";
3
+ import { basename, relative } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { inspectWorkspace } from "./trust.js";
6
+ const execFileAsync = promisify(execFile);
7
+ const ignored = new Set([".git", "node_modules", ".next", "dist", "build", "coverage"]);
8
+ async function walk(root, current, files, limit) {
9
+ if (files.length >= limit)
10
+ return;
11
+ const entries = await readdir(current, { withFileTypes: true }).catch(() => []);
12
+ for (const entry of entries) {
13
+ if (files.length >= limit || ignored.has(entry.name))
14
+ continue;
15
+ const path = `${current}/${entry.name}`;
16
+ if (entry.isDirectory())
17
+ await walk(root, path, files, limit);
18
+ else if (entry.isFile())
19
+ files.push(relative(root, path));
20
+ }
21
+ }
22
+ export async function collectProjectManifest(cwd, limit = 2_000) {
23
+ const workspace = await inspectWorkspace(cwd);
24
+ const root = workspace.repositoryRoot ?? workspace.path;
25
+ let files = [];
26
+ try {
27
+ const { stdout } = await execFileAsync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { cwd: root, timeout: 4_000, maxBuffer: 2_000_000 });
28
+ files = stdout.split("\n").filter(Boolean).slice(0, limit);
29
+ }
30
+ catch {
31
+ await walk(root, root, files, limit);
32
+ }
33
+ let branch = null;
34
+ try {
35
+ branch = (await execFileAsync("git", ["branch", "--show-current"], { cwd: root, timeout: 2_000 })).stdout.trim() || null;
36
+ }
37
+ catch { /* no git */ }
38
+ return { rootName: basename(root), branch, files, truncated: files.length >= limit };
39
+ }
40
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ForgeConfig, ForgeSession, SessionMessage } from "./types.js";
2
+ export declare function createSession(cwd: string, config: ForgeConfig, title?: string): Promise<ForgeSession>;
3
+ export declare function saveSession(session: ForgeSession): Promise<void>;
4
+ export declare function loadSession(id: string): Promise<ForgeSession | null>;
5
+ export declare function setCurrentSession(id: string): Promise<void>;
6
+ export declare function getCurrentSession(): Promise<ForgeSession | null>;
7
+ export declare function addMessage(session: ForgeSession, message: SessionMessage): Promise<ForgeSession>;
8
+ export declare function listSessions(): Promise<ForgeSession[]>;
9
+ export declare function forkSession(source: ForgeSession): Promise<ForgeSession>;
10
+ export declare function clearSession(session: ForgeSession): Promise<ForgeSession>;
11
+ export declare function deleteSession(id: string): Promise<void>;
12
+ export declare function compactSession(session: ForgeSession): Promise<ForgeSession>;
13
+ export declare function sessionsOlderThan(days: number): Promise<ForgeSession[]>;
@@ -0,0 +1,83 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readdir, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { forgePaths } from "./paths.js";
5
+ import { readJson, writePrivateJson } from "./storage.js";
6
+ const currentPath = () => join(forgePaths().home, "current-session.json");
7
+ const sessionPath = (id) => join(forgePaths().sessions, `${id}.json`);
8
+ export async function createSession(cwd, config, title = "New Forge task") {
9
+ const now = new Date().toISOString();
10
+ const session = {
11
+ id: randomUUID(), title, cwd, createdAt: now, updatedAt: now, state: "idle",
12
+ model: config.model, permissionMode: config.permissionMode, messages: [],
13
+ };
14
+ await saveSession(session);
15
+ await setCurrentSession(session.id);
16
+ return session;
17
+ }
18
+ export async function saveSession(session) {
19
+ const redact = (content) => content
20
+ .replace(/forge_[a-f0-9]{16,}/gi, "forge_[REDACTED]")
21
+ .replace(/\b(?:sk|pk)_[a-z0-9_-]{16,}\b/gi, "[REDACTED_SECRET]")
22
+ .replace(/\bBearer\s+[a-z0-9._~+\/-]{16,}/gi, "Bearer [REDACTED]");
23
+ const messages = session.messages.map((message) => ({ ...message, content: redact(message.content) }));
24
+ await writePrivateJson(sessionPath(session.id), { ...session, messages, updatedAt: new Date().toISOString() });
25
+ }
26
+ export async function loadSession(id) {
27
+ return readJson(sessionPath(id), null);
28
+ }
29
+ export async function setCurrentSession(id) {
30
+ await writePrivateJson(currentPath(), { id });
31
+ }
32
+ export async function getCurrentSession() {
33
+ const current = await readJson(currentPath(), {});
34
+ return current.id ? loadSession(current.id) : null;
35
+ }
36
+ export async function addMessage(session, message) {
37
+ const next = { ...session, messages: [...session.messages, message] };
38
+ await saveSession(next);
39
+ return next;
40
+ }
41
+ export async function listSessions() {
42
+ await mkdir(forgePaths().sessions, { recursive: true, mode: 0o700 });
43
+ const files = (await readdir(forgePaths().sessions)).filter((name) => name.endsWith(".json"));
44
+ const sessions = (await Promise.all(files.map((name) => loadSession(name.slice(0, -5))))).filter((item) => Boolean(item));
45
+ return sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
46
+ }
47
+ export async function forkSession(source) {
48
+ const now = new Date().toISOString();
49
+ const copy = { ...source, id: randomUUID(), title: `${source.title} (fork)`, createdAt: now, updatedAt: now, state: "idle" };
50
+ await saveSession(copy);
51
+ await setCurrentSession(copy.id);
52
+ return copy;
53
+ }
54
+ export async function clearSession(session) {
55
+ const { plan: _plan, ...rest } = session;
56
+ const next = { ...rest, messages: [], state: "idle" };
57
+ await saveSession(next);
58
+ return next;
59
+ }
60
+ export async function deleteSession(id) {
61
+ await rm(sessionPath(id), { force: true });
62
+ }
63
+ export async function compactSession(session) {
64
+ if (session.messages.length <= 20)
65
+ return session;
66
+ const archivePath = join(forgePaths().sessions, `${session.id}.archive-${Date.now()}.json`);
67
+ await writePrivateJson(archivePath, session);
68
+ const userAndSystem = session.messages.filter((message) => message.role !== "assistant");
69
+ const recentAssistant = session.messages.filter((message) => message.role === "assistant").slice(-8);
70
+ const marker = {
71
+ role: "system",
72
+ content: `Earlier assistant output was compacted after ${session.messages.length} messages. The complete transcript is archived locally. Accepted plan and every user/system message remain in this session.`,
73
+ at: new Date().toISOString(),
74
+ };
75
+ const next = { ...session, messages: [marker, ...userAndSystem, ...recentAssistant] };
76
+ await saveSession(next);
77
+ return next;
78
+ }
79
+ export async function sessionsOlderThan(days) {
80
+ const cutoff = Date.now() - days * 86_400_000;
81
+ return (await listSessions()).filter((session) => new Date(session.updatedAt).getTime() < cutoff);
82
+ }
83
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function readJsonFile<T>(path: string, fallback: T): Promise<T>;
2
+ export declare function writePrivateJson(path: string, value: unknown): Promise<void>;
3
+ export declare function appendPrivateLine(path: string, value: unknown): Promise<void>;
4
+ export declare const readJson: typeof readJsonFile;
5
+ export declare const appendPrivateJsonLine: typeof appendPrivateLine;
@@ -0,0 +1,41 @@
1
+ import { mkdir, open, readFile, rename, chmod } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export async function readJsonFile(path, fallback) {
4
+ try {
5
+ return JSON.parse(await readFile(path, "utf8"));
6
+ }
7
+ catch (error) {
8
+ const code = error.code;
9
+ if (code === "ENOENT")
10
+ return fallback;
11
+ throw error;
12
+ }
13
+ }
14
+ export async function writePrivateJson(path, value) {
15
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
16
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
17
+ const handle = await open(temporary, "w", 0o600);
18
+ try {
19
+ await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, "utf8");
20
+ await handle.sync();
21
+ }
22
+ finally {
23
+ await handle.close();
24
+ }
25
+ await rename(temporary, path);
26
+ await chmod(path, 0o600).catch(() => undefined);
27
+ }
28
+ export async function appendPrivateLine(path, value) {
29
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
30
+ const handle = await open(path, "a", 0o600);
31
+ try {
32
+ await handle.writeFile(`${JSON.stringify(value)}\n`, "utf8");
33
+ }
34
+ finally {
35
+ await handle.close();
36
+ }
37
+ await chmod(path, 0o600).catch(() => undefined);
38
+ }
39
+ export const readJson = readJsonFile;
40
+ export const appendPrivateJsonLine = appendPrivateLine;
41
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAI,IAAY,EAAE,QAAW;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;QACnD,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,QAAQ,CAAC;QACvC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,KAAc;IACjE,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtE,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9B,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAc;IAClE,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC/D,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { TrustRecord } from "./types.js";
2
+ export declare function inspectWorkspace(cwd: string): Promise<Omit<TrustRecord, "scope" | "trustedAt">>;
3
+ export declare function assertWorkspaceBoundary(root: string, target: string): Promise<string>;
4
+ export declare function loadTrustRecords(): Promise<TrustRecord[]>;
5
+ export declare function findTrustRecord(cwd: string): Promise<{
6
+ record: TrustRecord | null;
7
+ current: Awaited<ReturnType<typeof inspectWorkspace>>;
8
+ }>;
9
+ export declare function trustWorkspaceOnce(cwd: string): Promise<TrustRecord>;
10
+ export declare function trustWorkspace(cwd: string): Promise<TrustRecord>;
package/dist/trust.js ADDED
@@ -0,0 +1,91 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readdir, realpath } from "node:fs/promises";
3
+ import { join, relative, resolve } from "node:path";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { forgePaths } from "./paths.js";
7
+ import { readJson, writePrivateJson } from "./storage.js";
8
+ const execFileAsync = promisify(execFile);
9
+ const instructionNames = ["AGENTS.md", "CLAUDE.md", "FORGE.md", ".forge/instructions.md"];
10
+ const executableNames = [".mcp.json", ".forge/config.json", "package.json"];
11
+ const trustedOnce = new Map();
12
+ async function git(cwd, args) {
13
+ try {
14
+ const { stdout } = await execFileAsync("git", args, { cwd, timeout: 2_000 });
15
+ return stdout.trim() || null;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ async function existingFiles(root, names) {
22
+ const found = [];
23
+ for (const name of names) {
24
+ try {
25
+ const path = join(root, name);
26
+ const stat = await lstat(path);
27
+ if (stat.isFile())
28
+ found.push(name);
29
+ }
30
+ catch { /* optional file */ }
31
+ }
32
+ return found;
33
+ }
34
+ async function detectedExecutableConfig(root) {
35
+ const files = await existingFiles(root, executableNames);
36
+ const hooks = await readdir(join(root, ".git", "hooks"), { withFileTypes: true }).catch(() => []);
37
+ for (const hook of hooks) {
38
+ if (hook.isFile() && !hook.name.endsWith(".sample"))
39
+ files.push(`.git/hooks/${hook.name}`);
40
+ }
41
+ return files;
42
+ }
43
+ export async function inspectWorkspace(cwd) {
44
+ const path = await realpath(resolve(cwd));
45
+ const repositoryRoot = await git(path, ["rev-parse", "--show-toplevel"]);
46
+ const root = repositoryRoot ? await realpath(repositoryRoot) : path;
47
+ const remoteUrl = repositoryRoot ? await git(root, ["remote", "get-url", "origin"]) : null;
48
+ const instructionFiles = await existingFiles(root, instructionNames);
49
+ const executableConfigFiles = await detectedExecutableConfig(root);
50
+ const hash = createHash("sha256");
51
+ hash.update(JSON.stringify({ path, repositoryRoot: root, remoteUrl, instructionFiles, executableConfigFiles }));
52
+ for (const name of [...instructionFiles, ...executableConfigFiles]) {
53
+ try {
54
+ const stat = await lstat(join(root, name));
55
+ hash.update(`${name}:${stat.size}:${stat.mtimeMs}:${stat.mode}`);
56
+ }
57
+ catch { /* changed during inspection */ }
58
+ }
59
+ return { path, repositoryRoot: root, remoteUrl, instructionFiles, executableConfigFiles, fingerprint: hash.digest("hex") };
60
+ }
61
+ export async function assertWorkspaceBoundary(root, target) {
62
+ const canonicalRoot = await realpath(root);
63
+ const canonicalTarget = await realpath(target);
64
+ const traversal = relative(canonicalRoot, canonicalTarget);
65
+ if (traversal === "" || (!traversal.startsWith("..") && !traversal.startsWith("/")))
66
+ return canonicalTarget;
67
+ throw new Error(`Path escapes trusted workspace: ${target}`);
68
+ }
69
+ export async function loadTrustRecords() {
70
+ return readJson(forgePaths().trust, []);
71
+ }
72
+ export async function findTrustRecord(cwd) {
73
+ const current = await inspectWorkspace(cwd);
74
+ const records = await loadTrustRecords();
75
+ return { record: trustedOnce.get(current.fingerprint) ?? records.find((item) => item.path === current.path && item.fingerprint === current.fingerprint) ?? null, current };
76
+ }
77
+ export async function trustWorkspaceOnce(cwd) {
78
+ const current = await inspectWorkspace(cwd);
79
+ const record = { ...current, scope: "once", trustedAt: new Date().toISOString() };
80
+ trustedOnce.set(current.fingerprint, record);
81
+ return record;
82
+ }
83
+ export async function trustWorkspace(cwd) {
84
+ const current = await inspectWorkspace(cwd);
85
+ const records = (await loadTrustRecords()).filter((item) => item.path !== current.path);
86
+ const record = { ...current, scope: "always", trustedAt: new Date().toISOString() };
87
+ records.push(record);
88
+ await writePrivateJson(forgePaths().trust, records);
89
+ return record;
90
+ }
91
+ //# sourceMappingURL=trust.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust.js","sourceRoot":"","sources":["../src/trust.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ export type OutputMode = "human" | "json";
2
+ export type PermissionMode = "read-only" | "ask" | "workspace-write" | "trusted-session" | "custom" | "autonomous";
3
+ export type TaskMode = "plan" | "execute" | "review" | "read-only";
4
+ export type TaskState = "idle" | "thinking" | "planning" | "executing" | "waiting-for-approval" | "completed" | "cancelled" | "failed";
5
+ export interface ForgeConfig {
6
+ apiBaseUrl: string;
7
+ model: string;
8
+ permissionMode: PermissionMode;
9
+ taskMode: TaskMode;
10
+ reducedMotion: boolean;
11
+ }
12
+ export interface TrustRecord {
13
+ path: string;
14
+ repositoryRoot: string | null;
15
+ remoteUrl: string | null;
16
+ instructionFiles: string[];
17
+ executableConfigFiles: string[];
18
+ fingerprint: string;
19
+ scope: "once" | "always";
20
+ trustedAt: string;
21
+ }
22
+ export interface SessionMessage {
23
+ role: "user" | "assistant" | "system";
24
+ content: string;
25
+ at: string;
26
+ }
27
+ export interface ForgePlan {
28
+ task: string;
29
+ steps: string[];
30
+ status: "draft" | "approved" | "cancelled" | "completed";
31
+ createdAt: string;
32
+ approvedAt?: string;
33
+ }
34
+ export interface ForgeSession {
35
+ id: string;
36
+ title: string;
37
+ cwd: string;
38
+ createdAt: string;
39
+ updatedAt: string;
40
+ state: TaskState;
41
+ model: string;
42
+ permissionMode: PermissionMode;
43
+ messages: SessionMessage[];
44
+ plan?: ForgePlan;
45
+ usage?: SessionUsage;
46
+ }
47
+ export interface SessionUsage {
48
+ inputTokens: number;
49
+ cachedInputTokens: number;
50
+ outputTokens: number;
51
+ totalTokens: number;
52
+ estimatedTokens: number;
53
+ credits: number;
54
+ contextWindow: number | null;
55
+ updatedAt: string;
56
+ }
57
+ export interface CliEvent {
58
+ type: "info" | "success" | "warning" | "error" | "text-delta" | "status" | "usage" | "prompt";
59
+ message?: string;
60
+ data?: Record<string, unknown>;
61
+ at: string;
62
+ }
63
+ export interface CommandContext {
64
+ cwd: string;
65
+ args: string[];
66
+ flags: Map<string, string | boolean>;
67
+ outputMode: OutputMode;
68
+ signal: AbortSignal;
69
+ emit(event: Omit<CliEvent, "at">): void;
70
+ requestApproval?(question: string, defaultValue?: boolean): Promise<boolean>;
71
+ }
72
+ export interface CommandDefinition {
73
+ name: string;
74
+ aliases?: string[];
75
+ summary: string;
76
+ usage: string;
77
+ interactive: boolean;
78
+ run(context: CommandContext): Promise<number>;
79
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import type { SessionUsage } from "./types.js";
2
+ export interface UsageEventData {
3
+ inputTokens?: number | null;
4
+ cachedInputTokens?: number | null;
5
+ outputTokens?: number | null;
6
+ totalTokens?: number | null;
7
+ estimatedOutputTokens?: number | null;
8
+ credits?: number | null;
9
+ contextWindow?: number | null;
10
+ provenance?: "provider" | "estimated";
11
+ }
12
+ export declare function emptyUsage(contextWindow?: number | null): SessionUsage;
13
+ export declare function mergeUsage(current: SessionUsage | undefined, turn: UsageEventData): SessionUsage;
14
+ export declare function contextSummary(usage: SessionUsage | undefined): string;
15
+ export declare function contextWarning(usage: SessionUsage | undefined): string | null;
package/dist/usage.js ADDED
@@ -0,0 +1,40 @@
1
+ export function emptyUsage(contextWindow = null) {
2
+ return { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0, totalTokens: 0, estimatedTokens: 0, credits: 0, contextWindow, updatedAt: new Date().toISOString() };
3
+ }
4
+ export function mergeUsage(current, turn) {
5
+ const base = current ?? emptyUsage(turn.contextWindow ?? null);
6
+ const exact = turn.totalTokens ?? ((turn.inputTokens ?? 0) + (turn.outputTokens ?? 0));
7
+ return {
8
+ inputTokens: base.inputTokens + (turn.inputTokens ?? 0),
9
+ cachedInputTokens: base.cachedInputTokens + (turn.cachedInputTokens ?? 0),
10
+ outputTokens: base.outputTokens + (turn.outputTokens ?? 0),
11
+ totalTokens: base.totalTokens + exact,
12
+ estimatedTokens: base.estimatedTokens + (turn.provenance === "estimated" ? (turn.estimatedOutputTokens ?? exact) : 0),
13
+ credits: base.credits + (turn.credits ?? 0),
14
+ contextWindow: turn.contextWindow ?? base.contextWindow,
15
+ updatedAt: new Date().toISOString(),
16
+ };
17
+ }
18
+ export function contextSummary(usage) {
19
+ if (!usage)
20
+ return "context: not reported";
21
+ const label = usage.estimatedTokens > 0 ? "includes estimate" : "exact provider usage";
22
+ if (!usage.contextWindow)
23
+ return `context: ${usage.totalTokens.toLocaleString()} tokens (${label}; window unknown)`;
24
+ const used = Math.min(usage.totalTokens, usage.contextWindow);
25
+ const percent = Math.round((used / usage.contextWindow) * 100);
26
+ return `context: ${percent}% · ${used.toLocaleString()} / ${usage.contextWindow.toLocaleString()} · ${(usage.contextWindow - used).toLocaleString()} remaining (${label})`;
27
+ }
28
+ export function contextWarning(usage) {
29
+ if (!usage?.contextWindow)
30
+ return null;
31
+ const percent = (usage.totalTokens / usage.contextWindow) * 100;
32
+ if (percent >= 95)
33
+ return "Context is at or above 95%. Compact before continuing.";
34
+ if (percent >= 85)
35
+ return "Context is at or above 85%. Plan a compaction soon.";
36
+ if (percent >= 70)
37
+ return "Context is at or above 70%.";
38
+ return null;
39
+ }
40
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@cortexlabs/forge",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Forge CLI - a trustworthy AI engineering terminal",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "private": false,
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "tag": "next"
11
+ },
12
+ "bin": {
13
+ "forge": "dist/cli.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "SECURITY.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit",
26
+ "test": "npm run build && node --test test/*.test.mjs",
27
+ "pack:check": "npm pack --dry-run",
28
+ "release:next": "npm publish --access public --tag next"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.19.0",
32
+ "typescript": "^5.9.0"
33
+ }
34
+ }