@kubitnodes/stellaris 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -2
  3. package/dist/cli/index.js +167 -12
  4. package/dist/server/index.js +543 -32
  5. package/package.json +2 -1
  6. package/web/dist/assets/ChannelView-B4rv0TMD.js +1 -0
  7. package/web/dist/assets/CitizenView-DfBiP1Jw.js +2 -0
  8. package/web/dist/assets/CitizensView-DB2TlGxn.js +1 -0
  9. package/web/dist/assets/KnowledgeView-l0I_R5Zd.js +1 -0
  10. package/web/dist/assets/Markdown-C6mhAmbQ.js +29 -0
  11. package/web/dist/assets/MessageItem-BR30twQh.js +1 -0
  12. package/web/dist/assets/MetricsView-BZ0WW2Do.js +1 -0
  13. package/web/dist/assets/NeedsYouView-C6CZNnJn.js +1 -0
  14. package/web/dist/assets/Overview-D47FfDDO.js +1 -0
  15. package/web/dist/assets/Pane-BXpuXst6.js +1 -0
  16. package/web/dist/assets/ProjectView-CxUA5nFw.js +1 -0
  17. package/web/dist/assets/ProposalView-C0oLBLQD.js +1 -0
  18. package/web/dist/assets/ProposalsView-BscIGUTT.js +1 -0
  19. package/web/dist/assets/RunnersView-BypVHvzI.js +1 -0
  20. package/web/dist/assets/SocietyView-C403e1uR.js +1 -0
  21. package/web/dist/assets/SubjectThread-irSHRJpB.js +1 -0
  22. package/web/dist/assets/TaskView-B-4WGLWK.js +1 -0
  23. package/web/dist/assets/TasksView-CA02-WIW.js +1 -0
  24. package/web/dist/assets/ThreadCard-C4rb-53v.js +1 -0
  25. package/web/dist/assets/ThreadForms-Dh9ky_aU.js +1 -0
  26. package/web/dist/assets/ThreadView-CAdCu-y_.js +1 -0
  27. package/web/dist/assets/{citizen-BXVebeiP.js → citizen-BWGATcew.js} +1 -1
  28. package/web/dist/assets/format-DZLzLk7S.js +55 -0
  29. package/web/dist/assets/index-D-wuKd9C.js +21 -0
  30. package/web/dist/assets/index-HwY3AK-1.css +2 -0
  31. package/web/dist/assets/{tasks-rltmWd5a.js → tasks-DZgkXWwu.js} +1 -1
  32. package/web/dist/assets/{useStickyScroll-DDS3s21P.js → useStickyScroll-CVTdIoKj.js} +1 -1
  33. package/web/dist/index.html +4 -4
  34. package/web/dist/assets/ChannelView-B8BZLsf1.js +0 -1
  35. package/web/dist/assets/CitizenView-GomLDcrr.js +0 -2
  36. package/web/dist/assets/CitizensView-yNv8WtND.js +0 -1
  37. package/web/dist/assets/KnowledgeView--jxofSSN.js +0 -1
  38. package/web/dist/assets/Markdown-BpYzeV5r.js +0 -28
  39. package/web/dist/assets/MessageItem-BGPpqgpq.js +0 -1
  40. package/web/dist/assets/MetricsView-CcqLr6S1.js +0 -1
  41. package/web/dist/assets/NeedsYouView-r7gJGgVG.js +0 -1
  42. package/web/dist/assets/Overview-Cmz4_gfI.js +0 -1
  43. package/web/dist/assets/Pane-DRpHmeJ9.js +0 -1
  44. package/web/dist/assets/ProjectView-CuACZj4L.js +0 -1
  45. package/web/dist/assets/ProposalView-BiVcNEDL.js +0 -1
  46. package/web/dist/assets/ProposalsView-BIdkOfZO.js +0 -1
  47. package/web/dist/assets/SocietyView-Dya97szC.js +0 -1
  48. package/web/dist/assets/SubjectThread-2j_msY38.js +0 -1
  49. package/web/dist/assets/TaskView-CcJr7xnH.js +0 -1
  50. package/web/dist/assets/TasksView-BUvHntvK.js +0 -1
  51. package/web/dist/assets/ThreadCard-tzMGvn9j.js +0 -1
  52. package/web/dist/assets/ThreadForms-DE2yJEhz.js +0 -1
  53. package/web/dist/assets/ThreadView-FsNOTyVC.js +0 -1
  54. package/web/dist/assets/format-Ca2VAvEx.js +0 -77
  55. package/web/dist/assets/index-CtuchAFa.css +0 -2
  56. package/web/dist/assets/index-D4GJkeKU.js +0 -237
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kr4t0n
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -22,10 +22,11 @@ export STELLARIS_DATA_DIR=~/stellaris-data
22
22
  stellaris init --name my-society # prints your user token once
23
23
  stellaris agent add desk --role concierge --cli claude # the front desk, which routes what you ask
24
24
  stellaris agent add stew --role steward --cli claude # the steward, which reads the society's signals
25
- stellaris runner add laptop # prints the runner's token once
26
25
  stellaris-server # http://127.0.0.1:4700
27
26
  ```
28
27
 
29
- Open the server's address and enter the user token. Then start a runner with the runner's token on any machine that reaches the server.
28
+ Open the server's address and enter the user token. Then start a runner on any machine that reaches the server; it prints a code to approve on the board, under runners.
29
+
30
+ To sign in with GitHub instead, create a GitHub OAuth app whose callback URL is the server's address followed by `/auth/github/callback`, and start the server with `STELLARIS_GITHUB_CLIENT_ID`, `STELLARIS_GITHUB_CLIENT_SECRET`, and `STELLARIS_GITHUB_USERS`, the logins allowed in. A lost user token is replaced with `stellaris user token --rotate`, which a running server takes after a restart.
30
31
 
31
32
  The CLI writes the data directory directly, so run its setup commands before the server starts; once it runs, act as the user through the interface or the HTTP API. `STELLARIS_HOST` and `STELLARIS_PORT` set where the server listens (`127.0.0.1:4700`); every setting is a `STELLARIS_*` variable, listed in the [repository's README](https://github.com/kr4t0n/stellaris#environment-variables). The server is also published as a Docker image, `kr4t0n/stellaris-server`, and a Helm chart.
package/dist/cli/index.js CHANGED
@@ -670,6 +670,9 @@ var BOARD_EVENT_TYPES = [
670
670
  "knowledge.written",
671
671
  "skill.promoted",
672
672
  "runner.added",
673
+ "user.signed_in",
674
+ "user.signed_out",
675
+ "user.token_rotated",
673
676
  "runner.changed",
674
677
  "ops.signal",
675
678
  "paused.changed",
@@ -771,6 +774,36 @@ var RunnerHelloSchema = z6.object({
771
774
  turns: z6.array(UlidSchema).default([])
772
775
  });
773
776
  var RunnerWelcomeSchema = z6.object({ name: NameSchema, version: z6.string() });
777
+ var RunnerEnrollRequestSchema = z6.object({
778
+ protocol: z6.number().int().positive(),
779
+ version: z6.string().min(1).max(64),
780
+ hostname: z6.string().min(1).max(253),
781
+ os: RunnerOsSchema,
782
+ clis: z6.array(CliKindSchema).max(8),
783
+ capabilities: z6.array(z6.string().min(1).max(64)).max(32).default([])
784
+ });
785
+ var RunnerEnrollmentSchema = z6.object({
786
+ deviceCode: z6.string().min(1),
787
+ userCode: z6.string().min(1),
788
+ expiresAt: IsoDateTimeSchema,
789
+ intervalMs: z6.number().int().positive()
790
+ });
791
+ var RunnerEnrollPollSchema = z6.object({ deviceCode: z6.string().min(1) });
792
+ var RunnerEnrollStatusSchema = z6.discriminatedUnion("status", [
793
+ z6.object({ status: z6.literal("pending") }),
794
+ z6.object({ status: z6.literal("approved"), name: NameSchema, token: z6.string().min(1) }),
795
+ z6.object({ status: z6.literal("denied") })
796
+ ]);
797
+ var PendingEnrollmentSchema = z6.object({
798
+ userCode: z6.string().min(1),
799
+ hostname: z6.string(),
800
+ os: RunnerOsSchema,
801
+ clis: z6.array(CliKindSchema),
802
+ capabilities: z6.array(z6.string()),
803
+ version: z6.string(),
804
+ requestedAt: IsoDateTimeSchema,
805
+ expiresAt: IsoDateTimeSchema
806
+ });
774
807
  var ProjectRepoSchema = z6.object({
775
808
  slug: NameSchema,
776
809
  origin: z6.string().nullable(),
@@ -1077,6 +1110,12 @@ var MetricsSchema = z9.object({
1077
1110
  // packages/shared/dist/config.js
1078
1111
  import { z as z10 } from "zod";
1079
1112
  var LogLevelSchema = z10.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1113
+ var GithubSignInSchema = z10.object({
1114
+ clientId: z10.string().min(1),
1115
+ clientSecret: z10.string().min(1),
1116
+ /** Logins, lowercased; GitHub logins are case-insensitive. */
1117
+ users: z10.array(z10.string().min(1)).min(1)
1118
+ });
1080
1119
  var ServerConfigSchema = z10.object({
1081
1120
  dataDir: z10.string().min(1),
1082
1121
  host: z10.string().min(1),
@@ -1094,13 +1133,23 @@ var ServerConfigSchema = z10.object({
1094
1133
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1095
1134
  toolRounds: z10.number().int().positive().nullable(),
1096
1135
  /** How long a resident session stays warm after its last turn. */
1097
- residentIdleMs: z10.number().int().positive()
1136
+ residentIdleMs: z10.number().int().positive(),
1137
+ /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1138
+ github: GithubSignInSchema.nullable(),
1139
+ /**
1140
+ * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1141
+ * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1142
+ */
1143
+ trustedProxies: z10.number().int().min(0).max(16)
1098
1144
  });
1099
1145
  var RunnerConfigSchema = z10.object({
1100
1146
  /** The board server, for example http://127.0.0.1:4700. */
1101
1147
  serverUrl: z10.string().url(),
1102
- /** The runner's token, minted by `runner add`. */
1103
- token: z10.string().min(1),
1148
+ /**
1149
+ * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1150
+ * to enroll it.
1151
+ */
1152
+ token: z10.string().min(1).nullable(),
1104
1153
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1105
1154
  dataDir: z10.string().min(1),
1106
1155
  logLevel: LogLevelSchema,
@@ -1111,7 +1160,7 @@ var RunnerConfigSchema = z10.object({
1111
1160
  });
1112
1161
 
1113
1162
  // packages/shared/dist/version.js
1114
- var STELLARIS_VERSION = "0.1.0";
1163
+ var STELLARIS_VERSION = "0.3.0";
1115
1164
 
1116
1165
  // packages/board-core/dist/errors.js
1117
1166
  var BoardError = class extends Error {
@@ -2141,6 +2190,14 @@ var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".cod
2141
2190
  var RunnerTokensSchema = z12.object({
2142
2191
  runners: z12.record(z12.string(), z12.object({ tokenHash: z12.string().min(1) }))
2143
2192
  });
2193
+ var SignInsSchema = z12.object({
2194
+ signIns: z12.array(z12.object({
2195
+ tokenHash: z12.string().min(1),
2196
+ login: z12.string().min(1),
2197
+ createdAt: IsoDateTimeSchema,
2198
+ expiresAt: IsoDateTimeSchema
2199
+ })).default([])
2200
+ });
2144
2201
  var TurnHistoryPayloadSchema = z12.object({
2145
2202
  project: NameSchema,
2146
2203
  thread: UlidSchema.optional(),
@@ -2346,6 +2403,8 @@ var Board = class _Board {
2346
2403
  turnTokens = /* @__PURE__ */ new Map();
2347
2404
  /** Runner token hashes to runner names. */
2348
2405
  runnerTokens = /* @__PURE__ */ new Map();
2406
+ /** Sign-ins by token hash, each acting as the user until it expires. */
2407
+ signIns = /* @__PURE__ */ new Map();
2349
2408
  files = new FileTree();
2350
2409
  homes;
2351
2410
  constructor(dataDir, options) {
@@ -2374,6 +2433,7 @@ var Board = class _Board {
2374
2433
  await board.mutex.run(() => board.applyAlignments());
2375
2434
  await board.loadTokenIndex();
2376
2435
  await board.loadRunnerTokens();
2436
+ await board.loadSignIns();
2377
2437
  await board.homes.installHooks();
2378
2438
  return board;
2379
2439
  }
@@ -2621,7 +2681,7 @@ var Board = class _Board {
2621
2681
  }
2622
2682
  }
2623
2683
  }
2624
- await this.updateAgent(USER_NAME, (user) => ({ ...user, subscriptions: [] }));
2684
+ await this.updateAgent(USER_NAME, (user2) => ({ ...user2, subscriptions: [] }));
2625
2685
  await this.refreshMember(USER_NAME);
2626
2686
  }
2627
2687
  /** Writes every missing seed charter and aligns the existing ones with their seed. */
@@ -2696,6 +2756,14 @@ ${charter.purpose}
2696
2756
  if (persistent !== void 0) {
2697
2757
  return persistent;
2698
2758
  }
2759
+ const signIn = this.signIns.get(hash);
2760
+ if (signIn !== void 0) {
2761
+ if (signIn.expiresAt > this.now().getTime()) {
2762
+ return this.userActor();
2763
+ }
2764
+ this.signIns.delete(hash);
2765
+ return null;
2766
+ }
2699
2767
  const temporary = this.turnTokens.get(hash);
2700
2768
  if (temporary === void 0) {
2701
2769
  return null;
@@ -2740,6 +2808,77 @@ ${charter.purpose}
2740
2808
  userActor() {
2741
2809
  return { name: USER_NAME, role: USER_ROLE };
2742
2810
  }
2811
+ /**
2812
+ * Signs the user in, as `login` on the identity provider, for `ttlMs`, and returns the sign-in's
2813
+ * token, shown once. Only its hash is kept, in `state/sign-ins.json`, so a sign-in outlives a
2814
+ * restart; it acts as the user, like the user's own token, until it expires or is signed out.
2815
+ */
2816
+ async signIn(login, ttlMs) {
2817
+ return this.mutex.run(async () => {
2818
+ const token = mintToken();
2819
+ const now = this.now().getTime();
2820
+ const record = {
2821
+ tokenHash: hashToken(token),
2822
+ login,
2823
+ createdAt: new Date(now).toISOString(),
2824
+ expiresAt: new Date(now + ttlMs).toISOString()
2825
+ };
2826
+ const live = (await this.readSignIns()).filter((each) => Date.parse(each.expiresAt) > now);
2827
+ await writeJson(this.signInsFile(), { signIns: [...live, record] });
2828
+ this.signIns.set(record.tokenHash, { login, expiresAt: Date.parse(record.expiresAt) });
2829
+ await this.events.append("user.signed_in", USER_NAME, { login });
2830
+ return token;
2831
+ });
2832
+ }
2833
+ /** Ends the sign-in a token belongs to; false for a token that is no sign-in's. */
2834
+ async signOut(token) {
2835
+ const hash = hashToken(token);
2836
+ return this.mutex.run(async () => {
2837
+ const signIn = this.signIns.get(hash);
2838
+ if (signIn === void 0) {
2839
+ return false;
2840
+ }
2841
+ this.signIns.delete(hash);
2842
+ await writeJson(this.signInsFile(), {
2843
+ signIns: (await this.readSignIns()).filter((each) => each.tokenHash !== hash)
2844
+ });
2845
+ await this.events.append("user.signed_out", USER_NAME, { login: signIn.login });
2846
+ return true;
2847
+ });
2848
+ }
2849
+ /**
2850
+ * Issues the user a new token and revokes the old one, returning the new one, shown once: the way
2851
+ * back in when the token is lost. A server already running keeps the old token's hash in memory
2852
+ * until it restarts, since the admin CLI that calls this writes the data directory beside it.
2853
+ */
2854
+ async rotateUserToken() {
2855
+ return this.mutex.run(async () => {
2856
+ const previous = await this.readAgent(USER_NAME);
2857
+ const token = mintToken();
2858
+ await this.updateAgent(USER_NAME, (agent2) => ({ ...agent2, tokenHash: hashToken(token) }));
2859
+ this.tokenIndex.delete(previous.tokenHash);
2860
+ this.tokenIndex.set(hashToken(token), this.userActor());
2861
+ await this.events.append("user.token_rotated", USER_NAME, {});
2862
+ return token;
2863
+ });
2864
+ }
2865
+ signInsFile() {
2866
+ return path6.join(this.paths.state(), "sign-ins.json");
2867
+ }
2868
+ async readSignIns() {
2869
+ const file = this.signInsFile();
2870
+ return await exists(file) ? (await readJson(file, SignInsSchema)).signIns : [];
2871
+ }
2872
+ async loadSignIns() {
2873
+ this.signIns.clear();
2874
+ const now = this.now().getTime();
2875
+ for (const signIn of await this.readSignIns()) {
2876
+ const expiresAt = Date.parse(signIn.expiresAt);
2877
+ if (expiresAt > now) {
2878
+ this.signIns.set(signIn.tokenHash, { login: signIn.login, expiresAt });
2879
+ }
2880
+ }
2881
+ }
2743
2882
  async actorFor(name) {
2744
2883
  const agent2 = await this.readAgent(name);
2745
2884
  return { name: agent2.name, role: agent2.role };
@@ -3074,9 +3213,9 @@ ${charter.purpose}
3074
3213
 
3075
3214
  Agents may edit this file. It is rendered by the board UI.
3076
3215
  `);
3077
- await this.updateAgent(USER_NAME, (user) => ({
3078
- ...user,
3079
- memberships: [.../* @__PURE__ */ new Set([...user.memberships, project2.slug])]
3216
+ await this.updateAgent(USER_NAME, (user2) => ({
3217
+ ...user2,
3218
+ memberships: [.../* @__PURE__ */ new Set([...user2.memberships, project2.slug])]
3080
3219
  }));
3081
3220
  await this.refreshMember(USER_NAME);
3082
3221
  await this.events.append("project.added", by, {
@@ -4736,8 +4875,9 @@ ${rationale}`}`);
4736
4875
  /**
4737
4876
  * Registers a runner and mints its token, returned once; only its hash is kept, outside the
4738
4877
  * projection. The runner reports its operating system, CLIs, and capabilities when it connects.
4878
+ * An enrolled runner names the machine that asked, which the event records.
4739
4879
  */
4740
- async addRunner(actor, name) {
4880
+ async addRunner(actor, name, enrolled) {
4741
4881
  if (actor.role !== USER_ROLE) {
4742
4882
  throw new BoardError("FORBIDDEN", "only the user adds runners");
4743
4883
  }
@@ -4761,7 +4901,10 @@ ${rationale}`}`);
4761
4901
  runners: { ...tokens, [parsed]: { tokenHash: hashToken(token) } }
4762
4902
  });
4763
4903
  this.runnerTokens.set(hashToken(token), parsed);
4764
- await this.events.append("runner.added", actor.name, { name: parsed });
4904
+ await this.events.append("runner.added", actor.name, {
4905
+ name: parsed,
4906
+ ...enrolled === void 0 ? {} : { enrolledFrom: enrolled.hostname }
4907
+ });
4765
4908
  return { runner: runner2, token };
4766
4909
  });
4767
4910
  }
@@ -4958,7 +5101,7 @@ ${charter.purpose}
4958
5101
  await writeJson(this.paths.pausedFile(), { paused: false });
4959
5102
  const userToken = mintToken();
4960
5103
  const userCharter = await this.readRole(USER_ROLE);
4961
- const user = AgentSchema.parse({
5104
+ const user2 = AgentSchema.parse({
4962
5105
  name: USER_NAME,
4963
5106
  role: USER_ROLE,
4964
5107
  cli: null,
@@ -4968,7 +5111,7 @@ ${charter.purpose}
4968
5111
  createdAt,
4969
5112
  tokenHash: hashToken(userToken)
4970
5113
  });
4971
- await this.writeAgentHome(user, userCharter);
5114
+ await this.writeAgentHome(user2, userCharter);
4972
5115
  await this.events.append("society.initialized", USER_NAME, { name: society.name });
4973
5116
  return userToken;
4974
5117
  }
@@ -5871,6 +6014,18 @@ program.command("init").description("Create a society in the data directory and
5871
6014
  `User token (shown once): ${userToken}`
5872
6015
  ].join("\n"));
5873
6016
  });
6017
+ var user = program.command("user").description("The user's own access");
6018
+ user.command("token").description("Issue the user a new token and print it once; the old one stops working").option("--rotate", "replace the token", false).action(async (opts) => {
6019
+ if (!opts.rotate) {
6020
+ throw new BoardError("VALIDATION", "the board keeps only a hash of the user token, so it cannot show it again; `user token --rotate` issues a new one");
6021
+ }
6022
+ const board = await open();
6023
+ const userToken = await board.rotateUserToken();
6024
+ print({ userToken }, () => [
6025
+ `User token (shown once): ${userToken}`,
6026
+ "A board server running on this data directory reads the new token only when it restarts."
6027
+ ].join("\n"));
6028
+ });
5874
6029
  var project = program.command("project").description("Manage projects");
5875
6030
  project.command("add <slug>").description("Add a project with its default channels").option("--name <name>", "display name").option("--repo <url>", "git remote").option("--on-done <effect>", "what finishing a task does: none, or merge its branch").action(async (slug, opts) => {
5876
6031
  const board = await open();