my-durable 0.0.0-stage → 0.0.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/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/ai_docs/durable_pi/announcement.md +860 -0
- package/ai_docs/durable_pi/changelog.md +51 -0
- package/ai_docs/durable_pi/chord-delta-findings.md +362 -0
- package/ai_docs/durable_pi/coding-agent-integration.md +70 -0
- package/ai_docs/durable_pi/pico-v5-chord-usage.md +391 -0
- package/ai_docs/durable_pi/pico-v5-handoff.md +921 -0
- package/ai_docs/durable_pi/readme.md +617 -0
- package/ai_docs/durable_pi/sources.md +22 -0
- package/ai_docs/durable_pi/spec.md +4696 -0
- package/ai_docs/durable_pi/vacation-planner.md +57 -0
- package/ai_docs/pi/chord.md +268 -0
- package/ai_docs/pi/cli-integration.md +110 -0
- package/ai_docs/pi/cli.md +325 -0
- package/ai_docs/pi/codemode.md +211 -0
- package/ai_docs/pi/compaction.md +467 -0
- package/ai_docs/pi/configuration.md +51 -0
- package/ai_docs/pi/containerization.md +187 -0
- package/ai_docs/pi/custom-provider.md +177 -0
- package/ai_docs/pi/environment-variables.md +102 -0
- package/ai_docs/pi/extensions.md +278 -0
- package/ai_docs/pi/how-pi-works.md +53 -0
- package/ai_docs/pi/index.md +43 -0
- package/ai_docs/pi/json.md +230 -0
- package/ai_docs/pi/keybindings.md +198 -0
- package/ai_docs/pi/llama-cpp.md +118 -0
- package/ai_docs/pi/mcp.md +274 -0
- package/ai_docs/pi/message-types.md +265 -0
- package/ai_docs/pi/models.md +209 -0
- package/ai_docs/pi/overview.md +166 -0
- package/ai_docs/pi/packages.md +133 -0
- package/ai_docs/pi/pi-agent-core.md +568 -0
- package/ai_docs/pi/pi-ai.md +1942 -0
- package/ai_docs/pi/prompt-templates.md +63 -0
- package/ai_docs/pi/providers.md +204 -0
- package/ai_docs/pi/quickstart.md +140 -0
- package/ai_docs/pi/readme.md +99 -0
- package/ai_docs/pi/rpc-commands.md +864 -0
- package/ai_docs/pi/rpc-extension-ui.md +204 -0
- package/ai_docs/pi/rpc.md +197 -0
- package/ai_docs/pi/sdk.md +151 -0
- package/ai_docs/pi/security.md +103 -0
- package/ai_docs/pi/session-format.md +297 -0
- package/ai_docs/pi/sessions.md +70 -0
- package/ai_docs/pi/settings.md +173 -0
- package/ai_docs/pi/shell-aliases.md +97 -0
- package/ai_docs/pi/skills.md +97 -0
- package/ai_docs/pi/slash-commands.md +64 -0
- package/ai_docs/pi/sources.md +57 -0
- package/ai_docs/pi/terminal-setup.md +223 -0
- package/ai_docs/pi/termux.md +121 -0
- package/ai_docs/pi/themes.md +140 -0
- package/ai_docs/pi/tmux.md +59 -0
- package/ai_docs/pi/toolkit.md +168 -0
- package/ai_docs/pi/tui.md +129 -0
- package/ai_docs/pi/usage.md +98 -0
- package/ai_docs/pi/virtual-models.md +118 -0
- package/ai_docs/pi/windows.md +71 -0
- package/apps/dpi-backend/.nvmrc +1 -0
- package/apps/dpi-backend/API.md +99 -0
- package/apps/dpi-backend/README.md +119 -0
- package/apps/dpi-backend/deploy/backend.env.example +9 -0
- package/apps/dpi-backend/deploy/dpi-backend.plist +26 -0
- package/apps/dpi-backend/deploy/dpi-backend.service +22 -0
- package/apps/dpi-backend/package.json +33 -0
- package/apps/dpi-backend/prompts/one_sentence.md +3 -0
- package/apps/dpi-backend/prompts/smart_ass_opus_fix.md +169 -0
- package/apps/dpi-backend/scripts/check-contract.mjs +151 -0
- package/apps/dpi-backend/scripts/migrate-playground.ts +66 -0
- package/apps/dpi-backend/scripts/record-nested-fixture.mts +154 -0
- package/apps/dpi-backend/scripts/token.ts +57 -0
- package/apps/dpi-backend/src/addons.ts +139 -0
- package/apps/dpi-backend/src/auth.ts +20 -0
- package/apps/dpi-backend/src/config.ts +54 -0
- package/apps/dpi-backend/src/contract-check.ts +118 -0
- package/apps/dpi-backend/src/errors.ts +50 -0
- package/apps/dpi-backend/src/extensions.ts +510 -0
- package/apps/dpi-backend/src/faux.ts +80 -0
- package/apps/dpi-backend/src/history.ts +165 -0
- package/apps/dpi-backend/src/idempotency.ts +20 -0
- package/apps/dpi-backend/src/jev.ts +43 -0
- package/apps/dpi-backend/src/jobs.ts +790 -0
- package/apps/dpi-backend/src/main.ts +76 -0
- package/apps/dpi-backend/src/model-catalog.ts +180 -0
- package/apps/dpi-backend/src/pairing-files.ts +142 -0
- package/apps/dpi-backend/src/pairing.ts +459 -0
- package/apps/dpi-backend/src/routes.ts +217 -0
- package/apps/dpi-backend/src/shared-state.ts +85 -0
- package/apps/dpi-backend/src/store.ts +1240 -0
- package/apps/dpi-backend/src/streams.ts +256 -0
- package/apps/dpi-backend/src/tunnel.ts +955 -0
- package/apps/dpi-backend/tsconfig.json +13 -0
- package/apps/dpi-playground/README.md +83 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-CFGc7lHT.js +77 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DFdKpwm_.css +1 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/index.html +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/index.html +15 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/anthropic.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/deepseek.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/durable-pi.svg +14 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/gemini.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/grok.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/kimi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/lab.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/meta.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/minimax.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/openai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/qwen.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/xiaomi.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/public/icons/zai.png +0 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/App.vue +180 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/commands.ts +119 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/composables.ts +129 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/hub.ts +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/icon-path.ts +7 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/ids.ts +2 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/jobs.ts +80 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/link-policy.ts +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown-policy.ts +115 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/markdown.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-close.ts +23 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair-handoff.ts +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/pair.ts +166 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/qrcode.ts +323 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/signature.ts +146 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/store.ts +373 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/client/stream.ts +183 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/AgentView.vue +342 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Composer.vue +198 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ContextBar.vue +254 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceCredentialsDialog.vue +40 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DeviceIcon.vue +56 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/DirBrowser.vue +95 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ExtensionsPanel.vue +96 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/HelpDialog.vue +69 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Inspector.vue +425 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/JobCard.vue +221 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/MobileNav.vue +39 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/ModelPicker.vue +71 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NetworkSidebar.vue +236 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/NewSessionDialog.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/OfflinePanel.vue +128 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PairDeviceWizard.vue +433 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/PromptDialog.vue +89 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/StateEditor.vue +179 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TasksPanel.vue +280 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/Toast.vue +11 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TokenManager.vue +175 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TopBar.vue +114 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TranscriptItem.vue +152 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TreeRow.vue +195 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/TunnelStatusBar.vue +107 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/extensions.css +105 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/jobs.css +655 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/main.ts +22 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/network.css +675 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/style.css +3203 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/tsconfig.json +16 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/vite.config.ts +14 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/package.json +5 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/audit.ts +406 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/client-ip.ts +72 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/credentials.ts +265 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/guard.ts +43 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/http-errors.ts +156 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/identity.ts +60 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/limiter.ts +165 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/main.ts +460 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/monitor.ts +320 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/mux.ts +277 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/pairing.ts +816 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/proxy.ts +128 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/rotation.ts +583 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/seed.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/transport.ts +108 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-token.ts +11 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel-transport.ts +560 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/src/tunnel.ts +873 -0
- package/apps/dpi-playground/apps/dpi-playground-hub/tsconfig.json +13 -0
- package/apps/dpi-playground/package.json +37 -0
- package/apps/dpi-sdk/README.md +74 -0
- package/apps/dpi-sdk/package.json +20 -0
- package/apps/dpi-sdk/scripts/net.ts +73 -0
- package/apps/dpi-sdk/src/api.ts +631 -0
- package/apps/dpi-sdk/src/app-state.ts +111 -0
- package/apps/dpi-sdk/src/client.ts +330 -0
- package/apps/dpi-sdk/src/errors.ts +16 -0
- package/apps/dpi-sdk/src/jobs.ts +99 -0
- package/apps/dpi-sdk/src/models.ts +472 -0
- package/apps/dpi-sdk/src/node/chunk.ts +488 -0
- package/apps/dpi-sdk/src/node/config.ts +175 -0
- package/apps/dpi-sdk/src/node/drivers.ts +402 -0
- package/apps/dpi-sdk/src/node/ops.ts +114 -0
- package/apps/dpi-sdk/src/node/private-dir.ts +48 -0
- package/apps/dpi-sdk/src/node/registry-migration.ts +549 -0
- package/apps/dpi-sdk/src/node/registry.ts +390 -0
- package/apps/dpi-sdk/src/node/sse-writer.ts +151 -0
- package/apps/dpi-sdk/src/protocol/backoff.ts +31 -0
- package/apps/dpi-sdk/src/protocol/limits.ts +199 -0
- package/apps/dpi-sdk/src/protocol/pairing.ts +311 -0
- package/apps/dpi-sdk/src/protocol/rest-routes.ts +105 -0
- package/apps/dpi-sdk/src/protocol/tunnel.ts +331 -0
- package/apps/dpi-sdk/src/sse.ts +117 -0
- package/apps/dpi-sdk/src/transcript.ts +291 -0
- package/apps/dpi-sdk/tsconfig.json +13 -0
- package/apps/dpi-sdk/w +20 -0
- package/apps/dpi-tui/README.md +54 -0
- package/apps/dpi-tui/bin/dpi-tui.mjs +5 -0
- package/apps/dpi-tui/package.json +20 -0
- package/apps/dpi-tui/src/app.ts +433 -0
- package/apps/dpi-tui/src/commands.ts +151 -0
- package/apps/dpi-tui/src/main.ts +122 -0
- package/apps/dpi-tui/src/pairing.ts +287 -0
- package/apps/dpi-tui/src/pickers.ts +238 -0
- package/apps/dpi-tui/src/render.ts +164 -0
- package/apps/dpi-tui/src/theme.ts +57 -0
- package/apps/dpi-tui/tsconfig.json +14 -0
- package/bin/device.mjs +5 -0
- package/bin/hub.mjs +5 -0
- package/bin/tui.mjs +5 -0
- package/deploy/Caddyfile +120 -0
- package/docs/verification.md +102 -0
- package/dpi-network.example.yaml +62 -0
- package/images/01_durable_agent_network.svg +185 -0
- package/images/02_pi_made_durable.svg +204 -0
- package/images/03_restart_mid_tool.svg +168 -0
- package/images/07_one_api_every_client.svg +123 -0
- package/images/08_device_session_agent.svg +123 -0
- package/images/09_compact_keep_history.svg +143 -0
- package/images/10_shared_app_state.svg +177 -0
- package/images/11_durable_jobs_wait.svg +141 -0
- package/images/12_tools_change_mid_run.svg +231 -0
- package/images/13_hook_blocks_tool.svg +122 -0
- package/images/14_fork_at_any_entry.svg +119 -0
- package/images/15_web_client.png +0 -0
- package/images/16_terminal_client.svg +43 -0
- package/images/17_devices_are_data.svg +182 -0
- package/images/18_should_every_agent_be_durable.svg +118 -0
- package/images/19_sqlite_on_every_device.svg +155 -0
- package/images/20_admission_and_effects.svg +134 -0
- package/justfile +33 -0
- package/package.json +41 -4
- package/scripts/verify.mjs +210 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Transcript items (from the SDK, same as the web client) → pi-tui components, cached by item id.
|
|
2
|
+
// Only items whose content changed are rebuilt; the live assistant reuses its Markdown blocks.
|
|
3
|
+
import {
|
|
4
|
+
Container,
|
|
5
|
+
Markdown,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
wrapTextWithAnsi,
|
|
8
|
+
type Component,
|
|
9
|
+
} from "@earendil-works/pi-tui";
|
|
10
|
+
import type { ToolCallView, TranscriptItem } from "../../dpi-sdk/src/transcript.ts";
|
|
11
|
+
import { c, markdownTheme } from "./theme.ts";
|
|
12
|
+
|
|
13
|
+
const SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
14
|
+
/** Advanced by the app while a tool runs; running tool rows read it at render time. */
|
|
15
|
+
export const spinner = { frame: 0 };
|
|
16
|
+
const TAIL = 6;
|
|
17
|
+
|
|
18
|
+
/** One styled line, cut to the width. */
|
|
19
|
+
export class Line implements Component {
|
|
20
|
+
constructor(public text: string) {}
|
|
21
|
+
render(width: number) {
|
|
22
|
+
return [truncateToWidth(this.text, width)];
|
|
23
|
+
}
|
|
24
|
+
invalidate() {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Wrapped text with a first-line prefix and a hanging indent. */
|
|
28
|
+
class Wrapped implements Component {
|
|
29
|
+
constructor(
|
|
30
|
+
private text: string,
|
|
31
|
+
private prefix: string,
|
|
32
|
+
private style: (s: string) => string,
|
|
33
|
+
private indent = " ",
|
|
34
|
+
) {}
|
|
35
|
+
render(width: number) {
|
|
36
|
+
const inner = Math.max(1, width - 2);
|
|
37
|
+
const lines = this.text.split("\n").flatMap((l) => (l ? wrapTextWithAnsi(l, inner) : [""]));
|
|
38
|
+
return lines.map((l, i) => truncateToWidth((i ? this.indent : this.prefix) + this.style(l), width));
|
|
39
|
+
}
|
|
40
|
+
invalidate() {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function argsPreview(call: ToolCallView) {
|
|
44
|
+
const args = call.args as Record<string, unknown> | undefined;
|
|
45
|
+
if (args && typeof args === "object") {
|
|
46
|
+
for (const key of ["command", "path", "file_path", "pattern", "task", "query"])
|
|
47
|
+
if (typeof args[key] === "string") return (args[key] as string).replace(/\s+/g, " ");
|
|
48
|
+
}
|
|
49
|
+
const json = JSON.stringify(call.args ?? {});
|
|
50
|
+
return json === "{}" ? "" : json;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class ToolRow implements Component {
|
|
54
|
+
constructor(
|
|
55
|
+
private call: ToolCallView,
|
|
56
|
+
private expand: boolean,
|
|
57
|
+
) {}
|
|
58
|
+
render(width: number) {
|
|
59
|
+
const s = this.call.status;
|
|
60
|
+
const icon =
|
|
61
|
+
s === "running" ? c.warn(SPIN[spinner.frame % SPIN.length]) : s === "done" ? c.ok("✓") : s === "error" ? c.err("✗") : c.muted("○");
|
|
62
|
+
const head = truncateToWidth(`${icon} ${c.bold(this.call.name)} ${c.muted(argsPreview(this.call))}`, width);
|
|
63
|
+
const body = (this.call.result ?? this.call.output ?? "").replace(/\s+$/, "");
|
|
64
|
+
if (!body) return [head];
|
|
65
|
+
const all = body.split("\n");
|
|
66
|
+
const shown = this.expand ? all : all.slice(-TAIL);
|
|
67
|
+
const gutter = c.muted(" │ ");
|
|
68
|
+
const lines = this.expand
|
|
69
|
+
? shown.flatMap((l) => (l ? wrapTextWithAnsi(l, Math.max(1, width - 4)) : [""]))
|
|
70
|
+
: shown;
|
|
71
|
+
const out = [head, ...lines.map((l) => truncateToWidth(gutter + (s === "error" ? c.err(l) : l), width))];
|
|
72
|
+
if (all.length > shown.length)
|
|
73
|
+
out.splice(1, 0, truncateToWidth(c.muted(` │ … ${all.length - shown.length} earlier lines (ctrl+o)`), width));
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
invalidate() {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class Stack implements Component {
|
|
80
|
+
constructor(public parts: Component[] = []) {}
|
|
81
|
+
render(width: number) {
|
|
82
|
+
return this.parts.flatMap((p) => p.render(width));
|
|
83
|
+
}
|
|
84
|
+
invalidate() {
|
|
85
|
+
for (const p of this.parts) p.invalidate();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Assistant message: Markdown text blocks (reused across deltas), thinking, tool rows, errors. */
|
|
90
|
+
class AssistantView extends Stack {
|
|
91
|
+
private markdown = new Map<number, Markdown>();
|
|
92
|
+
update(item: Extract<TranscriptItem, { kind: "assistant" }>, expand: boolean) {
|
|
93
|
+
const parts: Component[] = [new Line("")];
|
|
94
|
+
item.blocks.forEach((block, i) => {
|
|
95
|
+
if (block.type === "text") {
|
|
96
|
+
let md = this.markdown.get(i);
|
|
97
|
+
if (!md) {
|
|
98
|
+
md = new Markdown(block.text, 0, 0, markdownTheme);
|
|
99
|
+
this.markdown.set(i, md);
|
|
100
|
+
} else md.setText(block.text);
|
|
101
|
+
parts.push(md);
|
|
102
|
+
} else if (block.type === "thinking") {
|
|
103
|
+
parts.push(
|
|
104
|
+
expand
|
|
105
|
+
? new Wrapped(block.text, c.muted("∴ "), (s) => c.italic(c.muted(s)))
|
|
106
|
+
: new Line(c.italic(c.muted(`∴ ${block.text.replace(/\s+/g, " ")}`))),
|
|
107
|
+
);
|
|
108
|
+
} else parts.push(new ToolRow(block.call, expand));
|
|
109
|
+
});
|
|
110
|
+
if (item.streaming && !item.blocks.length) parts.push(new Line(c.muted("…")));
|
|
111
|
+
if (item.error) parts.push(new Line(c.err(`! ${item.error}`)));
|
|
112
|
+
this.parts = parts;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function itemComponent(item: TranscriptItem, expand: boolean): Component {
|
|
117
|
+
switch (item.kind) {
|
|
118
|
+
case "user":
|
|
119
|
+
return new Stack([new Line(""), new Wrapped(item.text, c.user("› "), c.user)]);
|
|
120
|
+
case "assistant": {
|
|
121
|
+
const view = new AssistantView();
|
|
122
|
+
view.update(item, expand);
|
|
123
|
+
return view;
|
|
124
|
+
}
|
|
125
|
+
case "tool-result":
|
|
126
|
+
return new ToolRow(
|
|
127
|
+
{ callId: String(item.id), name: item.name, args: undefined, status: item.isError ? "error" : "done", result: item.text },
|
|
128
|
+
expand,
|
|
129
|
+
);
|
|
130
|
+
case "tool":
|
|
131
|
+
return new ToolRow(item.call, expand);
|
|
132
|
+
default:
|
|
133
|
+
return new Line(c.muted(`── ${item.text} `));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class TranscriptView extends Container {
|
|
138
|
+
private cache = new Map<string, { sig: string; component: Component }>();
|
|
139
|
+
expand = false;
|
|
140
|
+
|
|
141
|
+
update(items: readonly TranscriptItem[]) {
|
|
142
|
+
const next = new Map<string, { sig: string; component: Component }>();
|
|
143
|
+
this.clear();
|
|
144
|
+
for (const item of items) {
|
|
145
|
+
const key = `${item.kind}:${item.id}`;
|
|
146
|
+
const sig = `${this.expand}|${JSON.stringify(item)}`;
|
|
147
|
+
let entry = this.cache.get(key);
|
|
148
|
+
if (!entry || entry.sig !== sig) {
|
|
149
|
+
if (entry && item.kind === "assistant" && entry.component instanceof AssistantView)
|
|
150
|
+
entry.component.update(item, this.expand);
|
|
151
|
+
else entry = { sig, component: itemComponent(item, this.expand) };
|
|
152
|
+
entry.sig = sig;
|
|
153
|
+
}
|
|
154
|
+
next.set(key, entry);
|
|
155
|
+
this.addChild(entry.component);
|
|
156
|
+
}
|
|
157
|
+
this.cache = next;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
reset() {
|
|
161
|
+
this.cache.clear();
|
|
162
|
+
this.clear();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Colors mirror the FE brand tokens exactly (apps/dpi-playground-fe/src/style.css :root):
|
|
2
|
+
// fg #f2eeee, accent #ff8997, secondary/muted #c9a87c, faint #8d7778, ok #6fcf97,
|
|
3
|
+
// warn #f2c94c, err #ff6b6b, line-soft #2a2021, primary #b9283c, primary-line #df5363.
|
|
4
|
+
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
6
|
+
const rgb = (hex: string) => {
|
|
7
|
+
const n = parseInt(hex.slice(1), 16);
|
|
8
|
+
return `${(n >> 16) & 255};${(n >> 8) & 255};${n & 255}`;
|
|
9
|
+
};
|
|
10
|
+
const fg = (hex: string) => (s: string) => `\x1b[38;2;${rgb(hex)}m${s}\x1b[39m`;
|
|
11
|
+
const wrap = (on: string, off: string) => (s: string) => `\x1b[${on}m${s}\x1b[${off}m`;
|
|
12
|
+
|
|
13
|
+
export const c = {
|
|
14
|
+
accent: fg("#ff8997"),
|
|
15
|
+
muted: fg("#8d7778"),
|
|
16
|
+
secondary: fg("#c9a87c"),
|
|
17
|
+
text: fg("#f2eeee"),
|
|
18
|
+
ok: fg("#6fcf97"),
|
|
19
|
+
warn: fg("#f2c94c"),
|
|
20
|
+
err: fg("#ff6b6b"),
|
|
21
|
+
user: fg("#8ec5ff"),
|
|
22
|
+
primary: fg("#b9283c"),
|
|
23
|
+
primaryLine: fg("#df5363"),
|
|
24
|
+
bold: wrap("1", "22"),
|
|
25
|
+
dim: wrap("2", "22"),
|
|
26
|
+
italic: wrap("3", "23"),
|
|
27
|
+
underline: wrap("4", "24"),
|
|
28
|
+
strike: wrap("9", "29"),
|
|
29
|
+
userBg: (s: string) => `\x1b[48;2;42;32;33m${s}\x1b[49m`,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const markdownTheme: MarkdownTheme = {
|
|
33
|
+
heading: (s) => c.bold(c.accent(s)),
|
|
34
|
+
link: c.user,
|
|
35
|
+
linkUrl: c.muted,
|
|
36
|
+
code: c.secondary,
|
|
37
|
+
codeBlock: c.text,
|
|
38
|
+
codeBlockBorder: c.muted,
|
|
39
|
+
quote: c.muted,
|
|
40
|
+
quoteBorder: c.muted,
|
|
41
|
+
hr: c.muted,
|
|
42
|
+
listBullet: c.accent,
|
|
43
|
+
bold: c.bold,
|
|
44
|
+
italic: c.italic,
|
|
45
|
+
strikethrough: c.strike,
|
|
46
|
+
underline: c.underline,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const selectTheme: SelectListTheme = {
|
|
50
|
+
selectedPrefix: c.accent,
|
|
51
|
+
selectedText: (s) => c.bold(c.accent(s)),
|
|
52
|
+
description: c.muted,
|
|
53
|
+
scrollInfo: c.muted,
|
|
54
|
+
noMatch: c.muted,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const editorTheme: EditorTheme = { borderColor: c.muted, selectList: selectTheme };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
|
14
|
+
}
|
package/bin/device.mjs
ADDED
package/bin/hub.mjs
ADDED
package/bin/tui.mjs
ADDED
package/deploy/Caddyfile
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Reference Caddy deployment — Durable Agent Network (P2-21, spec 05 §4).
|
|
2
|
+
#
|
|
3
|
+
# Placeholders (Caddy {$ENV} substitution, set by the operator environment):
|
|
4
|
+
# DPI_PUBLIC_HOST host:port Caddy listens on (the public origin)
|
|
5
|
+
# DPI_TLS_CERT full-chain PEM
|
|
6
|
+
# DPI_TLS_KEY private key (0600, managed outside the repo)
|
|
7
|
+
# DPI_HUB_ADDR loopback address of the hub process (e.g. 127.0.0.1:4781)
|
|
8
|
+
# DPI_ADMIN_HASH bcrypt hash for the single admin user (caddy hash-password;
|
|
9
|
+
# the PLAINTEXT never lives in this repo or in env logs)
|
|
10
|
+
#
|
|
11
|
+
# Auth matrix (05):
|
|
12
|
+
# exempt (exact path/method): GET /healthz · POST /api/pair/start ·
|
|
13
|
+
# POST /api/pair/verify · GET /api/tunnel (WS) — everything else requires
|
|
14
|
+
# HTTP Basic auth, and the Authorization header is STRIPPED before the hub
|
|
15
|
+
# so device tokens and admin credentials can never mix. Client-supplied
|
|
16
|
+
# XFF/XFP are overwritten from the real connection.
|
|
17
|
+
{
|
|
18
|
+
admin off
|
|
19
|
+
auto_https off
|
|
20
|
+
servers {
|
|
21
|
+
protocols h1 h2
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
https://{$DPI_PUBLIC_HOST} {
|
|
26
|
+
# TLS 1.2 is the declared floor (05 §4) rather than a Caddy default that a
|
|
27
|
+
# future build could change. Caddy adds a catch-all policy for handshakes whose
|
|
28
|
+
# SNI does not match; that one keeps Caddy's own default (also 1.2), and
|
|
29
|
+
# tests/caddy-tls.test.ts proves both paths refuse TLS 1.0/1.1.
|
|
30
|
+
tls {$DPI_TLS_CERT} {$DPI_TLS_KEY} {
|
|
31
|
+
protocols tls1.2 tls1.3
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
header {
|
|
35
|
+
Strict-Transport-Security "max-age=15552000; includeSubDomains"
|
|
36
|
+
X-Content-Type-Options "nosniff"
|
|
37
|
+
Referrer-Policy "no-referrer"
|
|
38
|
+
-Server
|
|
39
|
+
-X-Powered-By
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# CSP is deliberately NOT set here. The hub owns content security per
|
|
43
|
+
# response: the app shell is served with a restrictive document CSP
|
|
44
|
+
# (script-src/style-src 'self' + fonts.googleapis.com, connect-src 'self',
|
|
45
|
+
# frame-ancestors 'none', X-Frame-Options: DENY — hub main.ts) while
|
|
46
|
+
# proxied device answers are locked down harder still (default-src 'none';
|
|
47
|
+
# sandbox — hub proxy.ts). This block runs after proxying, so a CSP line
|
|
48
|
+
# here would overwrite both of those per-route policies with one blanket
|
|
49
|
+
# value: either it weakens device responses or it breaks the app shell.
|
|
50
|
+
# Do not "harden" this file by adding one; the harness also runs without
|
|
51
|
+
# Caddy, so the hub must be correct on its own either way.
|
|
52
|
+
|
|
53
|
+
# Admin surface = everything except the four exact exemptions below.
|
|
54
|
+
@admin {
|
|
55
|
+
not path /api/tunnel /api/pair/start /api/pair/verify /healthz
|
|
56
|
+
}
|
|
57
|
+
@tunnel path /api/tunnel
|
|
58
|
+
@pairstart {
|
|
59
|
+
method POST
|
|
60
|
+
path /api/pair/start
|
|
61
|
+
}
|
|
62
|
+
@pairverify {
|
|
63
|
+
method POST
|
|
64
|
+
path /api/pair/verify
|
|
65
|
+
}
|
|
66
|
+
@healthz {
|
|
67
|
+
method GET
|
|
68
|
+
path /healthz
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# 1. Tunnel WebSocket: exempt from basic auth, Bearer preserved.
|
|
72
|
+
handle @tunnel {
|
|
73
|
+
reverse_proxy {$DPI_HUB_ADDR} {
|
|
74
|
+
header_up X-Forwarded-For {remote_host}
|
|
75
|
+
header_up X-Forwarded-Proto https
|
|
76
|
+
flush_interval -1
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# 2-4. Exempt pairing entry points — strip any caller Authorization so
|
|
81
|
+
# credentials never mix (04 §9).
|
|
82
|
+
handle @pairstart {
|
|
83
|
+
reverse_proxy {$DPI_HUB_ADDR} {
|
|
84
|
+
header_up X-Forwarded-For {remote_host}
|
|
85
|
+
header_up X-Forwarded-Proto https
|
|
86
|
+
header_up -Authorization
|
|
87
|
+
flush_interval -1
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
handle @pairverify {
|
|
92
|
+
reverse_proxy {$DPI_HUB_ADDR} {
|
|
93
|
+
header_up X-Forwarded-For {remote_host}
|
|
94
|
+
header_up X-Forwarded-Proto https
|
|
95
|
+
header_up -Authorization
|
|
96
|
+
flush_interval -1
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
handle @healthz {
|
|
101
|
+
reverse_proxy {$DPI_HUB_ADDR} {
|
|
102
|
+
header_up X-Forwarded-For {remote_host}
|
|
103
|
+
header_up X-Forwarded-Proto https
|
|
104
|
+
header_up -Authorization
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Everything else: basic auth, then STRIP Authorization before the hub.
|
|
109
|
+
handle @admin {
|
|
110
|
+
basic_auth {
|
|
111
|
+
admin {$DPI_ADMIN_HASH}
|
|
112
|
+
}
|
|
113
|
+
reverse_proxy {$DPI_HUB_ADDR} {
|
|
114
|
+
header_up X-Forwarded-For {remote_host}
|
|
115
|
+
header_up X-Forwarded-Proto https
|
|
116
|
+
header_up -Authorization
|
|
117
|
+
flush_interval -1
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Local verification and release gates
|
|
2
|
+
|
|
3
|
+
Run from the repository root after installing the four apps:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
just test verify # full local matrix
|
|
7
|
+
just test verify P2 # core checks without lint/browser/PTY
|
|
8
|
+
node scripts/verify.mjs --phase all
|
|
9
|
+
node --test scripts/verify.test.mjs
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Phase names are case-insensitive. A missing value after `--phase`, an unknown
|
|
13
|
+
phase, a failed check or a check that cannot run produces a nonzero exit.
|
|
14
|
+
Every command runs with an explicit working directory.
|
|
15
|
+
|
|
16
|
+
## What each command covers
|
|
17
|
+
|
|
18
|
+
All phases run the verification CLI regressions and five build/type/contract
|
|
19
|
+
checks. The CLI tests execute the real runner in an isolated filesystem and
|
|
20
|
+
replace only its external commands.
|
|
21
|
+
|
|
22
|
+
| Selector | Additional checks |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `P0` | SDK tests (a focused selector, not a G0 decision) |
|
|
25
|
+
| `P1` | SDK and playground tests (not a G1 decision) |
|
|
26
|
+
| `P2` (default) | SDK, backend, playground and TUI tests |
|
|
27
|
+
| `all` | P2 plus the R-1/M4 SIGKILL recovery timing, both lint groups, the network/mobile browser, pairing/offline-credential browser and real PTY pairing/resize tests |
|
|
28
|
+
|
|
29
|
+
The Web build runs before browser tests. Lint/browser/PTY checks are **NOT_RUN**
|
|
30
|
+
in `P2`, not implicitly passed. `all` requires the installed playground lint
|
|
31
|
+
toolchain, system Chrome (`channel: "chrome"` in Playwright) and `tuistory`.
|
|
32
|
+
Core integration tests also need `tools/caddy`. Test providers are faux and
|
|
33
|
+
servers bind loopback; no paid-model or deployment commands run.
|
|
34
|
+
|
|
35
|
+
## Recovery timing (R-1/M4)
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
just test timing # or: cd apps/dpi-backend && npm run test:timing
|
|
39
|
+
DPI_M4_REPORT=/path/m4.json npm run test:timing # also keep the JSON report
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This suite SIGKILLs a real device server mid-run and measures how long it takes
|
|
43
|
+
to be back at verified durable progress. Every sample is asserted **strictly
|
|
44
|
+
under 2 s**, measured from *before* the kill (the interval includes a fixed
|
|
45
|
+
kill-position wait, so it is an upper bound, not a stopwatch on the restart
|
|
46
|
+
alone). The runner prints every sample, including the ones before a failure.
|
|
47
|
+
|
|
48
|
+
It is a **this-machine** measurement and it is load-sensitive: run it on a
|
|
49
|
+
quiet machine, never in parallel with the rest of the matrix. On the reference
|
|
50
|
+
laptop (i5-6300HQ ×4, Node 22) quiet samples land around 1.0–1.4 s; background
|
|
51
|
+
load pushes individual samples up, so a failure under load is rerun on an idle
|
|
52
|
+
machine before it counts. A green run here is **not** the P2-29 M4 acceptance:
|
|
53
|
+
that target is defined over NAT with the current daemon and is measured
|
|
54
|
+
separately.
|
|
55
|
+
|
|
56
|
+
## Regression suites for the Gate A remediation
|
|
57
|
+
|
|
58
|
+
All run inside the phase suites above; each is also runnable on its own with
|
|
59
|
+
`npx tsx --test tests/<file>` from the owning app.
|
|
60
|
+
|
|
61
|
+
- `apps/dpi-backend`: `connector-backoff`, `connector-hardening`,
|
|
62
|
+
`connector-limits`, `connector-tls`, `pairing-lifecycle`, `private-dir`,
|
|
63
|
+
`rest-allowlist`, `token-script`, `timing/m4-resume.timing.ts`.
|
|
64
|
+
- `apps/dpi-playground`: `client-ip`, `http-errors`, `pairing-limits`,
|
|
65
|
+
`audit`, `guards-audit`, `hub-errors`, `tunnel-gate-a`, `pairing-gate-a`,
|
|
66
|
+
`device-lifecycle` (R-2 tunnel reconnect, T-2 bounded backoff), the
|
|
67
|
+
`fe-*` client suites, and the GA-* cases appended to `rotation`,
|
|
68
|
+
`rotation-ack`, `revoke` and `tls-policy`.
|
|
69
|
+
- `apps/dpi-sdk`: `registry-migration` with the `fixtures/registry-v0`
|
|
70
|
+
builder, `registry-writers` (writer exclusion), `backoff`, `private-dir`,
|
|
71
|
+
`sse-limits`.
|
|
72
|
+
- `apps/dpi-tui`: `pairing-ui.mjs` covers the offline-credential scan (S-7)
|
|
73
|
+
in a real PTY.
|
|
74
|
+
|
|
75
|
+
The runner prints command/status/count/duration summaries and bounded failure
|
|
76
|
+
output. It does **not** automatically save a report or source manifest.
|
|
77
|
+
Capture output to a new local evidence file and keep the command's exit code;
|
|
78
|
+
do not pipe it through a command that hides failure. Evidence needs the Git HEAD
|
|
79
|
+
and hashes of uncommitted source, not just a commit name.
|
|
80
|
+
|
|
81
|
+
## What a green local run does not establish
|
|
82
|
+
|
|
83
|
+
- **P2-29:** real NAT pairing and current-daemon M1–M4 targets are
|
|
84
|
+
**NOT_MEASURED**. M3 includes human fingerprint approval; M4 starts before
|
|
85
|
+
SIGKILL and ends at verified durable resumed progress.
|
|
86
|
+
- **D-4 (P3):** the disaster restore/quarantine/re-pair drill is
|
|
87
|
+
**NOT_MEASURED**. D-4 is not the NAT test.
|
|
88
|
+
- **Gate A / Gate B:** **NOT_EVALUATED**. A green runner is neither an independent
|
|
89
|
+
security verdict nor owner authorization to expose or deploy anything.
|
|
90
|
+
- Soak/capacity, real-model/provider acceptance and untested platforms remain
|
|
91
|
+
separate work. Historical records do not become current acceptance results.
|
|
92
|
+
|
|
93
|
+
## Memory units
|
|
94
|
+
|
|
95
|
+
The M1 contract is **150 MB = 150,000,000 bytes**, not 150 MiB.
|
|
96
|
+
Convert Linux `VmHWM` KiB to bytes by multiplying by 1024, then compare bytes
|
|
97
|
+
before rounding. Report both decimal MB and binary MiB.
|
|
98
|
+
|
|
99
|
+
The original P0 samples remain historical inputs. Their `targetMiB: 150` field
|
|
100
|
+
was incorrect contract metadata and must not determine the verdict. The first
|
|
101
|
+
sample is 153,256 KiB = 156,934,144 bytes, so that baseline misses M1 despite
|
|
102
|
+
rounding to 149.7 MiB. This correction is not a fresh P2 performance measurement.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# dpi-network.example.yaml → copy to dpi-network.yaml (gitignored) and add your own machines.
|
|
2
|
+
#
|
|
3
|
+
# cp dpi-network.example.yaml dpi-network.yaml
|
|
4
|
+
#
|
|
5
|
+
# Every device runs apps/dpi-backend. Clients (the web hub, dpi-tui, curl) connect with a URL and a
|
|
6
|
+
# bearer token. No code or recipe names a machine: `just net <verb> <id>` looks the id up here and
|
|
7
|
+
# runs the driver for its `kind`.
|
|
8
|
+
#
|
|
9
|
+
# just net up <id> provision → deploy → env → service → (expose) → register, idempotent
|
|
10
|
+
# just net status probe every device
|
|
11
|
+
# just net logs <id> | just net restart <id> | just net down <id> | just net deploy <id>
|
|
12
|
+
# just net destroy <id> exe-dev only; refuses any VM not named in this file
|
|
13
|
+
#
|
|
14
|
+
# Kinds
|
|
15
|
+
# local this machine; `just backend run` runs it in a terminal, or set `service: launchd|systemd`
|
|
16
|
+
# ssh any host reachable with `ssh <ssh>`; nvm + rsync + launchd (macOS) or systemd (Linux)
|
|
17
|
+
# exe-dev an exe.dev VM: the ssh driver at <vm>.exe.xyz plus VM create and HTTPS proxy exposure
|
|
18
|
+
# url connect only (someone else runs the backend); `just net register <id> --token <t>`
|
|
19
|
+
#
|
|
20
|
+
# Tokens never live here. Each device generates its own in <home_dir>/backend.env, and
|
|
21
|
+
# `just net register <id>` copies it into ~/.dpi/network.sqlite on this machine.
|
|
22
|
+
|
|
23
|
+
# Each device's <home_dir>/backend.env is written from this file and nothing else:
|
|
24
|
+
# DPI_TOKEN generated on the first `just net up`, kept after (new one: just net rotate <id>)
|
|
25
|
+
# DPI_DEVICE_NAME the device id
|
|
26
|
+
# DPI_PORT port
|
|
27
|
+
# DPI_HOST host (default 127.0.0.1 for kind local, 0.0.0.0 otherwise)
|
|
28
|
+
# DPI_START_DIRS start_dirs, colon-separated
|
|
29
|
+
# <NAME>=<value> for each name in env_keys, with its value read from this checkout's .env
|
|
30
|
+
# <NAME>=<value> for each entry in env (defaults.env merged with the device's env)
|
|
31
|
+
|
|
32
|
+
defaults:
|
|
33
|
+
port: 4780 # dpi-backend port on every device
|
|
34
|
+
node: 24 # Node major installed with nvm on ssh / exe-dev devices
|
|
35
|
+
code_dir: ~/dpi # where apps/dpi-backend + apps/dpi-sdk are synced (ssh / exe-dev)
|
|
36
|
+
home_dir: ~/.dpi # backend.env, data and logs; deploys never touch it
|
|
37
|
+
start_dirs: ["~/dpi-work"] # where new sessions start (created if missing); any existing folder works as a cwd
|
|
38
|
+
env_keys: [OPENROUTER_API_KEY] # secrets: copied by name from .env, never written here
|
|
39
|
+
# env: # plain settings, written as-is
|
|
40
|
+
# DPI_PROVIDER: faux # the free deterministic model, for tests
|
|
41
|
+
|
|
42
|
+
devices:
|
|
43
|
+
local:
|
|
44
|
+
name: Laptop
|
|
45
|
+
kind: local
|
|
46
|
+
|
|
47
|
+
# A Mac or Linux box you can reach with passwordless ssh.
|
|
48
|
+
# mac-mini:
|
|
49
|
+
# name: Mac mini
|
|
50
|
+
# kind: ssh
|
|
51
|
+
# ssh: me@my-mac-mini.local
|
|
52
|
+
# service: launchd # systemd on Linux (needs passwordless sudo)
|
|
53
|
+
# url: http://my-mac-mini.local:4780
|
|
54
|
+
# start_dirs: ["~/dpi-work"]
|
|
55
|
+
|
|
56
|
+
# An exe.dev VM (https://exe.dev). `just net up` creates it if missing and exposes it over HTTPS.
|
|
57
|
+
# vm-1:
|
|
58
|
+
# name: exe.dev VM
|
|
59
|
+
# kind: exe-dev
|
|
60
|
+
# vm: my-dpi-vm-1 # VM name, also the public host: https://my-dpi-vm-1.exe.xyz
|
|
61
|
+
# create: { cpu: 2, memory: 4GB, disk: 20GB }
|
|
62
|
+
# start_dirs: ["~/work"]
|