starcite 0.0.3 → 0.0.5
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 +37 -41
- package/dist/index.js +427 -385
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Starcite,
|
|
6
|
+
StarciteApiError,
|
|
7
|
+
StarciteIdentity
|
|
8
8
|
} from "@starcite/sdk";
|
|
9
9
|
import { Command, InvalidArgumentError } from "commander";
|
|
10
10
|
import { createConsola } from "consola";
|
|
@@ -13,7 +13,7 @@ import { z as z2 } from "zod";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "starcite",
|
|
16
|
-
version: "0.0.
|
|
16
|
+
version: "0.0.5",
|
|
17
17
|
description: "CLI for Starcite",
|
|
18
18
|
license: "Apache-2.0",
|
|
19
19
|
homepage: "https://starcite.ai",
|
|
@@ -45,6 +45,7 @@ var package_default = {
|
|
|
45
45
|
dev: "bun run src/index.ts",
|
|
46
46
|
compile: "bun run --cwd ../typescript-sdk build && bun build --compile src/index.ts --outfile dist/starcite",
|
|
47
47
|
test: "vitest run && bun run test:dist",
|
|
48
|
+
"test:live": "vitest run test/live.api.integration.test.ts",
|
|
48
49
|
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
49
50
|
prepublishOnly: "bun run clean && bun run build",
|
|
50
51
|
"publish:dry": "bun publish --dry-run --access public",
|
|
@@ -55,7 +56,7 @@ var package_default = {
|
|
|
55
56
|
},
|
|
56
57
|
dependencies: {
|
|
57
58
|
"@clack/prompts": "^1.0.1",
|
|
58
|
-
"@starcite/sdk": "^0.0.
|
|
59
|
+
"@starcite/sdk": "^0.0.5",
|
|
59
60
|
commander: "^13.1.0",
|
|
60
61
|
conf: "^15.1.0",
|
|
61
62
|
consola: "^3.4.2",
|
|
@@ -630,7 +631,15 @@ var cliVersion = package_default.version;
|
|
|
630
631
|
var nonNegativeIntegerSchema = z2.coerce.number().int().nonnegative();
|
|
631
632
|
var positiveIntegerSchema = z2.coerce.number().int().positive();
|
|
632
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
|
+
});
|
|
633
640
|
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
641
|
+
var DEFAULT_TAIL_BATCH_SIZE = 256;
|
|
642
|
+
var DEFAULT_CREATE_AGENT_ID = "starcite-cli";
|
|
634
643
|
function parseNonNegativeInteger(value, optionName) {
|
|
635
644
|
const parsed = nonNegativeIntegerSchema.safeParse(value);
|
|
636
645
|
if (!parsed.success) {
|
|
@@ -683,27 +692,19 @@ function parseConfigSetKey(value) {
|
|
|
683
692
|
"config key must be one of: endpoint, producer-id, api-key"
|
|
684
693
|
);
|
|
685
694
|
}
|
|
686
|
-
function
|
|
695
|
+
function parseJsonObject(value, optionName) {
|
|
687
696
|
let parsed;
|
|
688
697
|
try {
|
|
689
698
|
parsed = JSON.parse(value);
|
|
690
699
|
} catch {
|
|
691
700
|
throw new InvalidArgumentError(`${optionName} must be valid JSON`);
|
|
692
701
|
}
|
|
693
|
-
const result =
|
|
702
|
+
const result = jsonObjectSchema.safeParse(parsed);
|
|
694
703
|
if (!result.success) {
|
|
695
|
-
throw new InvalidArgumentError(
|
|
696
|
-
`${optionName} must be ${invalidShapeMessage}`
|
|
697
|
-
);
|
|
704
|
+
throw new InvalidArgumentError(`${optionName} must be a JSON object`);
|
|
698
705
|
}
|
|
699
706
|
return result.data;
|
|
700
707
|
}
|
|
701
|
-
function parseJsonObject(value, optionName) {
|
|
702
|
-
return parseJsonOption(value, jsonObjectSchema, optionName, "a JSON object");
|
|
703
|
-
}
|
|
704
|
-
function parseEventRefs(value) {
|
|
705
|
-
return parseJsonObject(value, "--refs");
|
|
706
|
-
}
|
|
707
708
|
function parseSessionMetadataFilters(value) {
|
|
708
709
|
const parsed = parseJsonObject(value, "--metadata");
|
|
709
710
|
const filters = {};
|
|
@@ -719,7 +720,12 @@ function parseSessionMetadataFilters(value) {
|
|
|
719
720
|
return filters;
|
|
720
721
|
}
|
|
721
722
|
function getGlobalOptions(command) {
|
|
722
|
-
|
|
723
|
+
const parsed = GlobalOptionsSchema.safeParse(command.optsWithGlobals());
|
|
724
|
+
if (!parsed.success) {
|
|
725
|
+
const issue = parsed.error.issues[0]?.message ?? "invalid global options";
|
|
726
|
+
throw new InvalidArgumentError(`Failed to parse global options: ${issue}`);
|
|
727
|
+
}
|
|
728
|
+
return parsed.data;
|
|
723
729
|
}
|
|
724
730
|
function trimString2(value) {
|
|
725
731
|
const trimmed = value?.trim();
|
|
@@ -742,403 +748,439 @@ async function resolveGlobalOptions(command) {
|
|
|
742
748
|
store
|
|
743
749
|
};
|
|
744
750
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
} catch {
|
|
751
|
-
}
|
|
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}`;
|
|
752
756
|
}
|
|
757
|
+
return `[${actorLabel}] ${JSON.stringify(event.payload)}`;
|
|
753
758
|
}
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
759
|
+
function parseJwtClaims(token) {
|
|
760
|
+
const parts = token.split(".");
|
|
761
|
+
if (parts.length < 2) {
|
|
762
|
+
return void 0;
|
|
763
|
+
}
|
|
764
|
+
const payload = parts[1];
|
|
765
|
+
if (!payload) {
|
|
766
|
+
return void 0;
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const decoded = Buffer.from(payload, "base64url").toString("utf8");
|
|
770
|
+
const parsed = JSON.parse(decoded);
|
|
771
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
772
|
+
} catch {
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
758
775
|
}
|
|
759
|
-
function
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
return `[${actorLabel}] ${event.text}`;
|
|
776
|
+
function tokenTenantId(token) {
|
|
777
|
+
if (!token) {
|
|
778
|
+
return void 0;
|
|
763
779
|
}
|
|
764
|
-
|
|
780
|
+
const claims = parseJwtClaims(token);
|
|
781
|
+
const tenantId = claims?.tenant_id;
|
|
782
|
+
if (typeof tenantId !== "string") {
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
const trimmed = tenantId.trim();
|
|
786
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
765
787
|
}
|
|
766
|
-
function
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
).option("--json", "Output JSON");
|
|
779
|
-
program.command("version").description("Print current CLI version").action(() => {
|
|
780
|
-
logger.info(cliVersion);
|
|
781
|
-
});
|
|
782
|
-
program.command("init").description("Initialize Starcite CLI config for a remote instance").option("--endpoint <url>", "Starcite endpoint URL").option("--api-key <key>", "API key to store").option("-y, --yes", "Skip prompts and only use provided options").action(async function initAction(options) {
|
|
783
|
-
const { baseUrl, json, store } = await resolveGlobalOptions(this);
|
|
784
|
-
const defaultEndpoint = parseEndpoint(baseUrl, "endpoint");
|
|
785
|
-
let endpoint = defaultEndpoint;
|
|
786
|
-
if (options.endpoint) {
|
|
787
|
-
endpoint = parseEndpoint(options.endpoint, "--endpoint");
|
|
788
|
-
} else if (!options.yes) {
|
|
789
|
-
endpoint = await promptForEndpoint(prompt, defaultEndpoint);
|
|
790
|
-
}
|
|
791
|
-
await store.updateConfig({ baseUrl: endpoint });
|
|
792
|
-
let apiKey = trimString2(options.apiKey);
|
|
793
|
-
if (!(apiKey || options.yes)) {
|
|
794
|
-
apiKey = await promptForApiKey(prompt);
|
|
788
|
+
function tokenScopes(token) {
|
|
789
|
+
const claims = parseJwtClaims(token);
|
|
790
|
+
if (!claims) {
|
|
791
|
+
return /* @__PURE__ */ new Set();
|
|
792
|
+
}
|
|
793
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
794
|
+
const scopeClaim = claims.scope;
|
|
795
|
+
if (typeof scopeClaim === "string") {
|
|
796
|
+
for (const scope of scopeClaim.split(" ")) {
|
|
797
|
+
if (scope.length > 0) {
|
|
798
|
+
scopes.add(scope);
|
|
799
|
+
}
|
|
795
800
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
801
|
+
}
|
|
802
|
+
const scopesClaim = claims.scopes;
|
|
803
|
+
if (Array.isArray(scopesClaim)) {
|
|
804
|
+
for (const scope of scopesClaim) {
|
|
805
|
+
if (typeof scope === "string" && scope.length > 0) {
|
|
806
|
+
scopes.add(scope);
|
|
807
|
+
}
|
|
799
808
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
809
|
+
}
|
|
810
|
+
return scopes;
|
|
811
|
+
}
|
|
812
|
+
function shouldAutoIssueSessionToken(token) {
|
|
813
|
+
const scopes = tokenScopes(token);
|
|
814
|
+
return scopes.has("auth:issue");
|
|
815
|
+
}
|
|
816
|
+
function resolveAppendMode(options) {
|
|
817
|
+
const highLevelMode = options.agent !== void 0 || options.text !== void 0;
|
|
818
|
+
const rawMode = options.actor !== void 0 || options.payload !== void 0;
|
|
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"
|
|
811
830
|
);
|
|
812
|
-
return;
|
|
813
831
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
832
|
+
return { kind: "high-level", agent, text };
|
|
833
|
+
}
|
|
834
|
+
if (rawMode) {
|
|
835
|
+
const actor = trimString2(options.actor);
|
|
836
|
+
const payload = trimString2(options.payload);
|
|
837
|
+
if (!(actor && payload)) {
|
|
838
|
+
throw new InvalidArgumentError(
|
|
839
|
+
"Raw append mode requires --actor and --payload, or use --agent and --text"
|
|
840
|
+
);
|
|
820
841
|
}
|
|
842
|
+
return { kind: "raw", actor, payload };
|
|
843
|
+
}
|
|
844
|
+
throw new InvalidArgumentError(
|
|
845
|
+
"append requires either high-level mode (--agent and --text) or raw mode (--actor and --payload)"
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
function toApiBaseUrlForContext(baseUrl) {
|
|
849
|
+
const parsed = new URL(baseUrl);
|
|
850
|
+
if (!(parsed.protocol === "http:" || parsed.protocol === "https:")) {
|
|
851
|
+
throw new InvalidArgumentError("base URL must use http:// or https://");
|
|
852
|
+
}
|
|
853
|
+
const normalized = parsed.toString().replace(TRAILING_SLASHES_REGEX, "");
|
|
854
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
855
|
+
}
|
|
856
|
+
async function resolveSession(client, apiKey, sessionId) {
|
|
857
|
+
if (!apiKey) {
|
|
858
|
+
throw new InvalidArgumentError(
|
|
859
|
+
"append/tail require --token or a saved API key"
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (shouldAutoIssueSessionToken(apiKey)) {
|
|
863
|
+
return await client.session({
|
|
864
|
+
identity: resolveCreateIdentity(apiKey),
|
|
865
|
+
id: sessionId
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const session = client.session({ token: apiKey });
|
|
869
|
+
if (session.id !== sessionId) {
|
|
870
|
+
throw new InvalidArgumentError(
|
|
871
|
+
`session token is bound to '${session.id}', expected '${sessionId}'`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
return session;
|
|
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
|
+
);
|
|
882
|
+
}
|
|
883
|
+
return new StarciteIdentity({
|
|
884
|
+
tenantId,
|
|
885
|
+
id: agentId,
|
|
886
|
+
type: "agent"
|
|
821
887
|
});
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
888
|
+
}
|
|
889
|
+
var StarciteCliApp = class {
|
|
890
|
+
createClient;
|
|
891
|
+
logger;
|
|
892
|
+
prompt;
|
|
893
|
+
runCommand;
|
|
894
|
+
constructor(deps = {}) {
|
|
895
|
+
this.createClient = deps.createClient ?? ((baseUrl, apiKey) => new Starcite({
|
|
896
|
+
baseUrl,
|
|
897
|
+
apiKey
|
|
898
|
+
}));
|
|
899
|
+
this.logger = deps.logger ?? defaultLogger;
|
|
900
|
+
this.prompt = deps.prompt ?? createDefaultPrompt();
|
|
901
|
+
this.runCommand = deps.runCommand ?? defaultCommandRunner;
|
|
902
|
+
}
|
|
903
|
+
buildProgram() {
|
|
904
|
+
const createClient = this.createClient;
|
|
905
|
+
const logger = this.logger;
|
|
906
|
+
const prompt = this.prompt;
|
|
907
|
+
const runCommand = this.runCommand;
|
|
908
|
+
const program = new Command();
|
|
909
|
+
program.name("starcite").description("Starcite CLI").showHelpAfterError().version(cliVersion, "-v, --version", "Print current CLI version").option("-u, --base-url <url>", "Starcite API base URL").option("-k, --token <token>", "Starcite API key").option(
|
|
910
|
+
"--config-dir <path>",
|
|
911
|
+
"Starcite CLI config directory (default: ~/.starcite)"
|
|
912
|
+
).option("--json", "Output JSON");
|
|
913
|
+
program.command("version").description("Print current CLI version").action(() => {
|
|
914
|
+
logger.info(cliVersion);
|
|
915
|
+
});
|
|
916
|
+
program.command("config").description("Manage CLI configuration").addCommand(
|
|
917
|
+
new Command("set").description("Set a configuration value").argument("<key>", "endpoint | producer-id | api-key").argument("<value>", "value to store").action(async function(key, value) {
|
|
918
|
+
const { store } = await resolveGlobalOptions(this);
|
|
919
|
+
const parsedKey = parseConfigSetKey(key);
|
|
920
|
+
if (parsedKey === "endpoint") {
|
|
921
|
+
const endpoint = parseEndpoint(value, "endpoint");
|
|
922
|
+
await store.updateConfig({ baseUrl: endpoint });
|
|
923
|
+
logger.info(`Endpoint set to ${endpoint}`);
|
|
924
|
+
return;
|
|
836
925
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
926
|
+
if (parsedKey === "producer-id") {
|
|
927
|
+
const producerId = trimString2(value);
|
|
928
|
+
if (!producerId) {
|
|
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,
|
|
982
|
+
cursor,
|
|
983
|
+
metadata
|
|
984
|
+
});
|
|
985
|
+
if (json) {
|
|
986
|
+
logger.info(JSON.stringify(page, null, 2));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (page.sessions.length === 0) {
|
|
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}`);
|
|
1001
|
+
}
|
|
1002
|
+
})
|
|
1003
|
+
);
|
|
1004
|
+
program.command("up").description("Start local Starcite services with Docker").option("-y, --yes", "Skip confirmation prompts and use defaults").option(
|
|
1005
|
+
"--port <port>",
|
|
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) {
|
|
847
1013
|
const { baseUrl, store } = await resolveGlobalOptions(this);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
JSON.stringify(
|
|
859
|
-
{
|
|
860
|
-
endpoint: config.baseUrl ?? baseUrl,
|
|
861
|
-
producerId: config.producerId ?? null,
|
|
862
|
-
apiKey: apiKey ? "***" : null,
|
|
863
|
-
apiKeySource,
|
|
864
|
-
configDir: store.directory
|
|
865
|
-
},
|
|
866
|
-
null,
|
|
867
|
-
2
|
|
868
|
-
)
|
|
869
|
-
);
|
|
870
|
-
})
|
|
871
|
-
);
|
|
872
|
-
program.command("auth").description("Manage API key authentication").addCommand(
|
|
873
|
-
new Command("login").description("Save an API key for authenticated requests").option("--api-key <key>", "API key to store").action(async function authLoginAction(options) {
|
|
874
|
-
const { store } = await resolveGlobalOptions(this);
|
|
875
|
-
let apiKey = trimString2(options.apiKey);
|
|
876
|
-
if (!apiKey) {
|
|
877
|
-
apiKey = await promptForApiKey(prompt);
|
|
878
|
-
}
|
|
879
|
-
if (!apiKey) {
|
|
880
|
-
throw new Error("API key cannot be empty");
|
|
881
|
-
}
|
|
882
|
-
await store.saveApiKey(apiKey);
|
|
883
|
-
await store.updateConfig({ apiKey: void 0 });
|
|
884
|
-
logger.info("API key saved.");
|
|
885
|
-
})
|
|
886
|
-
).addCommand(
|
|
887
|
-
new Command("logout").description("Remove the saved API key").action(async function authLogoutAction() {
|
|
888
|
-
const { store } = await resolveGlobalOptions(this);
|
|
889
|
-
await store.clearApiKey();
|
|
890
|
-
logger.info("Saved API key removed.");
|
|
891
|
-
})
|
|
892
|
-
).addCommand(
|
|
893
|
-
new Command("status").description("Show authentication status").action(async function authStatusAction() {
|
|
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) {
|
|
894
1024
|
const { store } = await resolveGlobalOptions(this);
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
(value) => parsePositiveInteger(value, "--limit")
|
|
913
|
-
).option("--cursor <cursor>", "Pagination cursor").option("--metadata <json>", "Metadata filter JSON object").action(async function sessionsListAction(options) {
|
|
914
|
-
const { baseUrl, apiKey, json } = await resolveGlobalOptions(this);
|
|
915
|
-
const client = apiKey ? createClient(baseUrl, apiKey) : createClient(baseUrl);
|
|
916
|
-
const metadata = options.metadata ? parseSessionMetadataFilters(options.metadata) : void 0;
|
|
917
|
-
const cursor = options.cursor?.trim();
|
|
918
|
-
if (options.cursor !== void 0 && !cursor) {
|
|
919
|
-
throw new InvalidArgumentError("--cursor must be non-empty");
|
|
920
|
-
}
|
|
921
|
-
const page = await client.listSessions({
|
|
922
|
-
limit: options.limit,
|
|
923
|
-
cursor,
|
|
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,
|
|
924
1042
|
metadata
|
|
925
1043
|
});
|
|
926
1044
|
if (json) {
|
|
927
|
-
logger.info(JSON.stringify(page, null, 2));
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
if (page.sessions.length === 0) {
|
|
931
|
-
logger.info("No sessions found.");
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
logger.info("id title created_at");
|
|
935
|
-
for (const session of page.sessions) {
|
|
936
1045
|
logger.info(
|
|
937
|
-
|
|
1046
|
+
JSON.stringify(session.record ?? { id: session.id }, null, 2)
|
|
938
1047
|
);
|
|
1048
|
+
return;
|
|
939
1049
|
}
|
|
940
|
-
|
|
941
|
-
logger.info(`next_cursor=${page.next_cursor}`);
|
|
942
|
-
}
|
|
943
|
-
})
|
|
944
|
-
);
|
|
945
|
-
program.command("up").description("Start local Starcite services with Docker").option("-y, --yes", "Skip confirmation prompts and use defaults").option(
|
|
946
|
-
"--port <port>",
|
|
947
|
-
"Starcite API port",
|
|
948
|
-
(value) => parsePort2(value, "--port")
|
|
949
|
-
).option(
|
|
950
|
-
"--db-port <port>",
|
|
951
|
-
"Postgres port",
|
|
952
|
-
(value) => parsePort2(value, "--db-port")
|
|
953
|
-
).option("--image <image>", "Override Starcite image").action(async function upAction(options) {
|
|
954
|
-
const { baseUrl, store } = await resolveGlobalOptions(this);
|
|
955
|
-
await runUpWizard({
|
|
956
|
-
baseUrl,
|
|
957
|
-
logger,
|
|
958
|
-
options,
|
|
959
|
-
prompt,
|
|
960
|
-
runCommand,
|
|
961
|
-
store
|
|
1050
|
+
logger.info(session.id);
|
|
962
1051
|
});
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1052
|
+
program.command("append <sessionId>").description("Append an event").option("--agent <agent>", "Agent name (high-level mode)").option("--text <text>", "Text content (high-level mode)").option("--type <type>", "Event type", "content").option("--source <source>", "Event source").option(
|
|
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));
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
logger.info(`seq=${response.seq} deduped=${response.deduped}`);
|
|
982
1112
|
});
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
)
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
).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(
|
|
999
|
-
"--expected-seq <seq>",
|
|
1000
|
-
"Expected sequence",
|
|
1001
|
-
(value) => parseNonNegativeInteger(value, "--expected-seq")
|
|
1002
|
-
).action(async function appendAction(sessionId, options) {
|
|
1003
|
-
const { baseUrl, apiKey, json, store } = await resolveGlobalOptions(this);
|
|
1004
|
-
const client = apiKey ? createClient(baseUrl, apiKey) : createClient(baseUrl);
|
|
1005
|
-
const session = client.session(sessionId);
|
|
1006
|
-
const metadata = options.metadata ? parseJsonObject(options.metadata, "--metadata") : void 0;
|
|
1007
|
-
const refs = options.refs ? parseEventRefs(options.refs) : void 0;
|
|
1008
|
-
const highLevelMode = options.agent !== void 0 || options.text !== void 0;
|
|
1009
|
-
const rawMode = options.actor !== void 0 || options.payload !== void 0;
|
|
1010
|
-
if (highLevelMode && rawMode) {
|
|
1011
|
-
throw new Error(
|
|
1012
|
-
"Choose either high-level mode (--agent and --text) or raw mode (--actor and --payload), not both"
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
const producerId = await store.resolveProducerId(options.producerId);
|
|
1016
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
1017
|
-
const contextKey = buildSeqContextKey(
|
|
1018
|
-
normalizedBaseUrl,
|
|
1019
|
-
sessionId,
|
|
1020
|
-
producerId
|
|
1021
|
-
);
|
|
1022
|
-
const response = await store.withStateLock(async () => {
|
|
1023
|
-
const producerSeq = options.producerSeq ?? await store.readNextSeq(contextKey);
|
|
1024
|
-
const appendOptions = {
|
|
1025
|
-
...options,
|
|
1026
|
-
producerId,
|
|
1027
|
-
producerSeq
|
|
1113
|
+
program.command("tail <sessionId>").description("Tail events from a session").option(
|
|
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();
|
|
1028
1128
|
};
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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);
|
|
1158
|
+
}
|
|
1032
1159
|
});
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
logger.info(
|
|
1038
|
-
`seq=${response.seq} last_seq=${response.last_seq} deduped=${response.deduped}`
|
|
1039
|
-
);
|
|
1040
|
-
});
|
|
1041
|
-
program.command("tail <sessionId>").description("Tail events from a session").option(
|
|
1042
|
-
"--cursor <cursor>",
|
|
1043
|
-
"Replay cursor",
|
|
1044
|
-
(value) => parseNonNegativeInteger(value, "--cursor")
|
|
1045
|
-
).option("--agent <agent>", "Filter by agent name").option(
|
|
1046
|
-
"--limit <count>",
|
|
1047
|
-
"Stop after N events",
|
|
1048
|
-
(value) => parseNonNegativeInteger(value, "--limit")
|
|
1049
|
-
).option("--no-follow", "Exit after replaying stored events").action(async function tailAction(sessionId, options) {
|
|
1050
|
-
const { baseUrl, apiKey, json } = await resolveGlobalOptions(this);
|
|
1051
|
-
const client = apiKey ? createClient(baseUrl, apiKey) : createClient(baseUrl);
|
|
1052
|
-
const session = client.session(sessionId);
|
|
1053
|
-
const abortController = new AbortController();
|
|
1054
|
-
const onSigint = () => {
|
|
1055
|
-
abortController.abort();
|
|
1056
|
-
};
|
|
1057
|
-
process.once("SIGINT", onSigint);
|
|
1160
|
+
return program;
|
|
1161
|
+
}
|
|
1162
|
+
async run(argv = process.argv) {
|
|
1163
|
+
const program = this.buildProgram();
|
|
1058
1164
|
try {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
})) {
|
|
1066
|
-
if (json) {
|
|
1067
|
-
logger.info(JSON.stringify(event));
|
|
1068
|
-
} else {
|
|
1069
|
-
logger.info(formatTailEvent(event));
|
|
1070
|
-
}
|
|
1071
|
-
emitted += 1;
|
|
1072
|
-
if (options.limit !== void 0 && emitted >= options.limit) {
|
|
1073
|
-
abortController.abort();
|
|
1074
|
-
break;
|
|
1075
|
-
}
|
|
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;
|
|
1170
|
+
return;
|
|
1076
1171
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
function appendHighLevel(session, options, metadata, refs) {
|
|
1084
|
-
if (!(options.agent && options.text)) {
|
|
1085
|
-
throw new Error(
|
|
1086
|
-
"--agent and --text are required for high-level append mode"
|
|
1087
|
-
);
|
|
1088
|
-
}
|
|
1089
|
-
return session.append({
|
|
1090
|
-
agent: options.agent,
|
|
1091
|
-
producerId: options.producerId,
|
|
1092
|
-
producerSeq: options.producerSeq,
|
|
1093
|
-
text: options.text,
|
|
1094
|
-
type: options.type,
|
|
1095
|
-
source: options.source,
|
|
1096
|
-
metadata,
|
|
1097
|
-
refs,
|
|
1098
|
-
idempotencyKey: options.idempotencyKey,
|
|
1099
|
-
expectedSeq: options.expectedSeq
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
function appendRaw(session, options, metadata, refs) {
|
|
1103
|
-
if (!(options.actor && options.payload)) {
|
|
1104
|
-
throw new Error(
|
|
1105
|
-
"Raw append mode requires --actor and --payload, or use --agent and --text"
|
|
1106
|
-
);
|
|
1107
|
-
}
|
|
1108
|
-
return session.appendRaw({
|
|
1109
|
-
type: options.type,
|
|
1110
|
-
payload: parseJsonObject(options.payload, "--payload"),
|
|
1111
|
-
actor: options.actor,
|
|
1112
|
-
producer_id: options.producerId,
|
|
1113
|
-
producer_seq: options.producerSeq,
|
|
1114
|
-
source: options.source,
|
|
1115
|
-
metadata,
|
|
1116
|
-
refs,
|
|
1117
|
-
idempotency_key: options.idempotencyKey,
|
|
1118
|
-
expected_seq: options.expectedSeq
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
async function run(argv = process.argv, deps = {}) {
|
|
1122
|
-
const program = buildProgram(deps);
|
|
1123
|
-
try {
|
|
1124
|
-
await program.parseAsync(argv);
|
|
1125
|
-
} catch (error) {
|
|
1126
|
-
if (error instanceof StarciteApiError) {
|
|
1127
|
-
const logger2 = deps.logger ?? defaultLogger;
|
|
1128
|
-
logger2.error(`${error.code} (${error.status}): ${error.message}`);
|
|
1129
|
-
process.exitCode = 1;
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
if (error instanceof Error) {
|
|
1133
|
-
const logger2 = deps.logger ?? defaultLogger;
|
|
1134
|
-
logger2.error(error.message);
|
|
1172
|
+
if (error instanceof Error) {
|
|
1173
|
+
this.logger.error(error.message);
|
|
1174
|
+
process.exitCode = 1;
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
this.logger.error("Unknown error");
|
|
1135
1178
|
process.exitCode = 1;
|
|
1136
|
-
return;
|
|
1137
1179
|
}
|
|
1138
|
-
const logger = deps.logger ?? defaultLogger;
|
|
1139
|
-
logger.error("Unknown error");
|
|
1140
|
-
process.exitCode = 1;
|
|
1141
1180
|
}
|
|
1181
|
+
};
|
|
1182
|
+
async function run(argv = process.argv, deps = {}) {
|
|
1183
|
+
await new StarciteCliApp(deps).run(argv);
|
|
1142
1184
|
}
|
|
1143
1185
|
|
|
1144
1186
|
// src/index.ts
|