starcite 0.0.6 → 0.0.8
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/README.md +31 -81
- package/dist/index.js +576 -983
- package/dist/index.js.map +1 -1
- package/package.json +3 -8
package/dist/index.js
CHANGED
|
@@ -1,114 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
5
|
-
Starcite,
|
|
6
|
-
StarciteApiError,
|
|
7
|
-
StarciteIdentity
|
|
8
|
-
} from "@starcite/sdk";
|
|
9
|
-
import { Command, InvalidArgumentError } from "commander";
|
|
10
|
-
import { createConsola } from "consola";
|
|
11
|
-
import { z as z2 } from "zod";
|
|
4
|
+
import { StarciteApiError } from "@starcite/sdk";
|
|
12
5
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
version: "0.0.6",
|
|
17
|
-
description: "CLI for Starcite",
|
|
18
|
-
license: "Apache-2.0",
|
|
19
|
-
homepage: "https://starcite.ai",
|
|
20
|
-
repository: {
|
|
21
|
-
type: "git",
|
|
22
|
-
url: "https://github.com/fastpaca/starcite-clients.git",
|
|
23
|
-
directory: "packages/starcite-cli"
|
|
24
|
-
},
|
|
25
|
-
bugs: {
|
|
26
|
-
url: "https://github.com/fastpaca/starcite-clients/issues"
|
|
27
|
-
},
|
|
28
|
-
keywords: [
|
|
29
|
-
"starcite",
|
|
30
|
-
"ai",
|
|
31
|
-
"sessions",
|
|
32
|
-
"event-log",
|
|
33
|
-
"cli"
|
|
34
|
-
],
|
|
35
|
-
type: "module",
|
|
36
|
-
bin: {
|
|
37
|
-
starcite: "./dist/index.js"
|
|
38
|
-
},
|
|
39
|
-
files: [
|
|
40
|
-
"dist"
|
|
41
|
-
],
|
|
42
|
-
scripts: {
|
|
43
|
-
clean: "rm -rf dist",
|
|
44
|
-
build: "tsup",
|
|
45
|
-
dev: "bun run src/index.ts",
|
|
46
|
-
compile: "bun run --cwd ../typescript-sdk build && bun build --compile src/index.ts --outfile dist/starcite",
|
|
47
|
-
test: "vitest run && bun run test:dist",
|
|
48
|
-
"test:live": "vitest run test/live.api.integration.test.ts",
|
|
49
|
-
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
50
|
-
prepublishOnly: "bun run clean && bun run build",
|
|
51
|
-
"publish:dry": "bun publish --dry-run --access public",
|
|
52
|
-
"test:dist": "bun run --cwd ../typescript-sdk build && bun run clean && bun run build && node dist/index.js --help > /dev/null",
|
|
53
|
-
lint: "ultracite check src test package.json tsconfig.json tsup.config.ts vitest.config.ts README.md",
|
|
54
|
-
format: "ultracite fix src test package.json tsconfig.json tsup.config.ts vitest.config.ts README.md",
|
|
55
|
-
check: "bun run lint && bun run typecheck && bun run test"
|
|
56
|
-
},
|
|
57
|
-
dependencies: {
|
|
58
|
-
"@clack/prompts": "^1.0.1",
|
|
59
|
-
"@starcite/sdk": "^0.0.6",
|
|
60
|
-
commander: "^13.1.0",
|
|
61
|
-
conf: "^15.1.0",
|
|
62
|
-
consola: "^3.4.2",
|
|
63
|
-
cosmiconfig: "^9.0.0",
|
|
64
|
-
"proper-lockfile": "^4.1.2",
|
|
65
|
-
toml: "^3.0.0",
|
|
66
|
-
zod: "^3.25.76"
|
|
67
|
-
},
|
|
68
|
-
devDependencies: {
|
|
69
|
-
"@types/node": "^22.15.30",
|
|
70
|
-
"@types/proper-lockfile": "^4.1.4",
|
|
71
|
-
tsup: "^8.5.0",
|
|
72
|
-
typescript: "^5.8.3",
|
|
73
|
-
vitest: "^2.1.9"
|
|
74
|
-
}
|
|
75
|
-
};
|
|
6
|
+
// src/runtime.ts
|
|
7
|
+
import { Starcite } from "@starcite/sdk";
|
|
8
|
+
import arg from "arg";
|
|
76
9
|
|
|
77
|
-
// src/
|
|
78
|
-
import {
|
|
79
|
-
import {
|
|
80
|
-
import { homedir, hostname } from "os";
|
|
10
|
+
// src/config.ts
|
|
11
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
12
|
+
import { homedir } from "os";
|
|
81
13
|
import { join, resolve } from "path";
|
|
82
14
|
import Conf from "conf";
|
|
83
|
-
import { cosmiconfig, defaultLoaders } from "cosmiconfig";
|
|
84
|
-
import { lock } from "proper-lockfile";
|
|
85
15
|
import { parse as parseToml } from "toml";
|
|
86
16
|
import { z } from "zod";
|
|
87
17
|
var DEFAULT_CONFIG_DIRECTORY_NAME = ".starcite";
|
|
88
18
|
var CONFIG_JSON_FILENAME = "config.json";
|
|
89
19
|
var CONFIG_TOML_FILENAME = "config.toml";
|
|
90
20
|
var CREDENTIALS_FILENAME = "credentials";
|
|
91
|
-
var IDENTITY_FILENAME = "identity";
|
|
92
|
-
var STATE_FILENAME = "state";
|
|
93
|
-
var STATE_LOCK_FILENAME = ".state.lock";
|
|
94
21
|
var TILDE_PREFIX_REGEX = /^~(?=\/|$)/;
|
|
95
22
|
var ConfigFileSchema = z.object({
|
|
96
23
|
baseUrl: z.string().optional(),
|
|
97
24
|
base_url: z.string().optional(),
|
|
98
|
-
producerId: z.string().optional(),
|
|
99
|
-
producer_id: z.string().optional(),
|
|
100
25
|
apiKey: z.string().optional(),
|
|
101
26
|
api_key: z.string().optional()
|
|
102
27
|
}).passthrough();
|
|
103
|
-
var IdentityFileSchema = z.object({
|
|
104
|
-
producerId: z.string().trim().min(1),
|
|
105
|
-
hostname: z.string().trim().min(1),
|
|
106
|
-
uuid: z.string().uuid(),
|
|
107
|
-
createdAt: z.string()
|
|
108
|
-
});
|
|
109
|
-
var StateFileSchema = z.object({
|
|
110
|
-
nextSeqByContext: z.record(z.number().int().positive()).default({})
|
|
111
|
-
});
|
|
112
28
|
var CredentialsFileSchema = z.object({
|
|
113
29
|
apiKey: z.string().trim().min(1).optional()
|
|
114
30
|
});
|
|
@@ -123,1064 +39,741 @@ function normalizeConfig(input) {
|
|
|
123
39
|
}
|
|
124
40
|
return {
|
|
125
41
|
baseUrl: trimString(parsed.data.baseUrl ?? parsed.data.base_url),
|
|
126
|
-
producerId: trimString(parsed.data.producerId ?? parsed.data.producer_id),
|
|
127
42
|
apiKey: trimString(parsed.data.apiKey ?? parsed.data.api_key)
|
|
128
43
|
};
|
|
129
44
|
}
|
|
130
45
|
function defaultConfigDirectory() {
|
|
131
46
|
const home = homedir();
|
|
132
|
-
|
|
133
|
-
return join(home, DEFAULT_CONFIG_DIRECTORY_NAME);
|
|
134
|
-
}
|
|
135
|
-
return resolve(DEFAULT_CONFIG_DIRECTORY_NAME);
|
|
47
|
+
return home.trim().length > 0 ? join(home, DEFAULT_CONFIG_DIRECTORY_NAME) : resolve(DEFAULT_CONFIG_DIRECTORY_NAME);
|
|
136
48
|
}
|
|
137
49
|
function resolveConfigDir(input) {
|
|
138
50
|
const configured = trimString(input) ?? trimString(process.env.STARCITE_HOME);
|
|
139
51
|
const withTilde = configured?.startsWith("~") ? configured.replace(TILDE_PREFIX_REGEX, homedir()) : configured;
|
|
140
52
|
return resolve(withTilde ?? defaultConfigDirectory());
|
|
141
53
|
}
|
|
142
|
-
|
|
143
|
-
return `${baseUrl}::${sessionId}::${producerId}`;
|
|
144
|
-
}
|
|
145
|
-
var StarciteCliStore = class {
|
|
54
|
+
var StarciteCliConfigStore = class {
|
|
146
55
|
directory;
|
|
147
|
-
lockPath;
|
|
148
|
-
configExplorer = cosmiconfig("starcite", {
|
|
149
|
-
cache: false,
|
|
150
|
-
searchStrategy: "none",
|
|
151
|
-
searchPlaces: [CONFIG_JSON_FILENAME, CONFIG_TOML_FILENAME],
|
|
152
|
-
loaders: {
|
|
153
|
-
...defaultLoaders,
|
|
154
|
-
".toml": (_filepath, content) => parseToml(content)
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
identityStore;
|
|
158
56
|
credentialsStore;
|
|
159
|
-
stateStore;
|
|
160
57
|
constructor(directory) {
|
|
161
58
|
this.directory = directory;
|
|
162
|
-
this.lockPath = join(directory, STATE_LOCK_FILENAME);
|
|
163
|
-
this.identityStore = new Conf({
|
|
164
|
-
cwd: directory,
|
|
165
|
-
clearInvalidConfig: true,
|
|
166
|
-
configName: IDENTITY_FILENAME,
|
|
167
|
-
fileExtension: "json"
|
|
168
|
-
});
|
|
169
59
|
this.credentialsStore = new Conf({
|
|
170
60
|
cwd: directory,
|
|
171
61
|
clearInvalidConfig: true,
|
|
172
62
|
configName: CREDENTIALS_FILENAME,
|
|
173
|
-
fileExtension: "json"
|
|
174
|
-
defaults: {}
|
|
175
|
-
});
|
|
176
|
-
this.stateStore = new Conf({
|
|
177
|
-
cwd: directory,
|
|
178
|
-
clearInvalidConfig: true,
|
|
179
|
-
configName: STATE_FILENAME,
|
|
180
|
-
fileExtension: "json",
|
|
181
|
-
defaults: { nextSeqByContext: {} }
|
|
63
|
+
fileExtension: "json"
|
|
182
64
|
});
|
|
183
65
|
}
|
|
184
66
|
async readConfig() {
|
|
185
|
-
await this.
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
67
|
+
await this.ensureDirectory();
|
|
68
|
+
for (const filename of [CONFIG_JSON_FILENAME, CONFIG_TOML_FILENAME]) {
|
|
69
|
+
const parsed = await this.readConfigFile(filename);
|
|
70
|
+
if (parsed !== void 0) {
|
|
71
|
+
return normalizeConfig(parsed);
|
|
72
|
+
}
|
|
189
73
|
}
|
|
190
|
-
return
|
|
74
|
+
return {};
|
|
191
75
|
}
|
|
192
76
|
async writeConfig(config) {
|
|
193
|
-
await this.
|
|
77
|
+
await this.ensureDirectory();
|
|
194
78
|
const normalized = normalizeConfig(config);
|
|
195
|
-
const serialized = {};
|
|
196
|
-
if (normalized.baseUrl) {
|
|
197
|
-
serialized.baseUrl = normalized.baseUrl;
|
|
198
|
-
}
|
|
199
|
-
if (normalized.producerId) {
|
|
200
|
-
serialized.producerId = normalized.producerId;
|
|
201
|
-
}
|
|
202
|
-
if (normalized.apiKey) {
|
|
203
|
-
serialized.apiKey = normalized.apiKey;
|
|
204
|
-
}
|
|
205
79
|
await writeFile(
|
|
206
80
|
join(this.directory, CONFIG_JSON_FILENAME),
|
|
207
|
-
`${JSON.stringify(
|
|
81
|
+
`${JSON.stringify(normalized, null, 2)}
|
|
208
82
|
`,
|
|
209
83
|
"utf8"
|
|
210
84
|
);
|
|
211
85
|
}
|
|
212
86
|
async updateConfig(patch) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
...current,
|
|
87
|
+
const config = normalizeConfig({
|
|
88
|
+
...await this.readConfig(),
|
|
216
89
|
...patch
|
|
217
90
|
});
|
|
218
|
-
await this.writeConfig(
|
|
219
|
-
return
|
|
91
|
+
await this.writeConfig(config);
|
|
92
|
+
return config;
|
|
220
93
|
}
|
|
221
94
|
async readApiKey() {
|
|
222
95
|
const fromEnv = trimString(process.env.STARCITE_API_KEY);
|
|
223
96
|
if (fromEnv) {
|
|
224
97
|
return fromEnv;
|
|
225
98
|
}
|
|
226
|
-
const
|
|
227
|
-
const fromCredentials = parsed.success ? trimString(parsed.data.apiKey) : void 0;
|
|
99
|
+
const fromCredentials = trimString(this.readCredentials().apiKey);
|
|
228
100
|
if (fromCredentials) {
|
|
229
101
|
return fromCredentials;
|
|
230
102
|
}
|
|
231
|
-
|
|
232
|
-
return trimString(config.apiKey);
|
|
103
|
+
return trimString((await this.readConfig()).apiKey);
|
|
233
104
|
}
|
|
234
105
|
async saveApiKey(apiKey) {
|
|
235
|
-
await this.
|
|
106
|
+
await this.ensureDirectory();
|
|
236
107
|
const normalized = trimString(apiKey);
|
|
237
108
|
if (!normalized) {
|
|
238
109
|
throw new Error("API key cannot be empty");
|
|
239
110
|
}
|
|
240
111
|
this.credentialsStore.set("apiKey", normalized);
|
|
241
112
|
}
|
|
242
|
-
async
|
|
243
|
-
|
|
244
|
-
this.credentialsStore.delete("apiKey");
|
|
245
|
-
}
|
|
246
|
-
async resolveProducerId(explicitProducerId) {
|
|
247
|
-
const explicit = trimString(explicitProducerId);
|
|
248
|
-
if (explicit) {
|
|
249
|
-
return explicit;
|
|
250
|
-
}
|
|
251
|
-
const fromEnv = trimString(process.env.STARCITE_PRODUCER_ID);
|
|
252
|
-
if (fromEnv) {
|
|
253
|
-
return fromEnv;
|
|
254
|
-
}
|
|
255
|
-
const config = await this.readConfig();
|
|
256
|
-
const fromConfig = trimString(config.producerId);
|
|
257
|
-
if (fromConfig) {
|
|
258
|
-
return fromConfig;
|
|
259
|
-
}
|
|
260
|
-
const identity = await this.readOrCreateIdentity();
|
|
261
|
-
return identity.producerId;
|
|
262
|
-
}
|
|
263
|
-
async withStateLock(action) {
|
|
264
|
-
await this.ensureConfigDirectory();
|
|
265
|
-
const release = await lock(this.directory, {
|
|
266
|
-
lockfilePath: this.lockPath,
|
|
267
|
-
realpath: false,
|
|
268
|
-
retries: {
|
|
269
|
-
retries: 50,
|
|
270
|
-
minTimeout: 20,
|
|
271
|
-
maxTimeout: 60
|
|
272
|
-
}
|
|
273
|
-
});
|
|
113
|
+
async readConfigFile(filename) {
|
|
114
|
+
const path = join(this.directory, filename);
|
|
274
115
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return Promise.resolve(state.nextSeqByContext[contextKey] ?? 1);
|
|
283
|
-
}
|
|
284
|
-
bumpNextSeq(contextKey, usedSeq) {
|
|
285
|
-
const state = this.readState();
|
|
286
|
-
const nextSeqByContext = {
|
|
287
|
-
...state.nextSeqByContext,
|
|
288
|
-
[contextKey]: Math.max(
|
|
289
|
-
state.nextSeqByContext[contextKey] ?? 1,
|
|
290
|
-
usedSeq + 1
|
|
291
|
-
)
|
|
292
|
-
};
|
|
293
|
-
this.stateStore.set("nextSeqByContext", nextSeqByContext);
|
|
294
|
-
return Promise.resolve();
|
|
295
|
-
}
|
|
296
|
-
readState() {
|
|
297
|
-
const parsed = StateFileSchema.safeParse(this.stateStore.store);
|
|
298
|
-
if (parsed.success) {
|
|
299
|
-
return parsed.data;
|
|
116
|
+
const content = await readFile(path, "utf8");
|
|
117
|
+
return filename.endsWith(".toml") ? parseToml(content) : JSON.parse(content);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
300
123
|
}
|
|
301
|
-
this.stateStore.clear();
|
|
302
|
-
return { nextSeqByContext: {} };
|
|
303
124
|
}
|
|
304
|
-
|
|
305
|
-
const parsed =
|
|
125
|
+
readCredentials() {
|
|
126
|
+
const parsed = CredentialsFileSchema.safeParse(this.credentialsStore.store);
|
|
306
127
|
if (parsed.success) {
|
|
307
128
|
return parsed.data;
|
|
308
129
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const identity = {
|
|
312
|
-
producerId: `cli:${host}:${uuid}`,
|
|
313
|
-
hostname: host,
|
|
314
|
-
uuid,
|
|
315
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
316
|
-
};
|
|
317
|
-
this.identityStore.store = identity;
|
|
318
|
-
return identity;
|
|
130
|
+
this.credentialsStore.clear();
|
|
131
|
+
return {};
|
|
319
132
|
}
|
|
320
|
-
async
|
|
133
|
+
async ensureDirectory() {
|
|
321
134
|
await mkdir(this.directory, { recursive: true });
|
|
322
135
|
}
|
|
323
136
|
};
|
|
324
137
|
|
|
325
|
-
// src/
|
|
326
|
-
import { spawn } from "child_process";
|
|
327
|
-
import { mkdir as mkdir2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
328
|
-
import { join as join2 } from "path";
|
|
138
|
+
// src/store.ts
|
|
329
139
|
import {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
var
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
var MAX_PORT = 65535;
|
|
340
|
-
var RUNTIME_DIRECTORY_NAME = "runtime";
|
|
341
|
-
var DEFAULT_COMPOSE_FILE = `services:
|
|
342
|
-
app:
|
|
343
|
-
image: \${STARCITE_IMAGE:-ghcr.io/fastpaca/starcite:latest}
|
|
344
|
-
depends_on:
|
|
345
|
-
db:
|
|
346
|
-
condition: service_healthy
|
|
347
|
-
environment:
|
|
348
|
-
SECRET_KEY_BASE: \${SECRET_KEY_BASE:-xuQnOFm6sH5Qdd7x4WJv5smuG2Xf2nG0BL8rJ4yX6HnKGeTjo6n8r5hQKsxNkZWz}
|
|
349
|
-
PHX_HOST: \${PHX_HOST:-localhost}
|
|
350
|
-
PORT: 4000
|
|
351
|
-
DATABASE_URL: \${DATABASE_URL:-ecto://postgres:postgres@db:5432/starcite_dev}
|
|
352
|
-
MIGRATE_ON_BOOT: \${MIGRATE_ON_BOOT:-true}
|
|
353
|
-
DNS_CLUSTER_QUERY: \${DNS_CLUSTER_QUERY:-}
|
|
354
|
-
DNS_CLUSTER_NODE_BASENAME: \${DNS_CLUSTER_NODE_BASENAME:-starcite}
|
|
355
|
-
DNS_POLL_INTERVAL_MS: \${DNS_POLL_INTERVAL_MS:-5000}
|
|
356
|
-
ports:
|
|
357
|
-
- "\${STARCITE_API_PORT:-45187}:4000"
|
|
358
|
-
restart: unless-stopped
|
|
359
|
-
|
|
360
|
-
db:
|
|
361
|
-
image: postgres:15
|
|
362
|
-
environment:
|
|
363
|
-
POSTGRES_DB: starcite_dev
|
|
364
|
-
POSTGRES_USER: postgres
|
|
365
|
-
POSTGRES_PASSWORD: postgres
|
|
366
|
-
healthcheck:
|
|
367
|
-
test: ["CMD", "pg_isready", "-U", "postgres"]
|
|
368
|
-
interval: 10s
|
|
369
|
-
timeout: 5s
|
|
370
|
-
retries: 5
|
|
371
|
-
ports:
|
|
372
|
-
- "\${STARCITE_DB_PORT:-5433}:5432"
|
|
373
|
-
volumes:
|
|
374
|
-
- db-data:/var/lib/postgresql/data
|
|
375
|
-
restart: unless-stopped
|
|
376
|
-
|
|
377
|
-
volumes:
|
|
378
|
-
db-data:
|
|
379
|
-
`;
|
|
380
|
-
function parsePort(value, optionName) {
|
|
381
|
-
const parsed = Number(value);
|
|
382
|
-
if (!Number.isInteger(parsed) || parsed < MIN_PORT || parsed > MAX_PORT) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
`${optionName} must be an integer between ${MIN_PORT} and ${MAX_PORT}`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
return parsed;
|
|
140
|
+
WebStorageSessionStore
|
|
141
|
+
} from "@starcite/sdk";
|
|
142
|
+
import Conf2 from "conf";
|
|
143
|
+
var STATE_FILENAME = "state";
|
|
144
|
+
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
145
|
+
var STORE_VERSION_KEY = "__starciteCliStoreVersion";
|
|
146
|
+
var CURRENT_STORE_VERSION = "2";
|
|
147
|
+
function buildSessionStoreContextKey(baseUrl, sessionId) {
|
|
148
|
+
return `${baseUrl}::${sessionId}`;
|
|
388
149
|
}
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (parsed.port) {
|
|
393
|
-
return parsePort(parsed.port, "base URL port");
|
|
394
|
-
}
|
|
395
|
-
} catch {
|
|
396
|
-
return DEFAULT_API_PORT;
|
|
150
|
+
function normalizeStoreBaseUrl(baseUrl) {
|
|
151
|
+
if (baseUrl.length === 0) {
|
|
152
|
+
return "";
|
|
397
153
|
}
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
async function ensureSuccess(runCommand, command, args) {
|
|
401
|
-
const result = await runCommand(command, args);
|
|
402
|
-
return result.code === 0;
|
|
154
|
+
const normalized = baseUrl.replace(TRAILING_SLASHES_REGEX, "");
|
|
155
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
403
156
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
157
|
+
var StarciteCliStore = class {
|
|
158
|
+
storage;
|
|
159
|
+
constructor(directory) {
|
|
160
|
+
this.storage = new Conf2({
|
|
161
|
+
cwd: directory,
|
|
162
|
+
clearInvalidConfig: true,
|
|
163
|
+
configName: STATE_FILENAME,
|
|
164
|
+
fileExtension: "json",
|
|
165
|
+
defaults: {}
|
|
166
|
+
});
|
|
167
|
+
this.resetOnStoreVersionMismatch();
|
|
409
168
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!hasDockerCompose) {
|
|
415
|
-
logger.error(
|
|
416
|
-
"Docker Compose is not available. Install Docker Compose and retry."
|
|
417
|
-
);
|
|
418
|
-
throw new Error("Docker Compose is required to run this command.");
|
|
169
|
+
sessionStore(baseUrl) {
|
|
170
|
+
return new WebStorageSessionStore(this.storageAdapter(), {
|
|
171
|
+
keyForSession: (sessionId) => buildSessionStoreContextKey(normalizeStoreBaseUrl(baseUrl), sessionId)
|
|
172
|
+
});
|
|
419
173
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
logger.error("Docker is installed but the daemon is not running.");
|
|
423
|
-
throw new Error("Start Docker and retry.");
|
|
174
|
+
load(sessionId) {
|
|
175
|
+
return this.sessionStore("").load(sessionId);
|
|
424
176
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (options.port !== void 0) {
|
|
428
|
-
return options.port;
|
|
177
|
+
save(sessionId, state) {
|
|
178
|
+
this.sessionStore("").save(sessionId, state);
|
|
429
179
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return fallbackPort;
|
|
180
|
+
clear(sessionId) {
|
|
181
|
+
this.sessionStore("").clear?.(sessionId);
|
|
433
182
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
183
|
+
clearSession(baseUrl, sessionId) {
|
|
184
|
+
this.sessionStore(baseUrl).clear?.(sessionId);
|
|
185
|
+
}
|
|
186
|
+
storageAdapter() {
|
|
187
|
+
return {
|
|
188
|
+
getItem: (key) => this.storage.get(key) ?? null,
|
|
189
|
+
setItem: (key, value) => {
|
|
190
|
+
this.storage.set(key, value);
|
|
191
|
+
},
|
|
192
|
+
removeItem: (key) => {
|
|
193
|
+
this.storage.delete(key);
|
|
443
194
|
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
resetOnStoreVersionMismatch() {
|
|
198
|
+
const storedVersion = this.storage.get(STORE_VERSION_KEY);
|
|
199
|
+
if (storedVersion === CURRENT_STORE_VERSION) {
|
|
200
|
+
return;
|
|
444
201
|
}
|
|
202
|
+
this.storage.clear();
|
|
203
|
+
this.storage.set(STORE_VERSION_KEY, CURRENT_STORE_VERSION);
|
|
445
204
|
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/runtime.ts
|
|
208
|
+
var DEFAULT_API_PORT = 45187;
|
|
209
|
+
var DEFAULT_TAIL_BATCH_SIZE = 256;
|
|
210
|
+
var TRAILING_SLASHES_REGEX2 = /\/+$/;
|
|
211
|
+
var CliUsageError = class extends Error {
|
|
212
|
+
};
|
|
213
|
+
var defaultLogger = {
|
|
214
|
+
info(message) {
|
|
215
|
+
console.log(message);
|
|
216
|
+
},
|
|
217
|
+
error(message) {
|
|
218
|
+
console.error(message);
|
|
456
219
|
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
await writeFile2(
|
|
462
|
-
join2(directory, "docker-compose.yml"),
|
|
463
|
-
DEFAULT_COMPOSE_FILE,
|
|
464
|
-
"utf8"
|
|
465
|
-
);
|
|
466
|
-
const envLines = [
|
|
467
|
-
`STARCITE_API_PORT=${options.apiPort}`,
|
|
468
|
-
`STARCITE_DB_PORT=${options.dbPort}`
|
|
469
|
-
];
|
|
470
|
-
if (options.image?.trim()) {
|
|
471
|
-
envLines.push(`STARCITE_IMAGE=${options.image.trim()}`);
|
|
220
|
+
};
|
|
221
|
+
var defaultStdout = {
|
|
222
|
+
write(message) {
|
|
223
|
+
process.stdout.write(message);
|
|
472
224
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
225
|
+
};
|
|
226
|
+
function trimString2(value) {
|
|
227
|
+
const trimmed = value?.trim();
|
|
228
|
+
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
476
229
|
}
|
|
477
|
-
function
|
|
230
|
+
function parseArgs(spec, argv, stopAtPositional = false) {
|
|
478
231
|
try {
|
|
479
|
-
return
|
|
232
|
+
return arg(spec, {
|
|
233
|
+
argv,
|
|
234
|
+
permissive: false,
|
|
235
|
+
stopAtPositional
|
|
236
|
+
});
|
|
480
237
|
} catch (error) {
|
|
481
238
|
if (error instanceof Error) {
|
|
482
|
-
throw new
|
|
239
|
+
throw new CliUsageError(error.message);
|
|
483
240
|
}
|
|
484
241
|
throw error;
|
|
485
242
|
}
|
|
486
243
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
await ensureDockerReady(logger, runCommand);
|
|
490
|
-
const confirmed = options.yes ? true : await prompt.confirm(
|
|
491
|
-
"Are you sure you want to create the docker containers?",
|
|
492
|
-
true
|
|
493
|
-
);
|
|
494
|
-
if (!confirmed) {
|
|
495
|
-
logger.info("Cancelled.");
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const apiPort = await selectApiPort(baseUrl, options, prompt);
|
|
499
|
-
const dbPort = options.dbPort ?? DEFAULT_DB_PORT;
|
|
500
|
-
const composeDirectory = await writeComposeFiles(store, {
|
|
501
|
-
apiPort,
|
|
502
|
-
dbPort,
|
|
503
|
-
image: options.image
|
|
504
|
-
});
|
|
505
|
-
const result = await runCommand("docker", ["compose", "up", "-d"], {
|
|
506
|
-
cwd: composeDirectory
|
|
507
|
-
});
|
|
508
|
-
if (result.code !== 0) {
|
|
509
|
-
const message = result.stderr || result.stdout || "docker compose up failed";
|
|
510
|
-
throw new Error(message.trim());
|
|
511
|
-
}
|
|
512
|
-
logger.info(`Starcite is starting on http://localhost:${apiPort}`);
|
|
513
|
-
logger.info(`Compose files are in ${composeDirectory}`);
|
|
244
|
+
function resolveConfiguredBaseUrl(config, options) {
|
|
245
|
+
return trimString2(options.baseUrl) ?? trimString2(process.env.STARCITE_BASE_URL) ?? trimString2(config.baseUrl) ?? `http://localhost:${DEFAULT_API_PORT}`;
|
|
514
246
|
}
|
|
515
|
-
async function runDownWizard(input) {
|
|
516
|
-
const { logger, options, prompt, runCommand, store } = input;
|
|
517
|
-
await ensureDockerReady(logger, runCommand);
|
|
518
|
-
const hasRuntimeDirectory = await runtimeDirectoryExists(store);
|
|
519
|
-
if (!hasRuntimeDirectory) {
|
|
520
|
-
logger.info(
|
|
521
|
-
`No Starcite runtime found at ${runtimeDirectory(
|
|
522
|
-
store
|
|
523
|
-
)}. Nothing to tear down.`
|
|
524
|
-
);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
const removeVolumes = options.volumes ?? true;
|
|
528
|
-
const confirmed = options.yes ? true : await prompt.confirm(
|
|
529
|
-
removeVolumes ? "Are you sure you want to stop and delete Starcite containers and volumes?" : "Are you sure you want to stop Starcite containers?",
|
|
530
|
-
false
|
|
531
|
-
);
|
|
532
|
-
if (!confirmed) {
|
|
533
|
-
logger.info("Cancelled.");
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
const args = ["compose", "down", "--remove-orphans"];
|
|
537
|
-
if (removeVolumes) {
|
|
538
|
-
args.push("-v");
|
|
539
|
-
}
|
|
540
|
-
const result = await runCommand("docker", args, {
|
|
541
|
-
cwd: runtimeDirectory(store)
|
|
542
|
-
});
|
|
543
|
-
if (result.code !== 0) {
|
|
544
|
-
const message = result.stderr || result.stdout || "docker compose down failed";
|
|
545
|
-
throw new Error(message.trim());
|
|
546
|
-
}
|
|
547
|
-
logger.info("Starcite containers stopped.");
|
|
548
|
-
if (removeVolumes) {
|
|
549
|
-
logger.info("Starcite volumes removed.");
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
function createDefaultPrompt() {
|
|
553
|
-
const assertInteractive = () => {
|
|
554
|
-
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
555
|
-
throw new Error(
|
|
556
|
-
"Interactive mode requires a TTY. Re-run with explicit options (for example: --yes, --endpoint, --api-key)."
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
return {
|
|
561
|
-
async confirm(message, defaultValue = true) {
|
|
562
|
-
assertInteractive();
|
|
563
|
-
const answer = await clackConfirm({
|
|
564
|
-
message,
|
|
565
|
-
initialValue: defaultValue,
|
|
566
|
-
input: process.stdin,
|
|
567
|
-
output: process.stdout
|
|
568
|
-
});
|
|
569
|
-
if (isCancel(answer)) {
|
|
570
|
-
clackCancel("Cancelled.");
|
|
571
|
-
return false;
|
|
572
|
-
}
|
|
573
|
-
return answer;
|
|
574
|
-
},
|
|
575
|
-
async input(message, defaultValue = "") {
|
|
576
|
-
assertInteractive();
|
|
577
|
-
const answer = await clackText({
|
|
578
|
-
message,
|
|
579
|
-
defaultValue,
|
|
580
|
-
placeholder: defaultValue || void 0,
|
|
581
|
-
input: process.stdin,
|
|
582
|
-
output: process.stdout
|
|
583
|
-
});
|
|
584
|
-
if (isCancel(answer)) {
|
|
585
|
-
clackCancel("Cancelled.");
|
|
586
|
-
throw new Error("Cancelled.");
|
|
587
|
-
}
|
|
588
|
-
const normalized = answer.trim();
|
|
589
|
-
return normalized || defaultValue;
|
|
590
|
-
},
|
|
591
|
-
async password(message) {
|
|
592
|
-
assertInteractive();
|
|
593
|
-
const answer = await clackPassword({
|
|
594
|
-
message,
|
|
595
|
-
input: process.stdin,
|
|
596
|
-
output: process.stdout
|
|
597
|
-
});
|
|
598
|
-
if (isCancel(answer)) {
|
|
599
|
-
clackCancel("Cancelled.");
|
|
600
|
-
throw new Error("Cancelled.");
|
|
601
|
-
}
|
|
602
|
-
return answer.trim();
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
var defaultCommandRunner = (command, args, options) => new Promise((resolve2) => {
|
|
607
|
-
const child = spawn(command, args, {
|
|
608
|
-
cwd: options?.cwd,
|
|
609
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
610
|
-
});
|
|
611
|
-
let stdout = "";
|
|
612
|
-
let stderr = "";
|
|
613
|
-
child.stdout?.on("data", (chunk) => {
|
|
614
|
-
stdout += chunk.toString();
|
|
615
|
-
});
|
|
616
|
-
child.stderr?.on("data", (chunk) => {
|
|
617
|
-
stderr += chunk.toString();
|
|
618
|
-
});
|
|
619
|
-
child.on("error", (error) => {
|
|
620
|
-
const message = error.message || "command failed to start";
|
|
621
|
-
resolve2({ code: 127, stdout, stderr: `${stderr}${message}` });
|
|
622
|
-
});
|
|
623
|
-
child.on("close", (code) => {
|
|
624
|
-
resolve2({ code: code ?? 1, stdout, stderr });
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
// src/cli.ts
|
|
629
|
-
var defaultLogger = createConsola();
|
|
630
|
-
var cliVersion = package_default.version;
|
|
631
|
-
var nonNegativeIntegerSchema = z2.coerce.number().int().nonnegative();
|
|
632
|
-
var positiveIntegerSchema = z2.coerce.number().int().positive();
|
|
633
|
-
var jsonObjectSchema = z2.record(z2.unknown());
|
|
634
|
-
var GlobalOptionsSchema = z2.object({
|
|
635
|
-
baseUrl: z2.string().optional(),
|
|
636
|
-
configDir: z2.string().optional(),
|
|
637
|
-
token: z2.string().optional(),
|
|
638
|
-
json: z2.boolean().optional().default(false)
|
|
639
|
-
});
|
|
640
|
-
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
641
|
-
var DEFAULT_TAIL_BATCH_SIZE = 256;
|
|
642
|
-
var DEFAULT_CREATE_AGENT_ID = "starcite-cli";
|
|
643
247
|
function parseNonNegativeInteger(value, optionName) {
|
|
644
|
-
const parsed =
|
|
645
|
-
if (!parsed
|
|
646
|
-
throw new
|
|
647
|
-
`${optionName} must be a non-negative integer`
|
|
648
|
-
);
|
|
248
|
+
const parsed = Number(value);
|
|
249
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
250
|
+
throw new CliUsageError(`${optionName} must be a non-negative integer`);
|
|
649
251
|
}
|
|
650
|
-
return parsed
|
|
252
|
+
return parsed;
|
|
651
253
|
}
|
|
652
254
|
function parsePositiveInteger(value, optionName) {
|
|
653
|
-
const parsed =
|
|
654
|
-
if (!parsed
|
|
655
|
-
throw new
|
|
255
|
+
const parsed = Number(value);
|
|
256
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
257
|
+
throw new CliUsageError(`${optionName} must be a positive integer`);
|
|
656
258
|
}
|
|
657
|
-
return parsed
|
|
658
|
-
}
|
|
659
|
-
function parsePort2(value, optionName) {
|
|
660
|
-
return parsePortOption(value, optionName);
|
|
259
|
+
return parsed;
|
|
661
260
|
}
|
|
662
261
|
function parseEndpoint(value, optionName) {
|
|
663
262
|
const endpoint = trimString2(value);
|
|
664
263
|
if (!endpoint) {
|
|
665
|
-
throw new
|
|
264
|
+
throw new CliUsageError(`${optionName} cannot be empty`);
|
|
666
265
|
}
|
|
667
266
|
let parsed;
|
|
668
267
|
try {
|
|
669
268
|
parsed = new URL(endpoint);
|
|
670
269
|
} catch {
|
|
671
|
-
throw new
|
|
270
|
+
throw new CliUsageError(`${optionName} must be a valid URL`);
|
|
672
271
|
}
|
|
673
272
|
if (!(parsed.protocol === "http:" || parsed.protocol === "https:")) {
|
|
674
|
-
throw new
|
|
675
|
-
`${optionName} must use http:// or https://`
|
|
676
|
-
);
|
|
273
|
+
throw new CliUsageError(`${optionName} must use http:// or https://`);
|
|
677
274
|
}
|
|
678
|
-
return endpoint.replace(
|
|
275
|
+
return endpoint.replace(TRAILING_SLASHES_REGEX2, "");
|
|
679
276
|
}
|
|
680
277
|
function parseConfigSetKey(value) {
|
|
681
278
|
const normalized = value.trim().toLowerCase();
|
|
682
|
-
if (
|
|
279
|
+
if (normalized === "endpoint" || normalized === "base-url") {
|
|
683
280
|
return "endpoint";
|
|
684
281
|
}
|
|
685
|
-
if (
|
|
686
|
-
return "producer-id";
|
|
687
|
-
}
|
|
688
|
-
if (["api-key", "api_key"].includes(normalized)) {
|
|
282
|
+
if (normalized === "api-key") {
|
|
689
283
|
return "api-key";
|
|
690
284
|
}
|
|
691
|
-
throw new
|
|
692
|
-
"config key must be one of: endpoint, producer-id, api-key"
|
|
693
|
-
);
|
|
285
|
+
throw new CliUsageError("config key must be one of: endpoint, api-key");
|
|
694
286
|
}
|
|
695
287
|
function parseJsonObject(value, optionName) {
|
|
696
288
|
let parsed;
|
|
697
289
|
try {
|
|
698
290
|
parsed = JSON.parse(value);
|
|
699
291
|
} catch {
|
|
700
|
-
throw new
|
|
292
|
+
throw new CliUsageError(`${optionName} must be valid JSON`);
|
|
701
293
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
throw new InvalidArgumentError(`${optionName} must be a JSON object`);
|
|
294
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
295
|
+
throw new CliUsageError(`${optionName} must be a JSON object`);
|
|
705
296
|
}
|
|
706
|
-
return
|
|
297
|
+
return parsed;
|
|
707
298
|
}
|
|
708
299
|
function parseSessionMetadataFilters(value) {
|
|
709
|
-
const parsed = parseJsonObject(value, "--metadata");
|
|
710
300
|
const filters = {};
|
|
711
|
-
for (const [key, rawValue] of Object.entries(
|
|
301
|
+
for (const [key, rawValue] of Object.entries(
|
|
302
|
+
parseJsonObject(value, "--metadata")
|
|
303
|
+
)) {
|
|
712
304
|
if (key.trim().length === 0) {
|
|
713
|
-
throw new
|
|
305
|
+
throw new CliUsageError("--metadata keys must be non-empty");
|
|
714
306
|
}
|
|
715
307
|
if (typeof rawValue !== "string") {
|
|
716
|
-
throw new
|
|
308
|
+
throw new CliUsageError("--metadata values must be strings");
|
|
717
309
|
}
|
|
718
310
|
filters[key] = rawValue;
|
|
719
311
|
}
|
|
720
312
|
return filters;
|
|
721
313
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
function trimString2(value) {
|
|
731
|
-
const trimmed = value?.trim();
|
|
732
|
-
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
733
|
-
}
|
|
734
|
-
function resolveBaseUrl(config, options) {
|
|
735
|
-
const defaultBaseUrl = `http://localhost:${DEFAULT_API_PORT}`;
|
|
736
|
-
return trimString2(options.baseUrl) ?? trimString2(process.env.STARCITE_BASE_URL) ?? trimString2(config.baseUrl) ?? defaultBaseUrl;
|
|
737
|
-
}
|
|
738
|
-
async function resolveGlobalOptions(command) {
|
|
739
|
-
const options = getGlobalOptions(command);
|
|
740
|
-
const configDir = resolveConfigDir(options.configDir);
|
|
741
|
-
const store = new StarciteCliStore(configDir);
|
|
742
|
-
const config = await store.readConfig();
|
|
743
|
-
const apiKey = trimString2(options.token) ?? await store.readApiKey();
|
|
744
|
-
return {
|
|
745
|
-
baseUrl: resolveBaseUrl(config, options),
|
|
746
|
-
apiKey,
|
|
747
|
-
json: options.json,
|
|
748
|
-
store
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
function formatTailEvent(event) {
|
|
752
|
-
const actorLabel = event.actor.startsWith("agent:") ? event.actor.slice("agent:".length) : event.actor;
|
|
753
|
-
const maybeText = event.payload?.text;
|
|
754
|
-
if (typeof maybeText === "string") {
|
|
755
|
-
return `[${actorLabel}] ${maybeText}`;
|
|
756
|
-
}
|
|
757
|
-
return `[${actorLabel}] ${JSON.stringify(event.payload)}`;
|
|
758
|
-
}
|
|
759
|
-
function parseJwtClaims(token) {
|
|
760
|
-
const parts = token.split(".");
|
|
761
|
-
if (parts.length < 2) {
|
|
762
|
-
return void 0;
|
|
314
|
+
var CliRuntime = class {
|
|
315
|
+
logger;
|
|
316
|
+
stdout;
|
|
317
|
+
createClient;
|
|
318
|
+
constructor(deps = {}) {
|
|
319
|
+
this.logger = deps.logger ?? defaultLogger;
|
|
320
|
+
this.stdout = deps.stdout ?? defaultStdout;
|
|
321
|
+
this.createClient = deps.createClient;
|
|
763
322
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
323
|
+
async resolveGlobalOptions(options) {
|
|
324
|
+
const config = new StarciteCliConfigStore(
|
|
325
|
+
resolveConfigDir(options.configDir)
|
|
326
|
+
);
|
|
327
|
+
const store = new StarciteCliStore(config.directory);
|
|
328
|
+
const baseUrl = resolveConfiguredBaseUrl(
|
|
329
|
+
await config.readConfig(),
|
|
330
|
+
options
|
|
331
|
+
);
|
|
332
|
+
const apiKey = trimString2(options.token) ?? await config.readApiKey();
|
|
333
|
+
const client = this.createClient?.(baseUrl, apiKey, store) ?? new Starcite({
|
|
334
|
+
baseUrl,
|
|
335
|
+
apiKey,
|
|
336
|
+
store: store.sessionStore(baseUrl)
|
|
337
|
+
});
|
|
338
|
+
return {
|
|
339
|
+
baseUrl,
|
|
340
|
+
json: options.json,
|
|
341
|
+
config,
|
|
342
|
+
store,
|
|
343
|
+
client
|
|
344
|
+
};
|
|
767
345
|
}
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
346
|
+
writeJsonOutput(value, pretty = false) {
|
|
347
|
+
const serialized = JSON.stringify(value, null, pretty ? 2 : void 0);
|
|
348
|
+
if (serialized === void 0) {
|
|
349
|
+
throw new Error("Failed to serialize JSON output");
|
|
350
|
+
}
|
|
351
|
+
this.stdout.write(`${serialized}
|
|
352
|
+
`);
|
|
774
353
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
return
|
|
354
|
+
formatTailEvent(event) {
|
|
355
|
+
const actor = event.actor.startsWith("agent:") ? event.actor.slice("agent:".length) : event.actor;
|
|
356
|
+
const text = event.payload?.text;
|
|
357
|
+
return typeof text === "string" ? `[${actor}] ${text}` : `[${actor}] ${JSON.stringify(event.payload)}`;
|
|
779
358
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/commands/append.ts
|
|
362
|
+
var DEFAULT_CREATE_AGENT_ID = "starcite-cli";
|
|
363
|
+
function resolveAppendIdentity(selection, client) {
|
|
364
|
+
if (!selection) {
|
|
365
|
+
return client.agent({ id: DEFAULT_CREATE_AGENT_ID });
|
|
366
|
+
}
|
|
367
|
+
return selection.type === "agent" ? client.agent({ id: selection.id }) : client.user({ id: selection.id });
|
|
368
|
+
}
|
|
369
|
+
async function runAppendCommand(args, globalOptions, runtime) {
|
|
370
|
+
const parsed = parseArgs(
|
|
371
|
+
{
|
|
372
|
+
"--agent": String,
|
|
373
|
+
"--user": String,
|
|
374
|
+
"--text": String,
|
|
375
|
+
"--type": String,
|
|
376
|
+
"--source": String,
|
|
377
|
+
"--payload": String,
|
|
378
|
+
"--metadata": String,
|
|
379
|
+
"--refs": String,
|
|
380
|
+
"--idempotency-key": String,
|
|
381
|
+
"--expected-seq": String
|
|
382
|
+
},
|
|
383
|
+
args
|
|
384
|
+
);
|
|
385
|
+
const sessionId = `${parsed._[0] ?? ""}`;
|
|
386
|
+
if (!sessionId) {
|
|
387
|
+
throw new CliUsageError("append requires <sessionId>");
|
|
388
|
+
}
|
|
389
|
+
const agent = trimString2(parsed["--agent"]);
|
|
390
|
+
const user = trimString2(parsed["--user"]);
|
|
391
|
+
const text = trimString2(parsed["--text"]);
|
|
392
|
+
const payload = parsed["--payload"] ? parseJsonObject(parsed["--payload"], "--payload") : void 0;
|
|
393
|
+
if (agent && user) {
|
|
394
|
+
throw new CliUsageError("Choose either --agent or --user, not both");
|
|
395
|
+
}
|
|
396
|
+
if (text && payload) {
|
|
397
|
+
throw new CliUsageError("Choose either --text or --payload, not both");
|
|
398
|
+
}
|
|
399
|
+
if (!(text || payload)) {
|
|
400
|
+
throw new CliUsageError("append requires either --text or --payload");
|
|
401
|
+
}
|
|
402
|
+
let identity;
|
|
403
|
+
if (agent) {
|
|
404
|
+
identity = { type: "agent", id: agent };
|
|
405
|
+
} else if (user) {
|
|
406
|
+
identity = { type: "user", id: user };
|
|
407
|
+
}
|
|
408
|
+
const resolved = await runtime.resolveGlobalOptions(globalOptions);
|
|
409
|
+
const session = await resolved.client.session({
|
|
410
|
+
identity: resolveAppendIdentity(identity, resolved.client),
|
|
411
|
+
id: sessionId
|
|
412
|
+
});
|
|
413
|
+
const response = await session.append({
|
|
414
|
+
type: parsed["--type"] ?? "content",
|
|
415
|
+
payload: payload ?? { text },
|
|
416
|
+
source: parsed["--source"],
|
|
417
|
+
metadata: parsed["--metadata"] ? parseJsonObject(parsed["--metadata"], "--metadata") : void 0,
|
|
418
|
+
refs: parsed["--refs"] ? parseJsonObject(parsed["--refs"], "--refs") : void 0,
|
|
419
|
+
idempotencyKey: parsed["--idempotency-key"],
|
|
420
|
+
expectedSeq: parsed["--expected-seq"] ? parseNonNegativeInteger(parsed["--expected-seq"], "--expected-seq") : void 0
|
|
421
|
+
});
|
|
422
|
+
if (resolved.json) {
|
|
423
|
+
runtime.writeJsonOutput(response, true);
|
|
424
|
+
return;
|
|
784
425
|
}
|
|
785
|
-
|
|
786
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
426
|
+
runtime.logger.info(`seq=${response.seq} deduped=${response.deduped}`);
|
|
787
427
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
428
|
+
|
|
429
|
+
// src/commands/config.ts
|
|
430
|
+
async function runConfigCommand(args, globalOptions, runtime) {
|
|
431
|
+
const action = args[0];
|
|
432
|
+
const config = new StarciteCliConfigStore(
|
|
433
|
+
resolveConfigDir(globalOptions.configDir)
|
|
434
|
+
);
|
|
435
|
+
if (action === "set") {
|
|
436
|
+
const key = args[1];
|
|
437
|
+
const value = args[2];
|
|
438
|
+
if (!(key && value)) {
|
|
439
|
+
throw new CliUsageError("config set requires <key> and <value>");
|
|
800
440
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
scopes.add(scope);
|
|
807
|
-
}
|
|
441
|
+
if (parseConfigSetKey(key) === "endpoint") {
|
|
442
|
+
const endpoint = parseEndpoint(value, "endpoint");
|
|
443
|
+
await config.updateConfig({ baseUrl: endpoint });
|
|
444
|
+
runtime.logger.info(`Endpoint set to ${endpoint}`);
|
|
445
|
+
return;
|
|
808
446
|
}
|
|
447
|
+
await config.saveApiKey(value);
|
|
448
|
+
await config.updateConfig({ apiKey: void 0 });
|
|
449
|
+
runtime.logger.info("API key saved.");
|
|
450
|
+
return;
|
|
809
451
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (highLevelMode && rawMode) {
|
|
820
|
-
throw new InvalidArgumentError(
|
|
821
|
-
"Choose either high-level mode (--agent and --text) or raw mode (--actor and --payload), not both"
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
|
-
if (highLevelMode) {
|
|
825
|
-
const agent = trimString2(options.agent);
|
|
826
|
-
const text = trimString2(options.text);
|
|
827
|
-
if (!(agent && text)) {
|
|
828
|
-
throw new InvalidArgumentError(
|
|
829
|
-
"--agent and --text are required for high-level append mode"
|
|
830
|
-
);
|
|
452
|
+
if (action === "show") {
|
|
453
|
+
const fileConfig = await config.readConfig();
|
|
454
|
+
const apiKey = await config.readApiKey();
|
|
455
|
+
const fromEnv = trimString2(process.env.STARCITE_API_KEY);
|
|
456
|
+
let apiKeySource = "unset";
|
|
457
|
+
if (fromEnv) {
|
|
458
|
+
apiKeySource = "env";
|
|
459
|
+
} else if (apiKey) {
|
|
460
|
+
apiKeySource = "stored";
|
|
831
461
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
462
|
+
const output = {
|
|
463
|
+
endpoint: resolveConfiguredBaseUrl(fileConfig, globalOptions),
|
|
464
|
+
apiKey: apiKey ? "***" : null,
|
|
465
|
+
apiKeySource,
|
|
466
|
+
configDir: config.directory
|
|
467
|
+
};
|
|
468
|
+
if (globalOptions.json) {
|
|
469
|
+
runtime.writeJsonOutput(output, true);
|
|
470
|
+
return;
|
|
841
471
|
}
|
|
842
|
-
|
|
472
|
+
runtime.logger.info(JSON.stringify(output, null, 2));
|
|
473
|
+
return;
|
|
843
474
|
}
|
|
844
|
-
throw new
|
|
845
|
-
"append requires either high-level mode (--agent and --text) or raw mode (--actor and --payload)"
|
|
846
|
-
);
|
|
475
|
+
throw new CliUsageError("config requires `set` or `show`");
|
|
847
476
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
477
|
+
|
|
478
|
+
// src/commands/create.ts
|
|
479
|
+
var DEFAULT_CREATE_AGENT_ID2 = "starcite-cli";
|
|
480
|
+
async function runCreateCommand(args, globalOptions, runtime) {
|
|
481
|
+
const parsed = parseArgs(
|
|
482
|
+
{
|
|
483
|
+
"--id": String,
|
|
484
|
+
"--title": String,
|
|
485
|
+
"--metadata": String
|
|
486
|
+
},
|
|
487
|
+
args
|
|
488
|
+
);
|
|
489
|
+
const metadata = parsed["--metadata"] ? parseJsonObject(parsed["--metadata"], "--metadata") : void 0;
|
|
490
|
+
const resolved = await runtime.resolveGlobalOptions(globalOptions);
|
|
491
|
+
const session = await resolved.client.session({
|
|
492
|
+
identity: resolved.client.agent({ id: DEFAULT_CREATE_AGENT_ID2 }),
|
|
493
|
+
id: parsed["--id"],
|
|
494
|
+
title: parsed["--title"],
|
|
495
|
+
metadata
|
|
496
|
+
});
|
|
497
|
+
if (resolved.json) {
|
|
498
|
+
runtime.writeJsonOutput(session.record ?? { id: session.id }, true);
|
|
499
|
+
return;
|
|
852
500
|
}
|
|
853
|
-
|
|
854
|
-
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
501
|
+
runtime.logger.info(session.id);
|
|
855
502
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
503
|
+
|
|
504
|
+
// src/commands/sessions.ts
|
|
505
|
+
async function runSessionsCommand(args, globalOptions, runtime) {
|
|
506
|
+
const parsed = parseArgs(
|
|
507
|
+
{
|
|
508
|
+
"--limit": String,
|
|
509
|
+
"--cursor": String,
|
|
510
|
+
"--metadata": String
|
|
511
|
+
},
|
|
512
|
+
args
|
|
513
|
+
);
|
|
514
|
+
if (`${parsed._[0] ?? ""}` !== "list") {
|
|
515
|
+
throw new CliUsageError("sessions requires `list`");
|
|
516
|
+
}
|
|
517
|
+
const cursor = trimString2(parsed["--cursor"]);
|
|
518
|
+
if (parsed["--cursor"] !== void 0 && !cursor) {
|
|
519
|
+
throw new CliUsageError("--cursor must be non-empty");
|
|
520
|
+
}
|
|
521
|
+
const resolved = await runtime.resolveGlobalOptions(globalOptions);
|
|
522
|
+
const page = await resolved.client.listSessions({
|
|
523
|
+
limit: parsed["--limit"] ? parsePositiveInteger(parsed["--limit"], "--limit") : void 0,
|
|
524
|
+
cursor,
|
|
525
|
+
metadata: parsed["--metadata"] ? parseSessionMetadataFilters(parsed["--metadata"]) : void 0
|
|
526
|
+
});
|
|
527
|
+
runtime.logger.error(
|
|
528
|
+
"Warning: `sessions list` is a bad call to use in production."
|
|
529
|
+
);
|
|
530
|
+
if (resolved.json) {
|
|
531
|
+
runtime.writeJsonOutput(page, true);
|
|
532
|
+
return;
|
|
861
533
|
}
|
|
862
|
-
if (
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
id: sessionId
|
|
866
|
-
});
|
|
534
|
+
if (page.sessions.length === 0) {
|
|
535
|
+
runtime.logger.info("No sessions found.");
|
|
536
|
+
return;
|
|
867
537
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
538
|
+
runtime.logger.info("id title created_at");
|
|
539
|
+
for (const session of page.sessions) {
|
|
540
|
+
runtime.logger.info(
|
|
541
|
+
`${session.id} ${session.title ?? ""} ${session.created_at}`
|
|
872
542
|
);
|
|
873
543
|
}
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
function resolveCreateIdentity(apiKey, agentId = DEFAULT_CREATE_AGENT_ID) {
|
|
877
|
-
const tenantId = tokenTenantId(apiKey);
|
|
878
|
-
if (!tenantId) {
|
|
879
|
-
throw new InvalidArgumentError(
|
|
880
|
-
"session identity binding requires an API key with tenant_id claims"
|
|
881
|
-
);
|
|
544
|
+
if (page.next_cursor) {
|
|
545
|
+
runtime.logger.info(`next_cursor=${page.next_cursor}`);
|
|
882
546
|
}
|
|
883
|
-
return new StarciteIdentity({
|
|
884
|
-
tenantId,
|
|
885
|
-
id: agentId,
|
|
886
|
-
type: "agent"
|
|
887
|
-
});
|
|
888
547
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
throw new InvalidArgumentError("producer-id cannot be empty");
|
|
930
|
-
}
|
|
931
|
-
await store.updateConfig({ producerId });
|
|
932
|
-
logger.info(`Producer ID set to ${producerId}`);
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
await store.saveApiKey(value);
|
|
936
|
-
await store.updateConfig({ apiKey: void 0 });
|
|
937
|
-
logger.info("API key saved.");
|
|
938
|
-
})
|
|
939
|
-
).addCommand(
|
|
940
|
-
new Command("show").description("Show current configuration").action(async function() {
|
|
941
|
-
const { baseUrl, store } = await resolveGlobalOptions(this);
|
|
942
|
-
const config = await store.readConfig();
|
|
943
|
-
const apiKey = await store.readApiKey();
|
|
944
|
-
const fromEnv = trimString2(process.env.STARCITE_API_KEY);
|
|
945
|
-
let apiKeySource = "unset";
|
|
946
|
-
if (fromEnv) {
|
|
947
|
-
apiKeySource = "env";
|
|
948
|
-
} else if (apiKey) {
|
|
949
|
-
apiKeySource = "stored";
|
|
950
|
-
}
|
|
951
|
-
logger.info(
|
|
952
|
-
JSON.stringify(
|
|
953
|
-
{
|
|
954
|
-
endpoint: config.baseUrl ?? baseUrl,
|
|
955
|
-
producerId: config.producerId ?? null,
|
|
956
|
-
apiKey: apiKey ? "***" : null,
|
|
957
|
-
apiKeySource,
|
|
958
|
-
configDir: store.directory
|
|
959
|
-
},
|
|
960
|
-
null,
|
|
961
|
-
2
|
|
962
|
-
)
|
|
963
|
-
);
|
|
964
|
-
})
|
|
965
|
-
);
|
|
966
|
-
program.command("sessions").description("Manage sessions").addCommand(
|
|
967
|
-
new Command("list").description("List sessions").option(
|
|
968
|
-
"--limit <count>",
|
|
969
|
-
"Maximum sessions to return",
|
|
970
|
-
(value) => parsePositiveInteger(value, "--limit")
|
|
971
|
-
).option("--cursor <cursor>", "Pagination cursor").option("--metadata <json>", "Metadata filter JSON object").action(async function(options) {
|
|
972
|
-
const resolved = await resolveGlobalOptions(this);
|
|
973
|
-
const { json } = resolved;
|
|
974
|
-
const client = resolved.apiKey ? createClient(resolved.baseUrl, resolved.apiKey) : createClient(resolved.baseUrl);
|
|
975
|
-
const metadata = options.metadata ? parseSessionMetadataFilters(options.metadata) : void 0;
|
|
976
|
-
const cursor = options.cursor?.trim();
|
|
977
|
-
if (options.cursor !== void 0 && !cursor) {
|
|
978
|
-
throw new InvalidArgumentError("--cursor must be non-empty");
|
|
979
|
-
}
|
|
980
|
-
const page = await client.listSessions({
|
|
981
|
-
limit: options.limit,
|
|
548
|
+
|
|
549
|
+
// src/commands/tail.ts
|
|
550
|
+
import {
|
|
551
|
+
SessionLogConflictError,
|
|
552
|
+
SessionLogGapError
|
|
553
|
+
} from "@starcite/sdk";
|
|
554
|
+
var DEFAULT_CREATE_AGENT_ID3 = "starcite-cli";
|
|
555
|
+
async function runTailCommand(args, globalOptions, runtime) {
|
|
556
|
+
const parsed = parseArgs(
|
|
557
|
+
{
|
|
558
|
+
"--cursor": String,
|
|
559
|
+
"--agent": String,
|
|
560
|
+
"--limit": String,
|
|
561
|
+
"--no-follow": Boolean
|
|
562
|
+
},
|
|
563
|
+
args
|
|
564
|
+
);
|
|
565
|
+
const sessionId = `${parsed._[0] ?? ""}`;
|
|
566
|
+
if (!sessionId) {
|
|
567
|
+
throw new CliUsageError("tail requires <sessionId>");
|
|
568
|
+
}
|
|
569
|
+
const resolved = await runtime.resolveGlobalOptions(globalOptions);
|
|
570
|
+
const abortController = new AbortController();
|
|
571
|
+
const onSigint = () => {
|
|
572
|
+
abortController.abort();
|
|
573
|
+
};
|
|
574
|
+
const cursor = parsed["--cursor"] ? parseNonNegativeInteger(parsed["--cursor"], "--cursor") : 0;
|
|
575
|
+
const limit = parsed["--limit"] ? parseNonNegativeInteger(parsed["--limit"], "--limit") : void 0;
|
|
576
|
+
process.once("SIGINT", onSigint);
|
|
577
|
+
try {
|
|
578
|
+
let retriedAfterStoreReset = false;
|
|
579
|
+
while (true) {
|
|
580
|
+
const session = await resolved.client.session({
|
|
581
|
+
identity: resolved.client.agent({ id: DEFAULT_CREATE_AGENT_ID3 }),
|
|
582
|
+
id: sessionId
|
|
583
|
+
});
|
|
584
|
+
try {
|
|
585
|
+
await emitTailEvents({
|
|
586
|
+
session,
|
|
587
|
+
agent: parsed["--agent"],
|
|
982
588
|
cursor,
|
|
983
|
-
|
|
589
|
+
follow: parsed["--no-follow"] !== true,
|
|
590
|
+
limit,
|
|
591
|
+
json: resolved.json,
|
|
592
|
+
runtime,
|
|
593
|
+
signal: abortController.signal
|
|
984
594
|
});
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
logger.info("No sessions found.");
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
logger.info("id title created_at");
|
|
994
|
-
for (const session of page.sessions) {
|
|
995
|
-
logger.info(
|
|
996
|
-
`${session.id} ${session.title ?? ""} ${session.created_at}`
|
|
997
|
-
);
|
|
998
|
-
}
|
|
999
|
-
if (page.next_cursor) {
|
|
1000
|
-
logger.info(`next_cursor=${page.next_cursor}`);
|
|
595
|
+
return;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
const isStaleStoreConflict = error instanceof SessionLogConflictError || error instanceof SessionLogGapError;
|
|
598
|
+
if (!(isStaleStoreConflict && !retriedAfterStoreReset)) {
|
|
599
|
+
throw error;
|
|
1001
600
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
"Starcite API port",
|
|
1007
|
-
(value) => parsePort2(value, "--port")
|
|
1008
|
-
).option(
|
|
1009
|
-
"--db-port <port>",
|
|
1010
|
-
"Postgres port",
|
|
1011
|
-
(value) => parsePort2(value, "--db-port")
|
|
1012
|
-
).option("--image <image>", "Override Starcite image").action(async function(options) {
|
|
1013
|
-
const { baseUrl, store } = await resolveGlobalOptions(this);
|
|
1014
|
-
await runUpWizard({
|
|
1015
|
-
baseUrl,
|
|
1016
|
-
logger,
|
|
1017
|
-
options,
|
|
1018
|
-
prompt,
|
|
1019
|
-
runCommand,
|
|
1020
|
-
store
|
|
1021
|
-
});
|
|
1022
|
-
});
|
|
1023
|
-
program.command("down").description("Stop and remove local Starcite services").option("-y, --yes", "Skip confirmation prompt").option("--no-volumes", "Keep Postgres volume data").action(async function(options) {
|
|
1024
|
-
const { store } = await resolveGlobalOptions(this);
|
|
1025
|
-
await runDownWizard({
|
|
1026
|
-
logger,
|
|
1027
|
-
options,
|
|
1028
|
-
prompt,
|
|
1029
|
-
runCommand,
|
|
1030
|
-
store
|
|
1031
|
-
});
|
|
1032
|
-
});
|
|
1033
|
-
program.command("create").description("Create a session").option("--id <id>", "Session ID").option("--title <title>", "Session title").option("--metadata <json>", "Session metadata JSON object").action(async function(options) {
|
|
1034
|
-
const resolved = await resolveGlobalOptions(this);
|
|
1035
|
-
const { json } = resolved;
|
|
1036
|
-
const client = resolved.apiKey ? createClient(resolved.baseUrl, resolved.apiKey) : createClient(resolved.baseUrl);
|
|
1037
|
-
const metadata = options.metadata ? parseJsonObject(options.metadata, "--metadata") : void 0;
|
|
1038
|
-
const session = await client.session({
|
|
1039
|
-
identity: resolveCreateIdentity(resolved.apiKey),
|
|
1040
|
-
id: options.id,
|
|
1041
|
-
title: options.title,
|
|
1042
|
-
metadata
|
|
1043
|
-
});
|
|
1044
|
-
if (json) {
|
|
1045
|
-
logger.info(
|
|
1046
|
-
JSON.stringify(session.record ?? { id: session.id }, null, 2)
|
|
601
|
+
retriedAfterStoreReset = true;
|
|
602
|
+
resolved.store.clearSession(resolved.baseUrl, sessionId);
|
|
603
|
+
runtime.logger.error(
|
|
604
|
+
`Warning: cleared stale local session cache for '${sessionId}' and retried tail.`
|
|
1047
605
|
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} finally {
|
|
609
|
+
process.removeListener("SIGINT", onSigint);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function emitTailEvents({
|
|
613
|
+
session,
|
|
614
|
+
agent,
|
|
615
|
+
cursor,
|
|
616
|
+
follow,
|
|
617
|
+
limit,
|
|
618
|
+
json,
|
|
619
|
+
runtime,
|
|
620
|
+
signal
|
|
621
|
+
}) {
|
|
622
|
+
let emitted = 0;
|
|
623
|
+
for await (const { event } of session.tail({
|
|
624
|
+
cursor,
|
|
625
|
+
batchSize: DEFAULT_TAIL_BATCH_SIZE,
|
|
626
|
+
agent,
|
|
627
|
+
follow,
|
|
628
|
+
signal
|
|
629
|
+
})) {
|
|
630
|
+
if (limit !== void 0 && emitted >= limit) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (json) {
|
|
634
|
+
runtime.writeJsonOutput(event);
|
|
635
|
+
} else {
|
|
636
|
+
runtime.logger.info(runtime.formatTailEvent(event));
|
|
637
|
+
}
|
|
638
|
+
emitted += 1;
|
|
639
|
+
if (limit !== void 0 && emitted >= limit) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/cli.ts
|
|
646
|
+
var HELP_CODE = "commander.helpDisplayed";
|
|
647
|
+
var HELP_TEXT = `Usage: starcite [options] <command>
|
|
648
|
+
|
|
649
|
+
Commands:
|
|
650
|
+
config
|
|
651
|
+
create
|
|
652
|
+
append
|
|
653
|
+
tail
|
|
654
|
+
sessions
|
|
655
|
+
|
|
656
|
+
Options:
|
|
657
|
+
-u, --base-url <url> Starcite API base URL
|
|
658
|
+
-k, --token <token> Starcite API key
|
|
659
|
+
--config-dir <path>
|
|
660
|
+
--json
|
|
661
|
+
-h, --help
|
|
662
|
+
`;
|
|
663
|
+
function normalizeArgv(argv, context) {
|
|
664
|
+
const args = context?.from === "user" ? [...argv] : argv.slice(2);
|
|
665
|
+
if (args[0] === "node" && typeof args[1] === "string" && args[1].includes("starcite")) {
|
|
666
|
+
return args.slice(2);
|
|
667
|
+
}
|
|
668
|
+
return args;
|
|
669
|
+
}
|
|
670
|
+
function parseGlobalArgs(args) {
|
|
671
|
+
const parsed = parseArgs(
|
|
672
|
+
{
|
|
673
|
+
"--base-url": String,
|
|
674
|
+
"-u": "--base-url",
|
|
675
|
+
"--token": String,
|
|
676
|
+
"-k": "--token",
|
|
677
|
+
"--config-dir": String,
|
|
678
|
+
"--json": Boolean,
|
|
679
|
+
"--help": Boolean,
|
|
680
|
+
"-h": "--help"
|
|
681
|
+
},
|
|
682
|
+
args,
|
|
683
|
+
true
|
|
684
|
+
);
|
|
685
|
+
return {
|
|
686
|
+
help: parsed["--help"] === true,
|
|
687
|
+
options: {
|
|
688
|
+
baseUrl: parsed["--base-url"],
|
|
689
|
+
configDir: parsed["--config-dir"],
|
|
690
|
+
token: parsed["--token"],
|
|
691
|
+
json: parsed["--json"] === true
|
|
692
|
+
},
|
|
693
|
+
rest: parsed._.map((value) => `${value}`)
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function helpOutputText() {
|
|
697
|
+
return `${HELP_TEXT}
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
function buildProgram(deps = {}) {
|
|
701
|
+
const runtime = new CliRuntime(deps);
|
|
702
|
+
let shouldThrowOnHelp = false;
|
|
703
|
+
let output = {
|
|
704
|
+
writeOut(text) {
|
|
705
|
+
process.stdout.write(text);
|
|
706
|
+
},
|
|
707
|
+
writeErr(text) {
|
|
708
|
+
process.stderr.write(text);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
return {
|
|
712
|
+
exitOverride() {
|
|
713
|
+
shouldThrowOnHelp = true;
|
|
714
|
+
},
|
|
715
|
+
configureOutput(next) {
|
|
716
|
+
output = {
|
|
717
|
+
writeOut: next.writeOut ?? output.writeOut,
|
|
718
|
+
writeErr: next.writeErr ?? output.writeErr
|
|
719
|
+
};
|
|
720
|
+
},
|
|
721
|
+
async parseAsync(argv, context) {
|
|
722
|
+
const parsed = parseGlobalArgs(normalizeArgv(argv, context));
|
|
723
|
+
const command = parsed.rest[0];
|
|
724
|
+
if (parsed.help || !command) {
|
|
725
|
+
output.writeOut(helpOutputText());
|
|
726
|
+
if (shouldThrowOnHelp) {
|
|
727
|
+
const error = new Error("Help displayed");
|
|
728
|
+
error.code = HELP_CODE;
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
1048
731
|
return;
|
|
1049
732
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
"--producer-id <id>",
|
|
1054
|
-
"Producer identity (auto-generated if omitted)"
|
|
1055
|
-
).option(
|
|
1056
|
-
"--producer-seq <seq>",
|
|
1057
|
-
"Producer sequence (defaults to persisted state, starting at 1)",
|
|
1058
|
-
(value) => parsePositiveInteger(value, "--producer-seq")
|
|
1059
|
-
).option("--actor <actor>", "Raw actor field (raw mode)").option("--payload <json>", "Raw payload JSON object (raw mode)").option("--metadata <json>", "Event metadata JSON object").option("--refs <json>", "Event refs JSON object").option("--idempotency-key <key>", "Idempotency key").option(
|
|
1060
|
-
"--expected-seq <seq>",
|
|
1061
|
-
"Expected sequence",
|
|
1062
|
-
(value) => parseNonNegativeInteger(value, "--expected-seq")
|
|
1063
|
-
).action(async function(sessionId, options) {
|
|
1064
|
-
const { baseUrl, apiKey, json, store } = await resolveGlobalOptions(this);
|
|
1065
|
-
const client = apiKey ? createClient(baseUrl, apiKey) : createClient(baseUrl);
|
|
1066
|
-
const metadata = options.metadata ? parseJsonObject(options.metadata, "--metadata") : void 0;
|
|
1067
|
-
const refs = options.refs ? parseJsonObject(options.refs, "--refs") : void 0;
|
|
1068
|
-
const mode = resolveAppendMode(options);
|
|
1069
|
-
const session = mode.kind === "high-level" && apiKey !== void 0 && shouldAutoIssueSessionToken(apiKey) ? await client.session({
|
|
1070
|
-
identity: resolveCreateIdentity(apiKey, mode.agent),
|
|
1071
|
-
id: sessionId
|
|
1072
|
-
}) : await resolveSession(client, apiKey, sessionId);
|
|
1073
|
-
const response = mode.kind === "high-level" ? await session.append({
|
|
1074
|
-
type: options.type,
|
|
1075
|
-
text: mode.text,
|
|
1076
|
-
source: options.source,
|
|
1077
|
-
metadata,
|
|
1078
|
-
refs,
|
|
1079
|
-
idempotencyKey: options.idempotencyKey,
|
|
1080
|
-
expectedSeq: options.expectedSeq
|
|
1081
|
-
}) : await store.withStateLock(async () => {
|
|
1082
|
-
const producerId = await store.resolveProducerId(
|
|
1083
|
-
options.producerId
|
|
1084
|
-
);
|
|
1085
|
-
const normalizedBaseUrl = toApiBaseUrlForContext(baseUrl);
|
|
1086
|
-
const contextKey = buildSeqContextKey(
|
|
1087
|
-
normalizedBaseUrl,
|
|
1088
|
-
sessionId,
|
|
1089
|
-
producerId
|
|
1090
|
-
);
|
|
1091
|
-
const producerSeq = options.producerSeq ?? await store.readNextSeq(contextKey);
|
|
1092
|
-
const appendResponse = await session.appendRaw({
|
|
1093
|
-
type: options.type,
|
|
1094
|
-
payload: parseJsonObject(mode.payload, "--payload"),
|
|
1095
|
-
actor: mode.actor,
|
|
1096
|
-
producer_id: producerId,
|
|
1097
|
-
producer_seq: producerSeq,
|
|
1098
|
-
source: options.source,
|
|
1099
|
-
metadata,
|
|
1100
|
-
refs,
|
|
1101
|
-
idempotency_key: options.idempotencyKey,
|
|
1102
|
-
expected_seq: options.expectedSeq
|
|
1103
|
-
});
|
|
1104
|
-
await store.bumpNextSeq(contextKey, producerSeq);
|
|
1105
|
-
return appendResponse;
|
|
1106
|
-
});
|
|
1107
|
-
if (json) {
|
|
1108
|
-
logger.info(JSON.stringify(response, null, 2));
|
|
733
|
+
const commandArgs = parsed.rest.slice(1);
|
|
734
|
+
if (command === "config") {
|
|
735
|
+
await runConfigCommand(commandArgs, parsed.options, runtime);
|
|
1109
736
|
return;
|
|
1110
737
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
"--cursor <cursor>",
|
|
1115
|
-
"Replay cursor",
|
|
1116
|
-
(value) => parseNonNegativeInteger(value, "--cursor")
|
|
1117
|
-
).option("--agent <agent>", "Filter by agent name").option(
|
|
1118
|
-
"--limit <count>",
|
|
1119
|
-
"Stop after N events",
|
|
1120
|
-
(value) => parseNonNegativeInteger(value, "--limit")
|
|
1121
|
-
).option("--no-follow", "Exit after replaying stored events").action(async function(sessionId, options) {
|
|
1122
|
-
const { baseUrl, apiKey, json } = await resolveGlobalOptions(this);
|
|
1123
|
-
const client = apiKey ? createClient(baseUrl, apiKey) : createClient(baseUrl);
|
|
1124
|
-
const session = await resolveSession(client, apiKey, sessionId);
|
|
1125
|
-
const abortController = new AbortController();
|
|
1126
|
-
const onSigint = () => {
|
|
1127
|
-
abortController.abort();
|
|
1128
|
-
};
|
|
1129
|
-
process.once("SIGINT", onSigint);
|
|
1130
|
-
try {
|
|
1131
|
-
let emitted = 0;
|
|
1132
|
-
await session.tail(
|
|
1133
|
-
(event) => {
|
|
1134
|
-
if (options.limit !== void 0 && emitted >= options.limit) {
|
|
1135
|
-
abortController.abort();
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
if (json) {
|
|
1139
|
-
logger.info(JSON.stringify(event));
|
|
1140
|
-
} else {
|
|
1141
|
-
logger.info(formatTailEvent(event));
|
|
1142
|
-
}
|
|
1143
|
-
emitted += 1;
|
|
1144
|
-
if (options.limit !== void 0 && emitted >= options.limit) {
|
|
1145
|
-
abortController.abort();
|
|
1146
|
-
}
|
|
1147
|
-
},
|
|
1148
|
-
{
|
|
1149
|
-
cursor: options.cursor ?? 0,
|
|
1150
|
-
batchSize: DEFAULT_TAIL_BATCH_SIZE,
|
|
1151
|
-
agent: options.agent,
|
|
1152
|
-
follow: options.follow,
|
|
1153
|
-
signal: abortController.signal
|
|
1154
|
-
}
|
|
1155
|
-
);
|
|
1156
|
-
} finally {
|
|
1157
|
-
process.removeListener("SIGINT", onSigint);
|
|
738
|
+
if (command === "create") {
|
|
739
|
+
await runCreateCommand(commandArgs, parsed.options, runtime);
|
|
740
|
+
return;
|
|
1158
741
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
}
|
|
1162
|
-
async run(argv = process.argv) {
|
|
1163
|
-
const program = this.buildProgram();
|
|
1164
|
-
try {
|
|
1165
|
-
await program.parseAsync(argv);
|
|
1166
|
-
} catch (error) {
|
|
1167
|
-
if (error instanceof StarciteApiError) {
|
|
1168
|
-
this.logger.error(`${error.code} (${error.status}): ${error.message}`);
|
|
1169
|
-
process.exitCode = 1;
|
|
742
|
+
if (command === "append") {
|
|
743
|
+
await runAppendCommand(commandArgs, parsed.options, runtime);
|
|
1170
744
|
return;
|
|
1171
745
|
}
|
|
1172
|
-
if (
|
|
1173
|
-
|
|
1174
|
-
process.exitCode = 1;
|
|
746
|
+
if (command === "tail") {
|
|
747
|
+
await runTailCommand(commandArgs, parsed.options, runtime);
|
|
1175
748
|
return;
|
|
1176
749
|
}
|
|
1177
|
-
|
|
750
|
+
if (command === "sessions") {
|
|
751
|
+
await runSessionsCommand(commandArgs, parsed.options, runtime);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw new CliUsageError(`Unknown command: ${command}`);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
async function run(argv = process.argv, deps = {}) {
|
|
759
|
+
const runtime = new CliRuntime(deps);
|
|
760
|
+
const program = buildProgram(deps);
|
|
761
|
+
try {
|
|
762
|
+
await program.parseAsync(argv);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
if (error instanceof StarciteApiError) {
|
|
765
|
+
runtime.logger.error(`${error.code} (${error.status}): ${error.message}`);
|
|
1178
766
|
process.exitCode = 1;
|
|
767
|
+
return;
|
|
1179
768
|
}
|
|
769
|
+
if (error instanceof Error) {
|
|
770
|
+
runtime.logger.error(error.message);
|
|
771
|
+
process.exitCode = 1;
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
runtime.logger.error("Unknown error");
|
|
775
|
+
process.exitCode = 1;
|
|
1180
776
|
}
|
|
1181
|
-
};
|
|
1182
|
-
async function run(argv = process.argv, deps = {}) {
|
|
1183
|
-
await new StarciteCliApp(deps).run(argv);
|
|
1184
777
|
}
|
|
1185
778
|
|
|
1186
779
|
// src/index.ts
|