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,203 +0,0 @@
1
- import React, { useState } from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import TextInput from "ink-text-input";
4
- import type { Config, ProviderName, ProviderConfig } from "../config/schema.ts";
5
- import { PROVIDER_LIST, PROVIDER_META, type ProviderMeta } from "../providers/catalog.ts";
6
- import { providerRequiresKey } from "../providers/registry.ts";
7
- import { ModelPicker } from "./ModelPicker.tsx";
8
- import { theme } from "./theme.ts";
9
- import { WELCOME } from "./figures.ts";
10
-
11
- // Which of the just-entered providers are actually usable (have a key, or are keyless).
12
- function readyProviders(creds: Partial<Record<ProviderName, ProviderConfig>>): ProviderName[] {
13
- return (Object.keys(creds) as ProviderName[]).filter((name) =>
14
- providerRequiresKey(name) ? Boolean(creds[name]?.apiKey) : true,
15
- );
16
- }
17
-
18
- export interface OnboardingResult {
19
- providers: Partial<Record<ProviderName, ProviderConfig>>;
20
- defaultModel: string;
21
- }
22
-
23
- // Step 1 — multi-select the providers to configure. Arrows/jk move, space toggles,
24
- // enter confirms (needs at least one).
25
- function SelectStep({
26
- initial,
27
- onConfirm,
28
- }: {
29
- initial: Set<ProviderName>;
30
- onConfirm: (selected: ProviderMeta[]) => void;
31
- }) {
32
- const [cursor, setCursor] = useState(0);
33
- const [selected, setSelected] = useState<Set<ProviderName>>(new Set(initial));
34
-
35
- useInput((input, key) => {
36
- if (key.upArrow || input === "k") setCursor((c) => (c - 1 + PROVIDER_LIST.length) % PROVIDER_LIST.length);
37
- else if (key.downArrow || input === "j") setCursor((c) => (c + 1) % PROVIDER_LIST.length);
38
- else if (input === " ") {
39
- const name = PROVIDER_LIST[cursor].name;
40
- setSelected((s) => {
41
- const next = new Set(s);
42
- next.has(name) ? next.delete(name) : next.add(name);
43
- return next;
44
- });
45
- } else if (key.return && selected.size > 0) {
46
- onConfirm(PROVIDER_LIST.filter((p) => selected.has(p.name)));
47
- }
48
- });
49
-
50
- return (
51
- <Box flexDirection="column">
52
- <Text color={theme.dim}>
53
- Select the providers you want to use — <Text color={theme.accent}>space</Text> to toggle,{" "}
54
- <Text color={theme.accent}>enter</Text> to continue.
55
- </Text>
56
- <Box flexDirection="column" marginTop={1}>
57
- {PROVIDER_LIST.map((p, i) => {
58
- const on = selected.has(p.name);
59
- const active = i === cursor;
60
- return (
61
- <Text key={p.name} color={active ? theme.accent : undefined}>
62
- {active ? "❯ " : " "}
63
- {on ? "◉" : "○"} {p.label.padEnd(16)}
64
- <Text color={theme.dim}> {p.requiresKey ? `key: ${p.keyHint}` : p.keyHint}</Text>
65
- </Text>
66
- );
67
- })}
68
- </Box>
69
- {selected.size === 0 && (
70
- <Text color={theme.dim}>{"\n"}Select at least one provider to continue.</Text>
71
- )}
72
- </Box>
73
- );
74
- }
75
-
76
- // Step 2 — walk the chosen providers one at a time, collecting a masked API key (or a
77
- // base URL for keyless/local providers). Empty key = skip that provider's credential.
78
- function KeyStep({
79
- providers,
80
- onDone,
81
- }: {
82
- providers: ProviderMeta[];
83
- onDone: (creds: Partial<Record<ProviderName, ProviderConfig>>) => void;
84
- }) {
85
- const [index, setIndex] = useState(0);
86
- const [value, setValue] = useState("");
87
- const [creds, setCreds] = useState<Partial<Record<ProviderName, ProviderConfig>>>({});
88
- const meta = providers[index];
89
-
90
- function submit(raw: string) {
91
- const v = raw.trim();
92
- const entry: ProviderConfig = meta.requiresKey
93
- ? v
94
- ? { apiKey: v }
95
- : {}
96
- : { baseURL: v || meta.baseURLDefault };
97
- const nextCreds = { ...creds, [meta.name]: entry };
98
- setCreds(nextCreds);
99
- setValue("");
100
- if (index + 1 < providers.length) setIndex(index + 1);
101
- else onDone(nextCreds);
102
- }
103
-
104
- return (
105
- <Box flexDirection="column">
106
- <Text color={theme.dim}>
107
- Step {index + 1} of {providers.length} —{" "}
108
- <Text color={theme.accent}>{meta.label}</Text>
109
- </Text>
110
- <Text color={theme.dim}>
111
- {meta.requiresKey
112
- ? `Paste your API key (${meta.keyHint}). Enter to skip.`
113
- : `Base URL for Ollama. Enter to use the default.`}
114
- </Text>
115
- <Box marginTop={1} borderStyle="round" borderColor={theme.accent} paddingX={1}>
116
- <Text color={theme.accent}>{"> "}</Text>
117
- <TextInput
118
- value={value}
119
- onChange={setValue}
120
- onSubmit={submit}
121
- mask={meta.requiresKey ? "*" : undefined}
122
- placeholder={meta.requiresKey ? "sk-…" : meta.baseURLDefault}
123
- />
124
- </Box>
125
- </Box>
126
- );
127
- }
128
-
129
- // Step 3 — pick the default model from the providers just configured, with the model
130
- // list fetched live using the entered keys. Esc (or a fetch failure) falls back to the
131
- // provider's catalog default, so onboarding always completes.
132
- function ModelStep({
133
- creds,
134
- ready,
135
- fallback,
136
- onDone,
137
- }: {
138
- creds: Partial<Record<ProviderName, ProviderConfig>>;
139
- ready: ProviderName[];
140
- fallback: string;
141
- onDone: (spec: string) => void;
142
- }) {
143
- // ModelPicker only reads config.providers; a synthetic config is enough here.
144
- const synthetic = { providers: creds } as Config;
145
- return (
146
- <Box flexDirection="column">
147
- <Text color={theme.dim}>Choose your default model — you can change it later with /model.</Text>
148
- <Box marginTop={1}>
149
- <ModelPicker
150
- config={synthetic}
151
- providers={ready}
152
- onSelect={onDone}
153
- onCancel={() => onDone(fallback)}
154
- />
155
- </Box>
156
- </Box>
157
- );
158
- }
159
-
160
- export function Onboarding({
161
- initialSelected = [],
162
- onComplete,
163
- }: {
164
- initialSelected?: ProviderName[];
165
- onComplete: (result: OnboardingResult) => void;
166
- }) {
167
- const [chosen, setChosen] = useState<ProviderMeta[] | null>(null);
168
- const [creds, setCreds] = useState<Partial<Record<ProviderName, ProviderConfig>> | null>(null);
169
-
170
- function onKeysDone(entered: Partial<Record<ProviderName, ProviderConfig>>) {
171
- const ready = readyProviders(entered);
172
- // No usable credentials (every key skipped): nothing to pick from — finish on the
173
- // chosen provider's catalog default rather than showing an empty picker.
174
- if (ready.length === 0) {
175
- onComplete({ providers: entered, defaultModel: chosen![0].defaultModel });
176
- return;
177
- }
178
- setCreds(entered);
179
- }
180
-
181
- return (
182
- <Box flexDirection="column" paddingX={1} paddingTop={1}>
183
- <Text bold color={theme.accent}>
184
- {WELCOME} Welcome to Privateer — let's set up your providers
185
- </Text>
186
- <Text color={theme.dim}>Bring your own keys. They're saved to ~/.privateer/config.json.</Text>
187
- <Box marginTop={1}>
188
- {chosen === null ? (
189
- <SelectStep initial={new Set(initialSelected)} onConfirm={setChosen} />
190
- ) : creds === null ? (
191
- <KeyStep providers={chosen} onDone={onKeysDone} />
192
- ) : (
193
- <ModelStep
194
- creds={creds}
195
- ready={readyProviders(creds)}
196
- fallback={PROVIDER_META[readyProviders(creds)[0]].defaultModel}
197
- onDone={(spec) => onComplete({ providers: creds, defaultModel: spec })}
198
- />
199
- )}
200
- </Box>
201
- </Box>
202
- );
203
- }
@@ -1,37 +0,0 @@
1
- import React from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import { theme } from "./theme.ts";
4
-
5
- // Shown after a turn completes while in plan mode: the agent has presented a plan,
6
- // and the user chooses to approve (leave plan mode), keep planning, or chat about
7
- // the plan (return to the prompt, staying in plan mode, to ask questions).
8
- export function PlanConfirm({
9
- onApprove,
10
- onKeep,
11
- onChat,
12
- }: {
13
- onApprove: () => void;
14
- onKeep: () => void;
15
- onChat: () => void;
16
- }) {
17
- useInput((input, key) => {
18
- if (input === "a" || key.return) onApprove();
19
- else if (input === "c") onChat();
20
- else if (input === "k" || key.escape) onKeep();
21
- });
22
- return (
23
- <Box flexDirection="column" borderStyle="round" borderColor={theme.accent} paddingX={1}>
24
- <Text color={theme.accent}>Plan ready — review it above.</Text>
25
- <Text>
26
- <Text color={theme.accent}>a</Text>
27
- <Text color={theme.dim}> approve & exit plan mode</Text>
28
- <Text color={theme.dim}>{" "}</Text>
29
- <Text color={theme.accent}>c</Text>
30
- <Text color={theme.dim}> chat about the plan</Text>
31
- <Text color={theme.dim}>{" "}</Text>
32
- <Text color={theme.accent}>k</Text>
33
- <Text color={theme.dim}> keep planning</Text>
34
- </Text>
35
- </Box>
36
- );
37
- }
@@ -1,109 +0,0 @@
1
- import React, { useEffect, useRef, useState } from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import Spinner from "ink-spinner";
4
- import { theme } from "./theme.ts";
5
- import { WELCOME } from "./figures.ts";
6
- import { runDeviceLogin, type DeviceCode, type PrivateerUser } from "../auth/privateer.ts";
7
-
8
- // Device-authorization login screen. Shows the user_code to approve in the
9
- // Privateer app, polls in the background, and resolves once approved. Esc
10
- // cancels. Works the same for email and wallet accounts — all the signing
11
- // happens in the app, never here.
12
- export function PrivateerLogin({
13
- onComplete,
14
- onCancel,
15
- }: {
16
- onComplete: (user: PrivateerUser) => void;
17
- onCancel: () => void;
18
- }) {
19
- const [code, setCode] = useState<DeviceCode | null>(null);
20
- const [error, setError] = useState<string | null>(null);
21
- const [slow, setSlow] = useState(false);
22
- const abortRef = useRef<AbortController | null>(null);
23
-
24
- useEffect(() => {
25
- const ac = new AbortController();
26
- abortRef.current = ac;
27
- let cancelled = false;
28
-
29
- runDeviceLogin({
30
- onCode: (c) => !cancelled && setCode(c),
31
- onPoll: (state) => !cancelled && setSlow(state === "slow_down"),
32
- signal: ac.signal,
33
- })
34
- .then((user) => {
35
- if (!cancelled) onComplete(user);
36
- })
37
- .catch((err) => {
38
- if (cancelled) return;
39
- if (ac.signal.aborted) return; // user cancelled — handled by useInput
40
- setError(err instanceof Error ? err.message : String(err));
41
- });
42
-
43
- return () => {
44
- cancelled = true;
45
- ac.abort();
46
- };
47
- // eslint-disable-next-line react-hooks/exhaustive-deps
48
- }, []);
49
-
50
- useInput((_input, key) => {
51
- if (key.escape || (key.return && error)) {
52
- abortRef.current?.abort();
53
- onCancel();
54
- }
55
- });
56
-
57
- return (
58
- <Box flexDirection="column" paddingX={1} paddingTop={1}>
59
- <Text bold color={theme.accent}>
60
- {WELCOME} Sign in to your Privateer account
61
- </Text>
62
- <Text color={theme.dim}>
63
- Inference runs on your account and is billed to your subscription — no API key needed.
64
- </Text>
65
-
66
- {error ? (
67
- <Box flexDirection="column" marginTop={1}>
68
- <Text color={theme.error ?? "red"}>{error}</Text>
69
- <Text color={theme.dim}>Press Esc or Enter to go back.</Text>
70
- </Box>
71
- ) : !code ? (
72
- <Box marginTop={1}>
73
- <Text color={theme.accent}>
74
- <Spinner type="dots" />
75
- </Text>
76
- <Text color={theme.dim}> Starting secure login…</Text>
77
- </Box>
78
- ) : (
79
- <Box flexDirection="column" marginTop={1}>
80
- <Text color={theme.dim}>1. Open the Privateer app (or web) where you're signed in.</Text>
81
- <Text color={theme.dim}>
82
- 2. Go to <Text color={theme.accent}>Settings → Link a terminal</Text>
83
- {code.verification_uri ? (
84
- <Text color={theme.dim}>
85
- {" "}
86
- ({code.verification_uri})
87
- </Text>
88
- ) : null}
89
- </Text>
90
- <Text color={theme.dim}>3. Enter this code (or just open the link above):</Text>
91
- <Box marginY={1} borderStyle="round" borderColor={theme.accent} paddingX={2}>
92
- <Text bold color={theme.accent}>
93
- {code.user_code}
94
- </Text>
95
- </Box>
96
- {/* Deliberately NOT an animated spinner here: ink-spinner re-renders the
97
- whole frame ~12×/s, which wipes any text selection on the code above
98
- and makes it almost impossible to copy. A static line keeps the code
99
- selectable; this view only re-renders on a real state change. */}
100
- <Box>
101
- <Text color={theme.dim}>
102
- Waiting for approval…{slow ? " (slowing down)" : ""} · Esc to cancel
103
- </Text>
104
- </Box>
105
- </Box>
106
- )}
107
- </Box>
108
- );
109
- }