@noopolis/daimon 0.1.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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/examples/pi-agent.d.ts +2 -0
- package/dist/examples/pi-agent.d.ts.map +1 -0
- package/dist/examples/pi-agent.js +108 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/pi/auth.d.ts +20 -0
- package/dist/pi/auth.d.ts.map +1 -0
- package/dist/pi/auth.js +98 -0
- package/dist/pi/modelConfig.d.ts +37 -0
- package/dist/pi/modelConfig.d.ts.map +1 -0
- package/dist/pi/modelConfig.js +94 -0
- package/dist/pi/piHarness.d.ts +19 -0
- package/dist/pi/piHarness.d.ts.map +1 -0
- package/dist/pi/piHarness.js +153 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Noopolis
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Daimon
|
|
2
|
+
|
|
3
|
+
Daimon is the Noopolis-native per-agent runtime harness.
|
|
4
|
+
|
|
5
|
+
It defines a small per-agent contract and currently implements that contract on
|
|
6
|
+
top of Pi. A Daimon runs one harnessed agent inside a caller-prepared workspace.
|
|
7
|
+
|
|
8
|
+
Spawnfile should own orgs, nested teams, schedules, Moltnet wiring, workspace
|
|
9
|
+
resource compilation, and the app that starts many harnessed agents. This package
|
|
10
|
+
should not know what an org is.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @noopolis/daimon
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Tests
|
|
19
|
+
|
|
20
|
+
The package has a non-live test suite for auth seeding and Pi model config
|
|
21
|
+
generation:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm test
|
|
25
|
+
npm run typecheck
|
|
26
|
+
npm run build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
These tests do not call a model provider.
|
|
30
|
+
|
|
31
|
+
## Model And Auth Helpers
|
|
32
|
+
|
|
33
|
+
The Pi adapter supports the same model intent shape Spawnfile lowers for Pi:
|
|
34
|
+
|
|
35
|
+
- OpenAI Codex subscription auth maps to Pi's `openai-codex` OAuth auth store.
|
|
36
|
+
- Claude Code subscription auth maps to Pi's Anthropic auth store.
|
|
37
|
+
- API-key credentials can be written directly into Pi auth storage.
|
|
38
|
+
- Local and custom OpenAI-compatible endpoints render Pi `models.json`.
|
|
39
|
+
|
|
40
|
+
For Ollama-style local models, use a local endpoint with `auth.method: none`.
|
|
41
|
+
Pi still requires an API-key field for custom providers, so the helper writes the
|
|
42
|
+
upstream-documented dummy `ollama` value.
|
|
43
|
+
|
|
44
|
+
## Pi E2E
|
|
45
|
+
|
|
46
|
+
The Pi E2E uses the local Codex CLI subscription auth file to seed an ignored Pi
|
|
47
|
+
`auth.json` under `.runtime/`.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install
|
|
51
|
+
npm run e2e:pi-agent
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The example starts two harnessed Pi agents from plain caller code. The example
|
|
55
|
+
creates the workspaces and shared resource itself to demonstrate the intended
|
|
56
|
+
boundary: the caller prepares files, the harness runs agent turns.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type AgentId = string;
|
|
2
|
+
export interface AgentStartInput {
|
|
3
|
+
id: AgentId;
|
|
4
|
+
name: string;
|
|
5
|
+
instructions: string;
|
|
6
|
+
workspacePath: string;
|
|
7
|
+
runtimeHomePath: string;
|
|
8
|
+
tools?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface HarnessModelEndpoint {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
compatibility: "anthropic" | "openai";
|
|
13
|
+
}
|
|
14
|
+
export interface HarnessModelSpec {
|
|
15
|
+
auth?: {
|
|
16
|
+
keyEnv?: string;
|
|
17
|
+
method: "api_key" | "claude-code" | "codex" | "none";
|
|
18
|
+
};
|
|
19
|
+
endpoint?: HarnessModelEndpoint;
|
|
20
|
+
name: string;
|
|
21
|
+
provider: string;
|
|
22
|
+
}
|
|
23
|
+
export interface WakeEvent {
|
|
24
|
+
id: string;
|
|
25
|
+
kind: "manual" | "message" | "schedule";
|
|
26
|
+
from?: string;
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WakeResult {
|
|
30
|
+
agentId: AgentId;
|
|
31
|
+
text: string;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
}
|
|
34
|
+
export interface AgentStatus {
|
|
35
|
+
agentId: AgentId;
|
|
36
|
+
state: "starting" | "idle" | "running" | "stopped" | "failed";
|
|
37
|
+
lastWakeAt?: string;
|
|
38
|
+
lastError?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentHandle {
|
|
41
|
+
id: AgentId;
|
|
42
|
+
wake(event: WakeEvent): Promise<WakeResult>;
|
|
43
|
+
status(): AgentStatus;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
export interface AgentHarnessAdapter {
|
|
47
|
+
startAgent(input: AgentStartInput): Promise<AgentHandle>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,WAAW,GAAG,QAAQ,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;KACtD,CAAC;IACF,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,IAAI,WAAW,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-agent.d.ts","sourceRoot":"","sources":["../../src/examples/pi-agent.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { access, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { seedPiOpenAICodexAuthFromCodex } from "../pi/auth.js";
|
|
5
|
+
import { PiHarnessAdapter } from "../pi/piHarness.js";
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const daimonRoot = path.resolve(__dirname, "../..");
|
|
8
|
+
const runtimeRoot = path.join(daimonRoot, ".runtime", "pi-agent");
|
|
9
|
+
const sharedProjectPath = path.join(runtimeRoot, "shared", "product");
|
|
10
|
+
const piAuthPath = path.join(runtimeRoot, "auth", "auth.json");
|
|
11
|
+
const codexAuthPath = path.join(process.env.HOME ?? "", ".codex", "auth.json");
|
|
12
|
+
const ensureFile = async (filePath) => {
|
|
13
|
+
await access(filePath);
|
|
14
|
+
return readFile(filePath, "utf8");
|
|
15
|
+
};
|
|
16
|
+
const assertContains = (content, expected, filePath) => {
|
|
17
|
+
if (!content.includes(expected)) {
|
|
18
|
+
throw new Error(`${filePath} does not contain expected text: ${expected}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const prepareWorkspace = async (agentId) => {
|
|
22
|
+
const workspacePath = path.join(runtimeRoot, "agents", agentId, "workspace");
|
|
23
|
+
const runtimeHomePath = path.join(runtimeRoot, "agents", agentId, "runtime");
|
|
24
|
+
await mkdir(path.join(workspacePath, "repos"), { recursive: true });
|
|
25
|
+
await mkdir(runtimeHomePath, { recursive: true });
|
|
26
|
+
await writeFile(path.join(workspacePath, "AGENTS.md"), `# ${agentId}\n\nThis workspace was prepared by caller code, not by the Daimon package.\n`);
|
|
27
|
+
await symlink(sharedProjectPath, path.join(workspacePath, "repos", "product"));
|
|
28
|
+
return { workspacePath, runtimeHomePath };
|
|
29
|
+
};
|
|
30
|
+
const setupRuntime = async () => {
|
|
31
|
+
await rm(runtimeRoot, { recursive: true, force: true });
|
|
32
|
+
await mkdir(sharedProjectPath, { recursive: true });
|
|
33
|
+
await writeFile(path.join(sharedProjectPath, "source.txt"), [
|
|
34
|
+
"Daimon E2E source file.",
|
|
35
|
+
"The caller prepares shared files and Daimon runs agent turns.",
|
|
36
|
+
"Expected marker: daimon-e2e"
|
|
37
|
+
].join("\n"));
|
|
38
|
+
await seedPiOpenAICodexAuthFromCodex({ codexAuthPath, piAuthPath });
|
|
39
|
+
};
|
|
40
|
+
const startHarnessedAgent = async (adapter, input) => {
|
|
41
|
+
const paths = await prepareWorkspace(input.id);
|
|
42
|
+
return adapter.startAgent({
|
|
43
|
+
...input,
|
|
44
|
+
...paths
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const run = async () => {
|
|
48
|
+
await setupRuntime();
|
|
49
|
+
const adapter = new PiHarnessAdapter({
|
|
50
|
+
authPath: piAuthPath,
|
|
51
|
+
model: {
|
|
52
|
+
provider: "openai-codex",
|
|
53
|
+
name: process.env.HARNESS_PI_MODEL ?? "gpt-5.4-mini"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const mapper = await startHarnessedAgent(adapter, {
|
|
57
|
+
id: "mapper",
|
|
58
|
+
name: "Mapper",
|
|
59
|
+
instructions: "You map project inputs into concise markdown notes. Prefer editing files over long chat responses."
|
|
60
|
+
});
|
|
61
|
+
const reviewer = await startHarnessedAgent(adapter, {
|
|
62
|
+
id: "reviewer",
|
|
63
|
+
name: "Reviewer",
|
|
64
|
+
instructions: "You review files written by another harnessed agent and produce a final check note."
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
console.log("started", JSON.stringify([mapper.status(), reviewer.status()], null, 2));
|
|
68
|
+
const mapped = await mapper.wake({
|
|
69
|
+
id: "wake-mapper-1",
|
|
70
|
+
kind: "manual",
|
|
71
|
+
from: "caller",
|
|
72
|
+
text: [
|
|
73
|
+
"Read repos/product/source.txt.",
|
|
74
|
+
"Create repos/product/agent-output/mapper.md.",
|
|
75
|
+
"The file must contain the exact marker MAPPER_OK and the phrase daimon-e2e.",
|
|
76
|
+
"Reply with one short sentence naming the file."
|
|
77
|
+
].join("\n")
|
|
78
|
+
});
|
|
79
|
+
console.log("mapper", JSON.stringify(mapped, null, 2));
|
|
80
|
+
const reviewed = await reviewer.wake({
|
|
81
|
+
id: "wake-reviewer-1",
|
|
82
|
+
kind: "message",
|
|
83
|
+
from: "mapper",
|
|
84
|
+
text: [
|
|
85
|
+
"Read repos/product/agent-output/mapper.md.",
|
|
86
|
+
"Create repos/product/agent-output/review.md.",
|
|
87
|
+
"The file must contain the exact marker REVIEW_OK and mention MAPPER_OK.",
|
|
88
|
+
"Reply with one short sentence naming the file."
|
|
89
|
+
].join("\n")
|
|
90
|
+
});
|
|
91
|
+
console.log("reviewer", JSON.stringify(reviewed, null, 2));
|
|
92
|
+
const mapperFile = path.join(sharedProjectPath, "agent-output", "mapper.md");
|
|
93
|
+
const reviewFile = path.join(sharedProjectPath, "agent-output", "review.md");
|
|
94
|
+
assertContains(await ensureFile(mapperFile), "MAPPER_OK", mapperFile);
|
|
95
|
+
assertContains(await ensureFile(mapperFile), "daimon-e2e", mapperFile);
|
|
96
|
+
assertContains(await ensureFile(reviewFile), "REVIEW_OK", reviewFile);
|
|
97
|
+
assertContains(await ensureFile(reviewFile), "MAPPER_OK", reviewFile);
|
|
98
|
+
console.log("final-status", JSON.stringify([mapper.status(), reviewer.status()], null, 2));
|
|
99
|
+
console.log("e2e:pi-agent ok");
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await Promise.all([mapper.stop(), reviewer.stop()]);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
run().catch((error) => {
|
|
106
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function seedPiOpenAICodexAuthFromCodex(input: {
|
|
2
|
+
codexAuthPath: string;
|
|
3
|
+
piAuthPath: string;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function seedPiAnthropicAuthFromClaudeCode(input: {
|
|
6
|
+
claudeCredentialsPath: string;
|
|
7
|
+
piAuthPath: string;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function seedPiApiKeyAuth(input: {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
piAuthPath: string;
|
|
13
|
+
provider: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function createPiOpenAICodexAuthFromCodexToken(input: {
|
|
16
|
+
access: string;
|
|
17
|
+
accountId?: string;
|
|
18
|
+
refresh: string;
|
|
19
|
+
}): Record<string, unknown>;
|
|
20
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/pi/auth.ts"],"names":[],"mappings":"AA2DA,wBAAsB,8BAA8B,CAAC,KAAK,EAAE;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB;AAED,wBAAsB,iCAAiC,CAAC,KAAK,EAAE;IAC7D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAgB,qCAAqC,CAAC,KAAK,EAAE;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU1B"}
|
package/dist/pi/auth.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const decodeJwtPayload = (token) => {
|
|
4
|
+
const payload = token.split(".")[1];
|
|
5
|
+
if (!payload) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const resolveExpires = (accessToken) => {
|
|
16
|
+
const payload = decodeJwtPayload(accessToken);
|
|
17
|
+
const exp = payload?.exp;
|
|
18
|
+
return typeof exp === "number" ? exp * 1000 : Date.now() + 30 * 60 * 1000;
|
|
19
|
+
};
|
|
20
|
+
const readJsonFile = async (filePath) => JSON.parse(await readFile(filePath, "utf8"));
|
|
21
|
+
const readPiAuth = async (piAuthPath) => {
|
|
22
|
+
try {
|
|
23
|
+
return await readJsonFile(piAuthPath);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const writePiAuth = async (piAuthPath, next) => {
|
|
33
|
+
await mkdir(path.dirname(piAuthPath), { recursive: true });
|
|
34
|
+
await writeFile(piAuthPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
35
|
+
};
|
|
36
|
+
export async function seedPiOpenAICodexAuthFromCodex(input) {
|
|
37
|
+
const codex = await readJsonFile(input.codexAuthPath);
|
|
38
|
+
const access = codex.tokens?.access_token;
|
|
39
|
+
const refresh = codex.tokens?.refresh_token;
|
|
40
|
+
const accountId = codex.tokens?.account_id;
|
|
41
|
+
if (!access || !refresh || !accountId) {
|
|
42
|
+
throw new Error(`Codex auth file is missing access_token, refresh_token, or account_id: ${input.codexAuthPath}`);
|
|
43
|
+
}
|
|
44
|
+
const piAuth = await readPiAuth(input.piAuthPath);
|
|
45
|
+
piAuth["openai-codex"] = {
|
|
46
|
+
type: "oauth",
|
|
47
|
+
access,
|
|
48
|
+
refresh,
|
|
49
|
+
expires: resolveExpires(access),
|
|
50
|
+
accountId
|
|
51
|
+
};
|
|
52
|
+
await writePiAuth(input.piAuthPath, piAuth);
|
|
53
|
+
}
|
|
54
|
+
export async function seedPiAnthropicAuthFromClaudeCode(input) {
|
|
55
|
+
const claude = await readJsonFile(input.claudeCredentialsPath);
|
|
56
|
+
const access = claude.claudeAiOauth?.accessToken;
|
|
57
|
+
const refresh = claude.claudeAiOauth?.refreshToken;
|
|
58
|
+
const expires = claude.claudeAiOauth?.expiresAt;
|
|
59
|
+
if (!access || typeof expires !== "number") {
|
|
60
|
+
throw new Error(`Claude Code credentials are missing accessToken or expiresAt: ${input.claudeCredentialsPath}`);
|
|
61
|
+
}
|
|
62
|
+
const piAuth = await readPiAuth(input.piAuthPath);
|
|
63
|
+
piAuth.anthropic = refresh
|
|
64
|
+
? {
|
|
65
|
+
type: "oauth",
|
|
66
|
+
access,
|
|
67
|
+
refresh,
|
|
68
|
+
expires
|
|
69
|
+
}
|
|
70
|
+
: {
|
|
71
|
+
type: "api_key",
|
|
72
|
+
key: access
|
|
73
|
+
};
|
|
74
|
+
await writePiAuth(input.piAuthPath, piAuth);
|
|
75
|
+
}
|
|
76
|
+
export async function seedPiApiKeyAuth(input) {
|
|
77
|
+
if (input.apiKey.length === 0) {
|
|
78
|
+
throw new Error(`API key for ${input.provider} must not be empty`);
|
|
79
|
+
}
|
|
80
|
+
const piAuth = await readPiAuth(input.piAuthPath);
|
|
81
|
+
piAuth[input.provider] = {
|
|
82
|
+
type: "api_key",
|
|
83
|
+
key: input.apiKey,
|
|
84
|
+
...(input.env ? { env: input.env } : {})
|
|
85
|
+
};
|
|
86
|
+
await writePiAuth(input.piAuthPath, piAuth);
|
|
87
|
+
}
|
|
88
|
+
export function createPiOpenAICodexAuthFromCodexToken(input) {
|
|
89
|
+
return {
|
|
90
|
+
"openai-codex": {
|
|
91
|
+
type: "oauth",
|
|
92
|
+
access: input.access,
|
|
93
|
+
refresh: input.refresh,
|
|
94
|
+
expires: resolveExpires(input.access),
|
|
95
|
+
...(input.accountId ? { accountId: input.accountId } : {})
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HarnessModelSpec } from "../core/types.js";
|
|
2
|
+
type PiApi = "anthropic-messages" | "openai-completions";
|
|
3
|
+
interface PiModelProviderConfig {
|
|
4
|
+
api: PiApi;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
models: Array<{
|
|
8
|
+
api: PiApi;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
contextWindow: number;
|
|
11
|
+
cost: {
|
|
12
|
+
cacheRead: number;
|
|
13
|
+
cacheWrite: number;
|
|
14
|
+
input: number;
|
|
15
|
+
output: number;
|
|
16
|
+
};
|
|
17
|
+
id: string;
|
|
18
|
+
input: Array<"image" | "text">;
|
|
19
|
+
maxTokens: number;
|
|
20
|
+
name: string;
|
|
21
|
+
reasoning: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export interface PiModelResolution {
|
|
25
|
+
model: {
|
|
26
|
+
name: string;
|
|
27
|
+
provider: string;
|
|
28
|
+
};
|
|
29
|
+
modelsConfig: {
|
|
30
|
+
providers: Record<string, PiModelProviderConfig>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare const createPiProviderId: (model: HarnessModelSpec) => string;
|
|
34
|
+
export declare const resolvePiHarnessModel: (model: HarnessModelSpec) => PiModelResolution;
|
|
35
|
+
export declare const renderPiModelsConfig: (models: HarnessModelSpec[]) => string;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=modelConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelConfig.d.ts","sourceRoot":"","sources":["../../src/pi/modelConfig.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,KAAK,KAAK,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAEzD,UAAU,qBAAqB;IAC7B,GAAG,EAAE,KAAK,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QACZ,GAAG,EAAE,KAAK,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE;YACJ,SAAS,EAAE,MAAM,CAAC;YAClB,UAAU,EAAE,MAAM,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,YAAY,EAAE;QACZ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KAClD,CAAC;CACH;AA2CD,eAAO,MAAM,kBAAkB,GAAI,OAAO,gBAAgB,KAAG,MAG5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,OAAO,gBAAgB,KAAG,iBAiD/D,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,gBAAgB,EAAE,KAAG,MAajE,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const stableStringify = (value) => {
|
|
3
|
+
if (Array.isArray(value)) {
|
|
4
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
5
|
+
}
|
|
6
|
+
if (value && typeof value === "object") {
|
|
7
|
+
return `{${Object.entries(value)
|
|
8
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
9
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
|
|
10
|
+
.join(",")}}`;
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
};
|
|
14
|
+
const slugify = (value) => value
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
+
.replace(/^-+|-+$/g, "");
|
|
18
|
+
const shortHash = (value) => createHash("sha1").update(value).digest("hex").slice(0, 8);
|
|
19
|
+
const endpointApi = (compatibility) => compatibility === "anthropic" ? "anthropic-messages" : "openai-completions";
|
|
20
|
+
const endpointApiKey = (model) => model.auth?.method === "api_key"
|
|
21
|
+
? `$${model.auth.keyEnv ?? `${model.provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`}`
|
|
22
|
+
: "ollama";
|
|
23
|
+
const assertSupportedEndpointAuth = (model) => {
|
|
24
|
+
const method = model.auth?.method ?? "none";
|
|
25
|
+
if (method === "none" || method === "api_key") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Pi endpoint models only support none or api_key auth, got ${method}`);
|
|
29
|
+
};
|
|
30
|
+
export const createPiProviderId = (model) => {
|
|
31
|
+
const base = slugify(`${model.provider}-${model.endpoint?.compatibility ?? "builtin"}-${model.name}`);
|
|
32
|
+
return `${base || "model"}-${shortHash(stableStringify(model))}`;
|
|
33
|
+
};
|
|
34
|
+
export const resolvePiHarnessModel = (model) => {
|
|
35
|
+
if (!model.endpoint) {
|
|
36
|
+
return {
|
|
37
|
+
model: {
|
|
38
|
+
name: model.name,
|
|
39
|
+
provider: model.provider === "openai" && model.auth?.method === "codex"
|
|
40
|
+
? "openai-codex"
|
|
41
|
+
: model.provider
|
|
42
|
+
},
|
|
43
|
+
modelsConfig: { providers: {} }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
assertSupportedEndpointAuth(model);
|
|
47
|
+
const provider = createPiProviderId(model);
|
|
48
|
+
const api = endpointApi(model.endpoint.compatibility);
|
|
49
|
+
return {
|
|
50
|
+
model: {
|
|
51
|
+
name: model.name,
|
|
52
|
+
provider
|
|
53
|
+
},
|
|
54
|
+
modelsConfig: {
|
|
55
|
+
providers: {
|
|
56
|
+
[provider]: {
|
|
57
|
+
api,
|
|
58
|
+
apiKey: endpointApiKey(model),
|
|
59
|
+
baseUrl: model.endpoint.baseUrl,
|
|
60
|
+
models: [
|
|
61
|
+
{
|
|
62
|
+
api,
|
|
63
|
+
baseUrl: model.endpoint.baseUrl,
|
|
64
|
+
contextWindow: 128000,
|
|
65
|
+
cost: {
|
|
66
|
+
cacheRead: 0,
|
|
67
|
+
cacheWrite: 0,
|
|
68
|
+
input: 0,
|
|
69
|
+
output: 0
|
|
70
|
+
},
|
|
71
|
+
id: model.name,
|
|
72
|
+
input: ["text"],
|
|
73
|
+
maxTokens: 16384,
|
|
74
|
+
name: model.name,
|
|
75
|
+
reasoning: false
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
export const renderPiModelsConfig = (models) => {
|
|
84
|
+
const providers = new Map();
|
|
85
|
+
for (const model of models) {
|
|
86
|
+
const resolution = resolvePiHarnessModel(model);
|
|
87
|
+
for (const [provider, config] of Object.entries(resolution.modelsConfig.providers)) {
|
|
88
|
+
providers.set(provider, config);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${JSON.stringify({
|
|
92
|
+
providers: Object.fromEntries([...providers.entries()].sort(([left], [right]) => left.localeCompare(right)))
|
|
93
|
+
}, null, 2)}\n`;
|
|
94
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AgentHandle, AgentHarnessAdapter, AgentStartInput, HarnessModelSpec } from "../core/types.js";
|
|
2
|
+
export interface PiHarnessOptions {
|
|
3
|
+
authPath: string;
|
|
4
|
+
model?: {
|
|
5
|
+
auth?: HarnessModelSpec["auth"];
|
|
6
|
+
endpoint?: HarnessModelSpec["endpoint"];
|
|
7
|
+
provider: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
modelsPath?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class PiHarnessAdapter implements AgentHarnessAdapter {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly authStorage;
|
|
15
|
+
private readonly modelRegistry;
|
|
16
|
+
constructor(options: PiHarnessOptions);
|
|
17
|
+
startAgent(input: AgentStartInput): Promise<AgentHandle>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=piHarness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piHarness.d.ts","sourceRoot":"","sources":["../../src/pi/piHarness.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,eAAe,EAEf,gBAAgB,EAGjB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;QACxC,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4HD,qBAAa,gBAAiB,YAAW,mBAAmB;IAI9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAEjB,OAAO,EAAE,gBAAgB;IAKhD,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;CAgC/D"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AuthStorage, createAgentSession, createExtensionRuntime, ModelRegistry, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { resolvePiHarnessModel } from "./modelConfig.js";
|
|
5
|
+
const createModelRegistry = (authStorage, options) => {
|
|
6
|
+
const registry = options.modelsPath
|
|
7
|
+
? ModelRegistry.create(authStorage, options.modelsPath)
|
|
8
|
+
: ModelRegistry.inMemory(authStorage);
|
|
9
|
+
if (!options.modelsPath && options.model?.endpoint) {
|
|
10
|
+
const { modelsConfig } = resolvePiHarnessModel(options.model);
|
|
11
|
+
for (const [provider, config] of Object.entries(modelsConfig.providers)) {
|
|
12
|
+
registry.registerProvider(provider, {
|
|
13
|
+
api: config.api,
|
|
14
|
+
apiKey: config.apiKey,
|
|
15
|
+
baseUrl: config.baseUrl,
|
|
16
|
+
models: config.models
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return registry;
|
|
21
|
+
};
|
|
22
|
+
class PiAgentHandle {
|
|
23
|
+
id;
|
|
24
|
+
session;
|
|
25
|
+
state = "idle";
|
|
26
|
+
lastWakeAt;
|
|
27
|
+
lastError;
|
|
28
|
+
constructor(id, session) {
|
|
29
|
+
this.id = id;
|
|
30
|
+
this.session = session;
|
|
31
|
+
}
|
|
32
|
+
async wake(event) {
|
|
33
|
+
const startedAt = Date.now();
|
|
34
|
+
const chunks = [];
|
|
35
|
+
this.state = "running";
|
|
36
|
+
this.lastWakeAt = new Date().toISOString();
|
|
37
|
+
this.lastError = undefined;
|
|
38
|
+
const unsubscribe = this.session.subscribe((piEvent) => {
|
|
39
|
+
if (piEvent.type !== "turn_end") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const message = piEvent.message;
|
|
43
|
+
const content = message.content;
|
|
44
|
+
if (typeof content === "string") {
|
|
45
|
+
chunks.push(content);
|
|
46
|
+
}
|
|
47
|
+
else if (Array.isArray(content)) {
|
|
48
|
+
chunks.push(content
|
|
49
|
+
.filter((item) => {
|
|
50
|
+
const candidate = item;
|
|
51
|
+
return candidate.type === "text" && typeof candidate.text === "string";
|
|
52
|
+
})
|
|
53
|
+
.map((item) => item.text)
|
|
54
|
+
.join(""));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await this.session.prompt(formatWakePrompt(event), { expandPromptTemplates: false });
|
|
59
|
+
this.state = "idle";
|
|
60
|
+
return {
|
|
61
|
+
agentId: this.id,
|
|
62
|
+
text: chunks.join("\n").trim(),
|
|
63
|
+
durationMs: Date.now() - startedAt
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.state = "failed";
|
|
68
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
unsubscribe();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
status() {
|
|
76
|
+
return {
|
|
77
|
+
agentId: this.id,
|
|
78
|
+
state: this.state,
|
|
79
|
+
lastWakeAt: this.lastWakeAt,
|
|
80
|
+
lastError: this.lastError
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async stop() {
|
|
84
|
+
this.session.dispose();
|
|
85
|
+
this.state = "stopped";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const formatWakePrompt = (event) => `Wake event:
|
|
89
|
+
- id: ${event.id}
|
|
90
|
+
- kind: ${event.kind}
|
|
91
|
+
- from: ${event.from ?? "operator"}
|
|
92
|
+
|
|
93
|
+
${event.text}`;
|
|
94
|
+
const createResourceLoader = (input) => {
|
|
95
|
+
const systemPrompt = [
|
|
96
|
+
`You are ${input.name} (${input.id}).`,
|
|
97
|
+
input.instructions,
|
|
98
|
+
"You are running inside a harnessed workspace prepared by the caller.",
|
|
99
|
+
"Use the available coding tools when asked to read, write, edit, or inspect files.",
|
|
100
|
+
"Keep responses brief and report the exact files you created or modified."
|
|
101
|
+
].join("\n\n");
|
|
102
|
+
return {
|
|
103
|
+
getExtensions: () => ({ extensions: [], errors: [], runtime: createExtensionRuntime() }),
|
|
104
|
+
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
105
|
+
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
106
|
+
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
107
|
+
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
108
|
+
getSystemPrompt: () => systemPrompt,
|
|
109
|
+
getAppendSystemPrompt: () => [],
|
|
110
|
+
extendResources: () => { },
|
|
111
|
+
reload: async () => { }
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
export class PiHarnessAdapter {
|
|
115
|
+
options;
|
|
116
|
+
authStorage;
|
|
117
|
+
modelRegistry;
|
|
118
|
+
constructor(options) {
|
|
119
|
+
this.options = options;
|
|
120
|
+
this.authStorage = AuthStorage.create(options.authPath);
|
|
121
|
+
this.modelRegistry = createModelRegistry(this.authStorage, options);
|
|
122
|
+
}
|
|
123
|
+
async startAgent(input) {
|
|
124
|
+
await mkdir(input.runtimeHomePath, { recursive: true });
|
|
125
|
+
await mkdir(input.workspacePath, { recursive: true });
|
|
126
|
+
const modelSpec = this.options.model ?? {
|
|
127
|
+
auth: { method: "codex" },
|
|
128
|
+
provider: "openai",
|
|
129
|
+
name: "gpt-5.4-mini"
|
|
130
|
+
};
|
|
131
|
+
const resolvedModel = resolvePiHarnessModel(modelSpec).model;
|
|
132
|
+
const model = this.modelRegistry.find(resolvedModel.provider, resolvedModel.name);
|
|
133
|
+
if (!model) {
|
|
134
|
+
throw new Error(`Pi model not found: ${resolvedModel.provider}/${resolvedModel.name}`);
|
|
135
|
+
}
|
|
136
|
+
const { session } = await createAgentSession({
|
|
137
|
+
cwd: input.workspacePath,
|
|
138
|
+
agentDir: input.runtimeHomePath,
|
|
139
|
+
authStorage: this.authStorage,
|
|
140
|
+
modelRegistry: this.modelRegistry,
|
|
141
|
+
model,
|
|
142
|
+
thinkingLevel: "off",
|
|
143
|
+
resourceLoader: createResourceLoader(input),
|
|
144
|
+
tools: input.tools ?? ["read", "write", "edit", "bash", "grep", "find", "ls"],
|
|
145
|
+
sessionManager: SessionManager.create(input.workspacePath, path.join(input.runtimeHomePath, "sessions")),
|
|
146
|
+
settingsManager: SettingsManager.inMemory({
|
|
147
|
+
compaction: { enabled: false },
|
|
148
|
+
retry: { enabled: true, maxRetries: 1 }
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
return new PiAgentHandle(input.id, session);
|
|
152
|
+
}
|
|
153
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noopolis/daimon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Noopolis native per-agent runtime harness built on Pi.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+ssh://git@github.com/noopolis/daimon.git"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "rm -rf dist && tsc --project tsconfig.build.json",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"prepack": "npm run build",
|
|
30
|
+
"prepublishOnly": "npm run typecheck && npm test",
|
|
31
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
32
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
33
|
+
"e2e:pi-agent": "tsx src/examples/pi-agent.ts"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.19.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@earendil-works/pi-ai": "^0.79.9",
|
|
40
|
+
"@earendil-works/pi-coding-agent": "^0.79.9"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^24.12.4",
|
|
44
|
+
"tsx": "^4.21.0",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|