@pome-sh/cli 0.43.1 → 0.45.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.
@@ -1,187 +0,0 @@
1
- import { readSeedFileText, seedForTwin, parseSeedFileText, soleTwinOf, twinsNamedBy } from './chunk-GQSNU3YP.js';
2
- import { bootTwin } from './chunk-64AWQJ7R.js';
3
- import { TWIN_REGISTRY, isTwinName, TWIN_NAMES, defaultPortFor } from './chunk-4DXTT3RV.js';
4
- import './chunk-TWURH7YM.js';
5
- import './chunk-5KFDRR53.js';
6
- import './chunk-SG6ZTIMT.js';
7
- import './chunk-FBSA5L36.js';
8
- import { randomBytes } from 'node:crypto';
9
- import { readFileSync } from 'node:fs';
10
- import { mkdir, writeFile, chmod } from 'node:fs/promises';
11
- import { dirname, join } from 'node:path';
12
- import { serve } from '@hono/node-server';
13
- import { sign } from 'hono/jwt';
14
-
15
- var STANDALONE_SID = "standalone";
16
- var STANDALONE_STATUS_PATH = ".pome/twin-status.json";
17
- async function writeStandaloneStatusFile(status, path = STANDALONE_STATUS_PATH) {
18
- await mkdir(dirname(path), { recursive: true, mode: 448 });
19
- await writeFile(path, JSON.stringify(status, null, 2), { mode: 384 });
20
- await chmod(path, 384);
21
- }
22
- function resolveStandaloneAuthSecret(twin, env = process.env) {
23
- const injected = env.TWIN_AUTH_SECRET;
24
- if (injected) return { secret: injected, source: "env" };
25
- const dataDir = env.POME_TWIN_DATA_DIR || join(".pome-data", twin);
26
- const secretPath = join(dataDir, "secret");
27
- let raw;
28
- try {
29
- raw = readFileSync(secretPath, "utf8");
30
- } catch (err) {
31
- if (err.code !== "ENOENT") throw err;
32
- }
33
- const persisted = raw?.trim() ?? "";
34
- if (persisted.length >= 32) {
35
- return { secret: persisted, source: "persisted", path: secretPath };
36
- }
37
- if (persisted.length > 0) {
38
- throw new Error(
39
- `The persisted secret at ${secretPath} is shorter than 32 chars \u2014 fix or delete the file, or inject TWIN_AUTH_SECRET.`
40
- );
41
- }
42
- return { secret: randomBytes(32).toString("hex"), source: "ephemeral" };
43
- }
44
- async function resolveStandaloneSeed(twin, seedPath, env = process.env, seedText) {
45
- if (seedPath !== void 0) {
46
- const raw = seedText ?? readSeedFileText(seedPath, "pome twin start --seed");
47
- const origin = `--seed ${seedPath}`;
48
- return {
49
- seedState: await seedForTwin(parseSeedFileText(raw, origin), twin, origin),
50
- source: "file",
51
- path: seedPath
52
- };
53
- }
54
- const fromEnv = env.POME_SEED_JSON;
55
- if (fromEnv !== void 0 && fromEnv !== "") {
56
- return {
57
- seedState: await seedForTwin(
58
- parseSeedFileText(fromEnv, "POME_SEED_JSON"),
59
- twin,
60
- "POME_SEED_JSON"
61
- ),
62
- source: "env"
63
- };
64
- }
65
- return { seedState: await TWIN_REGISTRY[twin].defaultSeed(), source: "default" };
66
- }
67
- function resolveStandaloneTwin(name, seedPath, seedText) {
68
- if (name !== void 0) {
69
- if (!isTwinName(name)) {
70
- throw new Error(`Unknown twin '${name}'. Supported: ${TWIN_NAMES.join(", ")}.`);
71
- }
72
- return name;
73
- }
74
- if (seedPath === void 0 || seedText === void 0) {
75
- throw new Error(
76
- `pome twin start: name a twin (${TWIN_NAMES.join(", ")}), or pass a --seed file whose envelope names exactly one.`
77
- );
78
- }
79
- const origin = `--seed ${seedPath}`;
80
- const file = parseSeedFileText(seedText, origin);
81
- const sole = soleTwinOf(file);
82
- if (sole !== void 0) return sole;
83
- const named = twinsNamedBy(file);
84
- throw new Error(
85
- named.length === 0 ? `${origin} is a flat seed, so it does not name a twin. Pass the name: pome twin start <${TWIN_NAMES.join("|")}> --seed ${seedPath}` : `${origin} names ${named.length} twins (${named.join(", ")}), so it does not say which one to start. Pass the name: pome twin start ${named[0]} --seed ${seedPath}`
86
- );
87
- }
88
- async function runTwinStartCommand(nameArg, options) {
89
- const seedText = options.seed === void 0 ? void 0 : readSeedFileText(options.seed, "pome twin start --seed");
90
- const name = resolveStandaloneTwin(nameArg, options.seed, seedText);
91
- const portRaw = options.port ?? defaultPortFor(name, process.env);
92
- const port = Number(portRaw);
93
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
94
- throw new Error(`pome twin start: invalid --port "${portRaw}"`);
95
- }
96
- const world = await resolveStandaloneSeed(name, options.seed, process.env, seedText);
97
- const resolved = resolveStandaloneAuthSecret(name);
98
- process.env.TWIN_AUTH_SECRET = resolved.secret;
99
- const baseUrl = `http://127.0.0.1:${port}`;
100
- const harness = await bootTwin({
101
- twin: name,
102
- seedState: world.seedState,
103
- runId: STANDALONE_SID,
104
- twinBaseUrl: baseUrl
105
- });
106
- const token = await sign(
107
- {
108
- sid: STANDALONE_SID,
109
- team_id: "tm_local",
110
- // Same claims `pome run --local` mints: `login` so the GitHub REST
111
- // merge gate resolves the agent user; twin-supplied extras (stripe's
112
- // `account_id`) so the token lands on the seeded account.
113
- login: "pome-agent",
114
- ...harness.extraClaims ?? {},
115
- exp: Math.floor(Date.now() / 1e3) + 60 * 60 * 24
116
- },
117
- resolved.secret
118
- );
119
- const restUrl = `${baseUrl}/s/${STANDALONE_SID}`;
120
- const mcpUrl = `${restUrl}/mcp`;
121
- const server = serve({ fetch: harness.app.fetch, port, hostname: "127.0.0.1" });
122
- try {
123
- await new Promise((resolve, reject) => {
124
- const onError = (err) => {
125
- reject(
126
- err.code === "EADDRINUSE" ? new Error(
127
- `pome twin start: port ${port} is already in use \u2014 pass --port <port>, or stop the twin using it.`
128
- ) : err
129
- );
130
- };
131
- server.once("error", onError);
132
- server.once("listening", () => {
133
- server.off("error", onError);
134
- server.on("error", (err) => console.error(`pome twin start: server error: ${err}`));
135
- resolve();
136
- });
137
- });
138
- await writeStandaloneStatusFile({
139
- name,
140
- url: restUrl,
141
- rest_url: restUrl,
142
- mcp_url: mcpUrl,
143
- auth_token: token
144
- });
145
- } catch (err) {
146
- await new Promise((resolve) => server.close(() => resolve()));
147
- await harness.close();
148
- throw err;
149
- }
150
- console.log(`Pome ${name} twin listening at ${restUrl}`);
151
- if (world.source === "file") {
152
- console.log(`Seed: ${world.path} (replaces the ${name} twin's default).`);
153
- } else if (world.source === "env") {
154
- console.log(
155
- `Seed: POME_SEED_JSON (replaces the ${name} twin's default; --seed <path> overrides it).`
156
- );
157
- } else {
158
- console.log(
159
- `Seed: the ${name} twin's default (pass --seed <path>, or write one with \`pome twin new-seed ${name}\`).`
160
- );
161
- }
162
- if (resolved.source === "persisted") {
163
- console.log(
164
- `Auth: using the persisted secret from ${resolved.path} (an env-injected TWIN_AUTH_SECRET overrides it).`
165
- );
166
- }
167
- console.log(`POME_${harness.envName}_REST_URL=${restUrl}`);
168
- console.log(`POME_${harness.envName}_MCP_URL=${mcpUrl}`);
169
- console.log(`POME_AUTH_TOKEN=${token}`);
170
- if (harness.tokenEnvName) console.log(`${harness.tokenEnvName}=${token}`);
171
- console.log(`Health check (no auth): curl ${baseUrl}/healthz`);
172
- console.log("Ctrl-C to stop.");
173
- const shutdown = () => {
174
- void (async () => {
175
- const hardExit = setTimeout(() => process.exit(1), 1e4);
176
- hardExit.unref();
177
- server.closeAllConnections?.();
178
- await new Promise((resolve) => server.close(() => resolve()));
179
- await harness.close();
180
- process.exit(0);
181
- })();
182
- };
183
- process.once("SIGINT", shutdown);
184
- process.once("SIGTERM", shutdown);
185
- }
186
-
187
- export { STANDALONE_STATUS_PATH, resolveStandaloneAuthSecret, resolveStandaloneSeed, resolveStandaloneTwin, runTwinStartCommand, writeStandaloneStatusFile };