privateer-agent 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +143 -398
  2. package/bin/privateer-tui +86 -0
  3. package/bin/privateer.mjs +17 -6
  4. package/bin/pv +28 -0
  5. package/package.json +26 -23
  6. package/src/auth/privateer.ts +128 -1
  7. package/src/boot.ts +43 -0
  8. package/src/bridge/engineAdapter.ts +182 -0
  9. package/src/cli/chat.ts +243 -0
  10. package/src/config/paths.ts +24 -44
  11. package/src/config/permissionMode.ts +5 -0
  12. package/src/crypto/outboxSeal.ts +61 -0
  13. package/src/daemon/index.ts +405 -0
  14. package/src/daemon/ipc.ts +127 -0
  15. package/src/engine/errors.ts +95 -45
  16. package/src/engine/router.ts +11 -165
  17. package/src/ext/permissionGate.ts +216 -0
  18. package/src/main.ts +32 -0
  19. package/src/permissions/classify.ts +172 -0
  20. package/src/permissions/gate.ts +13 -11
  21. package/src/permissions/mode.ts +9 -1
  22. package/src/permissions/{uiGate.ts → modeGate.ts} +22 -6
  23. package/src/providers/account.ts +170 -0
  24. package/src/providers/catalog.ts +73 -61
  25. package/src/providers/genModelsJson.ts +97 -0
  26. package/src/remote/relayClient.ts +91 -4
  27. package/src/remote/remoteBridge.ts +152 -0
  28. package/src/routines/cron.ts +109 -0
  29. package/src/routines/delivery.ts +184 -0
  30. package/src/routines/schema.ts +84 -0
  31. package/src/routines/store.ts +248 -0
  32. package/src/routines/toolSelect.ts +50 -0
  33. package/src/routines/trigger.ts +41 -0
  34. package/src/session.ts +84 -251
  35. package/src/tools/routine.ts +129 -0
  36. package/src/tools/saveAttachment.ts +39 -42
  37. package/src/tools/sendFile.ts +75 -0
  38. package/src/util/attachmentStore.ts +18 -35
  39. package/src/util/redact.ts +33 -3
  40. package/LICENSE +0 -21
  41. package/src/agents/loader.ts +0 -49
  42. package/src/commands/custom.ts +0 -75
  43. package/src/commands/registry.ts +0 -499
  44. package/src/components/AgentGroupView.tsx +0 -104
  45. package/src/components/App.tsx +0 -1420
  46. package/src/components/ApprovalPrompt.tsx +0 -38
  47. package/src/components/Banner.tsx +0 -76
  48. package/src/components/Markdown.tsx +0 -183
  49. package/src/components/ModeHint.tsx +0 -40
  50. package/src/components/ModelPicker.tsx +0 -269
  51. package/src/components/Onboarding.tsx +0 -203
  52. package/src/components/PlanConfirm.tsx +0 -37
  53. package/src/components/PrivateerLogin.tsx +0 -109
  54. package/src/components/PromptInput.tsx +0 -602
  55. package/src/components/RewindPicker.tsx +0 -69
  56. package/src/components/Root.tsx +0 -95
  57. package/src/components/SessionPicker.tsx +0 -64
  58. package/src/components/StatusBar.tsx +0 -131
  59. package/src/components/TodoPanel.tsx +0 -36
  60. package/src/components/ToolCallView.tsx +0 -109
  61. package/src/components/Transcript.tsx +0 -203
  62. package/src/components/figures.ts +0 -14
  63. package/src/components/promptModel.ts +0 -73
  64. package/src/components/spinnerVerbs.ts +0 -46
  65. package/src/components/theme.ts +0 -55
  66. package/src/components/types.ts +0 -34
  67. package/src/components/useTeeShield.ts +0 -104
  68. package/src/components/useTerminalWidth.ts +0 -24
  69. package/src/components/useZdrShield.ts +0 -126
  70. package/src/config/load.ts +0 -115
  71. package/src/config/schema.ts +0 -94
  72. package/src/context/outputStyles.ts +0 -42
  73. package/src/context/projectInfo.ts +0 -59
  74. package/src/context/systemPrompt.ts +0 -167
  75. package/src/engine/QueryEngine.ts +0 -399
  76. package/src/hooks/engine.ts +0 -155
  77. package/src/main.tsx +0 -167
  78. package/src/mcp/client.ts +0 -236
  79. package/src/mcp/oauth.ts +0 -245
  80. package/src/memory/auto.ts +0 -146
  81. package/src/memory/checkpoints.ts +0 -227
  82. package/src/memory/store.ts +0 -127
  83. package/src/providers/attestation.ts +0 -149
  84. package/src/providers/capabilities.ts +0 -104
  85. package/src/providers/models.ts +0 -183
  86. package/src/providers/registry.ts +0 -71
  87. package/src/providers/resolve.ts +0 -78
  88. package/src/tools/bash.ts +0 -98
  89. package/src/tools/context.ts +0 -114
  90. package/src/tools/edit.ts +0 -67
  91. package/src/tools/exec.ts +0 -60
  92. package/src/tools/glob.ts +0 -39
  93. package/src/tools/grep.ts +0 -86
  94. package/src/tools/index.ts +0 -69
  95. package/src/tools/memory.ts +0 -53
  96. package/src/tools/processRegistry.ts +0 -77
  97. package/src/tools/read.ts +0 -42
  98. package/src/tools/task.ts +0 -52
  99. package/src/tools/todo.ts +0 -36
  100. package/src/tools/todoStore.ts +0 -31
  101. package/src/tools/walk.ts +0 -44
  102. package/src/tools/web.ts +0 -145
  103. package/src/tools/write.ts +0 -40
  104. package/src/util/images.ts +0 -356
  105. package/src/util/limit.ts +0 -32
  106. package/src/version.ts +0 -13
@@ -1,54 +1,39 @@
1
1
  import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, extname } from "node:path";
4
- import type { Attachment, Modality } from "./images.ts";
5
4
 
6
- // One stored attachment: its bytes persisted to a stable scratch file, keyed by the
7
- // "#n" reference the user sees in the prompt.
5
+ // Holds the decoded bytes of files the app sends down over the relay, in a private
6
+ // scratch dir keyed by the "#n" reference the model sees, so the save_attachment tool
7
+ // can write one back out to a real path on demand. Ported/adapted from tree-cli
8
+ // (which persisted paste/drop bytes); here the source is inbound relay attachments.
9
+
8
10
  export interface StoredAttachment {
9
11
  n: number;
10
12
  path: string; // absolute scratch-file path holding the decoded bytes
11
13
  mediaType: string;
12
- modality: Modality;
13
- origin: string; // the token the user referenced (for display/debugging)
14
+ name: string; // original filename from the app
14
15
  }
15
16
 
16
- // Keeps the decoded bytes of every attachment seen this session in a private scratch
17
- // dir, keyed by its "[Kind #n]" reference, so the `save_attachment` tool can write one
18
- // back out to a real path on demand.
19
- //
20
- // This exists to close the macOS file-promise drop gap: the bytes we capture at
21
- // paste-time (in resolveAttachments) are the only durable copy — the terminal's
22
- // …/T/drop-XXXXXX/ file is a volatile stub that may already be gone or truncated by the
23
- // time the agent goes looking. We copy what we captured into a place we control, once,
24
- // instead of trusting that path twice.
25
17
  export class AttachmentStore {
26
18
  private dir: string | null = null;
27
19
  private readonly byN = new Map<number, StoredAttachment>();
20
+ private nextN = 1;
28
21
 
29
22
  private ensureDir(): string {
30
- // mkdtemp gives a 0700 dir; created lazily so a session that never attaches
31
- // anything leaves no temp files behind.
23
+ // mkdtemp gives a 0700 dir; created lazily so a session that never receives an
24
+ // attachment leaves no temp files behind.
32
25
  if (!this.dir) this.dir = mkdtempSync(join(tmpdir(), "privateer-att-"));
33
26
  return this.dir;
34
27
  }
35
28
 
36
- // Persist an attachment's bytes to the scratch dir, keyed by n. Idempotent: the same
37
- // n (a file referenced twice in a session) is written once and reused.
38
- register(att: Attachment): StoredAttachment | undefined {
39
- if (att.n == null) return undefined;
40
- const existing = this.byN.get(att.n);
41
- if (existing) return existing;
42
- const file = join(this.ensureDir(), `att-${att.n}${extname(att.path)}`);
43
- writeFileSync(file, Buffer.from(att.data, "base64"));
44
- const stored: StoredAttachment = {
45
- n: att.n,
46
- path: file,
47
- mediaType: att.mediaType,
48
- modality: att.modality,
49
- origin: att.path,
50
- };
51
- this.byN.set(att.n, stored);
29
+ // Persist an inbound attachment's bytes, assign it the next ref number, and return
30
+ // the stored record (its ref + scratch path).
31
+ register(file: { name: string; mediaType: string; base64: string }): StoredAttachment {
32
+ const n = this.nextN++;
33
+ const path = join(this.ensureDir(), `att-${n}${extname(file.name) || ""}`);
34
+ writeFileSync(path, Buffer.from(file.base64, "base64"));
35
+ const stored: StoredAttachment = { n, path, mediaType: file.mediaType, name: file.name };
36
+ this.byN.set(n, stored);
52
37
  return stored;
53
38
  }
54
39
 
@@ -56,13 +41,11 @@ export class AttachmentStore {
56
41
  return this.byN.get(n);
57
42
  }
58
43
 
59
- // The reference numbers currently held, ascending — for a helpful error when the
60
- // model asks for one that isn't there.
44
+ // Held reference numbers, ascending — for a helpful error when a missing one is asked for.
61
45
  refs(): number[] {
62
46
  return [...this.byN.keys()].sort((a, b) => a - b);
63
47
  }
64
48
 
65
- // Remove the scratch dir (e.g. on session exit). Safe to call when nothing was stored.
66
49
  cleanup(): void {
67
50
  if (!this.dir) return;
68
51
  rmSync(this.dir, { recursive: true, force: true });
@@ -6,11 +6,22 @@
6
6
  const PLACEHOLDER = "«redacted»";
7
7
 
8
8
  // Common API-key shapes, masked even when we don't have the exact value on hand:
9
- // OpenAI `sk-…`, Anthropic `sk-ant-…`, OpenRouter `sk-or-v1-…`, and bare
10
- // "Bearer <token>" / "x-api-key: <token>" header fragments.
9
+ // OpenAI `sk-…`, Anthropic `sk-ant-…`, OpenRouter `sk-or-v1-…`, Google `AIza…`,
10
+ // xAI `xai-…`, Groq `gsk_…`, Z.ai/Zhipu `<32 hex>.<suffix>`, Cerebras `csk-…`,
11
+ // Venice `vapi_…`, Fireworks `fw_…`, and bare
12
+ // "Bearer <token>" / "x-api-key: <token>" / "x-goog-api-key: <token>" header
13
+ // fragments. (Mistral and Together keys are prefix-less, so they rely on
14
+ // exact-value masking and the header patterns.)
11
15
  const KEY_PATTERNS: RegExp[] = [
12
16
  /\bsk-(ant|or|proj|live|test)?-?[A-Za-z0-9_-]{16,}\b/g,
13
- /\b(authorization|x-api-key)\b\s*[:=]\s*(bearer\s+)?["']?[A-Za-z0-9_\-.]{16,}["']?/gi,
17
+ /\bAIza[A-Za-z0-9_-]{20,}\b/g,
18
+ /\bxai-[A-Za-z0-9_-]{16,}\b/g,
19
+ /\bgsk_[A-Za-z0-9_-]{16,}\b/g,
20
+ /\bcsk-[A-Za-z0-9_-]{16,}\b/g,
21
+ /\bvapi_[A-Za-z0-9_-]{16,}\b/g,
22
+ /\bfw_[A-Za-z0-9_-]{16,}\b/g,
23
+ /\b[a-f0-9]{32}\.[A-Za-z0-9]{10,}\b/g,
24
+ /\b(authorization|x-api-key|x-goog-api-key)\b\s*[:=]\s*(bearer\s+)?["']?[A-Za-z0-9_\-.]{16,}["']?/gi,
14
25
  ];
15
26
 
16
27
  // Exact secret strings to mask, gathered from the resolved config + environment.
@@ -26,8 +37,27 @@ export function collectSecrets(providers?: Record<string, { apiKey?: string } |
26
37
  "OPENROUTER_API_KEY",
27
38
  "ANTHROPIC_API_KEY",
28
39
  "OPENAI_API_KEY",
40
+ "GOOGLE_GENERATIVE_AI_API_KEY",
41
+ "GEMINI_API_KEY",
42
+ "GOOGLE_API_KEY",
43
+ "XAI_API_KEY",
44
+ "GROQ_API_KEY",
45
+ "MISTRAL_API_KEY",
46
+ "ZAI_API_KEY",
47
+ "Z_AI_API_KEY",
48
+ "MOONSHOT_API_KEY",
49
+ "CEREBRAS_API_KEY",
50
+ "FIREWORKS_API_KEY",
51
+ "TOGETHER_API_KEY",
52
+ "TOGETHER_AI_API_KEY",
53
+ "DEEPSEEK_API_KEY",
54
+ "MINIMAX_API_KEY",
55
+ "DASHSCOPE_API_KEY",
56
+ "QWEN_API_KEY",
29
57
  "NEAR_AI_API_KEY",
30
58
  "NEARAI_API_KEY",
59
+ "TINFOIL_API_KEY",
60
+ "VENICE_API_KEY",
31
61
  ])
32
62
  add(process.env[k]);
33
63
  return [...out];
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Patrick
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,49 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { globalPaths, projectPaths } from "../config/paths.ts";
4
- import { walkFiles } from "../tools/walk.ts";
5
- import { parseFrontmatter } from "../commands/custom.ts";
6
-
7
- // A user-defined sub-agent loaded from .privateer/agents/<name>.md. The body is the
8
- // agent's instructions (system prompt); frontmatter narrows its tools and model.
9
- export interface AgentDefinition {
10
- name: string;
11
- description: string;
12
- tools?: string[]; // tool names the agent may use (default: read/glob/grep)
13
- model?: string; // optional "provider:model" override
14
- prompt: string; // the agent's instructions
15
- scope: "project" | "user";
16
- }
17
-
18
- function loadFromDir(dir: string, scope: "project" | "user"): AgentDefinition[] {
19
- if (!existsSync(dir)) return [];
20
- const out: AgentDefinition[] = [];
21
- for (const rel of walkFiles(dir)) {
22
- if (!rel.endsWith(".md")) continue;
23
- const { meta, body } = parseFrontmatter(readFileSync(join(dir, rel), "utf8"));
24
- out.push({
25
- name: meta.name || rel.replace(/\.md$/, "").split("/").join(":"),
26
- description: meta.description ?? `custom ${scope} sub-agent`,
27
- tools: meta.tools
28
- ?.split(",")
29
- .map((s) => s.trim())
30
- .filter(Boolean),
31
- model: meta.model,
32
- prompt: body.trim(),
33
- scope,
34
- });
35
- }
36
- return out;
37
- }
38
-
39
- // User (~/.privateer) then project (./.privateer) agents; project overrides user.
40
- export function loadAgents(cwd: string = process.cwd()): AgentDefinition[] {
41
- const byName = new Map<string, AgentDefinition>();
42
- for (const a of loadFromDir(globalPaths().agents, "user")) byName.set(a.name, a);
43
- for (const a of loadFromDir(projectPaths(cwd).agents, "project")) byName.set(a.name, a);
44
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
45
- }
46
-
47
- export function findAgent(name: string, cwd: string = process.cwd()): AgentDefinition | undefined {
48
- return loadAgents(cwd).find((a) => a.name === name);
49
- }
@@ -1,75 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { globalPaths, projectPaths } from "../config/paths.ts";
4
- import { walkFiles } from "../tools/walk.ts";
5
-
6
- // A user-authored slash command loaded from a markdown file under
7
- // .privateer/commands/. The body is a prompt template; frontmatter is optional.
8
- export interface CustomCommand {
9
- name: string; // file path minus .md, separators → ":" (e.g. "git/pr.md" → "git:pr")
10
- description: string;
11
- argumentHint?: string;
12
- allowedTools?: string[];
13
- model?: string;
14
- body: string;
15
- scope: "project" | "user";
16
- }
17
-
18
- // Minimal YAML-ish frontmatter: a leading `---` block of `key: value` lines.
19
- export function parseFrontmatter(raw: string): { meta: Record<string, string>; body: string } {
20
- const lines = raw.split("\n");
21
- if (lines[0]?.trim() !== "---") return { meta: {}, body: raw };
22
- const meta: Record<string, string> = {};
23
- let i = 1;
24
- for (; i < lines.length; i++) {
25
- if (lines[i].trim() === "---") {
26
- i++;
27
- break;
28
- }
29
- const m = lines[i].match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
30
- if (m) meta[m[1].toLowerCase()] = m[2].trim();
31
- }
32
- return { meta, body: lines.slice(i).join("\n").replace(/^\n+/, "") };
33
- }
34
-
35
- function loadFromDir(dir: string, scope: "project" | "user"): CustomCommand[] {
36
- if (!existsSync(dir)) return [];
37
- const out: CustomCommand[] = [];
38
- for (const rel of walkFiles(dir)) {
39
- if (!rel.endsWith(".md")) continue;
40
- const { meta, body } = parseFrontmatter(readFileSync(join(dir, rel), "utf8"));
41
- out.push({
42
- name: rel.replace(/\.md$/, "").split("/").join(":"),
43
- description: meta.description ?? `custom ${scope} command`,
44
- argumentHint: meta["argument-hint"],
45
- allowedTools: meta["allowed-tools"]
46
- ?.split(",")
47
- .map((s) => s.trim())
48
- .filter(Boolean),
49
- model: meta.model,
50
- body: body.trim(),
51
- scope,
52
- });
53
- }
54
- return out;
55
- }
56
-
57
- // Load custom commands from user (~/.privateer) then project (./.privateer);
58
- // a project command overrides a user command of the same name.
59
- export function loadCustomCommands(cwd: string = process.cwd()): CustomCommand[] {
60
- const byName = new Map<string, CustomCommand>();
61
- for (const c of loadFromDir(globalPaths().commands, "user")) byName.set(c.name, c);
62
- for (const c of loadFromDir(projectPaths(cwd).commands, "project")) byName.set(c.name, c);
63
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
64
- }
65
-
66
- // Expand a command body against its arguments: $1..$9 are positional words,
67
- // $ARGUMENTS and $@ are the full argument string.
68
- export function expandCommand(cmd: CustomCommand, argString: string): string {
69
- const args = argString.trim();
70
- const parts = args.length ? args.split(/\s+/) : [];
71
- return cmd.body
72
- .replace(/\$([1-9])/g, (_, d: string) => parts[Number(d) - 1] ?? "")
73
- .replace(/\$ARGUMENTS\b/g, args)
74
- .replace(/\$@/g, args);
75
- }