@intelli-1113/stratos-sdk 1.3.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,183 +1,148 @@
1
- import { platform } from "node:os";
2
- import { writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- export class ConfigTemplate {
6
- constructor(opts = {}) {
7
- this.personEmail = opts.personEmail || "alice@company.com";
8
- this.surface = opts.surface || "claude-code"; // claude-code, claude-chat, claude-cowork
9
- this.token = opts.token || `sk_dev_${opts.personEmail.split("@")[0]}_${opts.surface}_${Date.now()}`;
10
- this.url = opts.url || "https://stratos.company.com";
11
- this.osType = opts.osType || platform();
12
- this.contentOff = opts.contentOff !== false;
13
- this.recordInputs = opts.recordInputs === true ? "1" : "0";
14
- this.recordOutputs = opts.recordOutputs === true ? "1" : "0";
15
- }
16
-
17
- toEnv() {
18
- return `STRATOS_TOKEN=${this.token}
19
- STRATOS_URL=${this.url}
20
- STRATOS_SURFACE=${this.surface}
21
- STRATOS_RECORD_INPUTS=${this.recordInputs}
22
- STRATOS_RECORD_OUTPUTS=${this.recordOutputs}
23
- NODE_OPTIONS=--import @intelli-1113/stratos-sdk/register
24
- OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
25
- `;
26
- }
27
-
28
- toSettingsJson() {
29
- return {
30
- surface: this.surface,
31
- owner_email: this.personEmail,
32
- env: {
33
- STRATOS_TOKEN: this.token,
34
- STRATOS_URL: this.url,
35
- STRATOS_SURFACE: this.surface,
36
- STRATOS_RECORD_INPUTS: this.recordInputs,
37
- STRATOS_RECORD_OUTPUTS: this.recordOutputs,
38
- NODE_OPTIONS: "--import @intelli-1113/stratos-sdk/register",
39
- OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
40
- },
41
- hooks: {
42
- PreToolUse: { enabled: true },
43
- PostToolUse: { enabled: true },
44
- SessionStart: { enabled: true },
45
- SessionStop: { enabled: true },
46
- SubagentStop: { enabled: true },
47
- UserPromptSubmit: { enabled: true },
48
- },
49
- };
50
- }
51
-
52
- toYaml() {
53
- const settings = this.toSettingsJson();
54
- return `# Stratos SDK Configuration
55
- # Generated for: ${this.personEmail}
56
- # Surface: ${this.surface}
57
- # OS: ${this.osType}
58
-
59
- surface: ${settings.surface}
60
- owner_email: ${settings.owner_email}
61
-
62
- env:
63
- ${Object.entries(settings.env)
64
- .map(([k, v]) => ` ${k}: ${v}`)
65
- .join("\n")}
66
-
67
- hooks:
68
- ${Object.entries(settings.hooks)
69
- .map(([k, v]) => ` ${k}: ${v.enabled}`)
70
- .join("\n")}
71
-
72
- content_off: ${this.contentOff}
73
- created_at: ${new Date().toISOString()}
74
- `;
75
- }
76
-
77
- toBundle() {
78
- return {
79
- version: "1.0",
80
- person: this.personEmail,
81
- surface: this.surface,
82
- os: this.osType,
83
- config: this.toSettingsJson(),
84
- env_file: this.toEnv(),
85
- yaml_template: this.toYaml(),
86
- metadata: {
87
- created_at: new Date().toISOString(),
88
- sdk_version: "1.4.0",
89
- },
90
- };
91
- }
92
-
93
- save(dirPath) {
94
- const fileName = `stratos-config-${this.personEmail.replace(/@.*/, "")}-${this.surface}`;
95
- const yamlPath = join(dirPath, `${fileName}.yaml`);
96
- const jsonPath = join(dirPath, `${fileName}.json`);
97
-
98
- writeFileSync(yamlPath, this.toYaml(), "utf8");
99
- writeFileSync(jsonPath, JSON.stringify(this.toBundle(), null, 2), "utf8");
100
-
101
- return { yamlPath, jsonPath };
102
- }
103
- }
104
-
105
- export class MDMBundleGenerator {
106
- constructor(opts = {}) {
107
- this.orgUrl = opts.orgUrl || "https://stratos.company.com";
108
- this.adminToken = opts.adminToken;
109
- this.templates = [];
110
- }
111
-
112
- addTemplate(template) {
113
- this.templates.push(template);
114
- }
115
-
116
- generateForPerson(personEmail, surfaces = ["claude-code", "claude-chat", "claude-cowork"]) {
117
- const templates = [];
118
- for (const surface of surfaces) {
119
- const template = new ConfigTemplate({
120
- personEmail,
121
- surface,
122
- url: this.orgUrl,
123
- });
124
- templates.push(template);
125
- }
126
- return templates;
127
- }
128
-
129
- toManifest() {
130
- return {
131
- version: "1.0",
132
- org_url: this.orgUrl,
133
- bundles: this.templates.map((t) => t.toBundle()),
134
- metadata: {
135
- created_at: new Date().toISOString(),
136
- total_bundles: this.templates.length,
137
- total_people: new Set(this.templates.map((t) => t.personEmail)).size,
138
- total_surfaces: new Set(this.templates.map((t) => t.surface)).size,
139
- },
140
- };
141
- }
142
-
143
- async postToEnrollment(enrollmentUrl) {
144
- if (!this.adminToken) {
145
- console.error("[mdm-bundle] Admin token required to post enrollment");
146
- return false;
147
- }
148
-
149
- try {
150
- const manifest = this.toManifest();
151
- const res = await fetch(`${enrollmentUrl}/api/mdm/bundle`, {
152
- method: "POST",
153
- headers: {
154
- "Content-Type": "application/json",
155
- Authorization: `Bearer ${this.adminToken}`,
156
- },
157
- body: JSON.stringify(manifest),
158
- });
159
-
160
- if (!res.ok) {
161
- console.error(`[mdm-bundle] Enrollment returned ${res.status}`);
162
- return false;
163
- }
164
-
165
- console.log("[mdm-bundle] Bundle posted to enrollment successfully");
166
- return true;
167
- } catch (err) {
168
- console.error(`[mdm-bundle] Failed to post: ${err.message}`);
169
- return false;
170
- }
171
- }
172
- }
173
-
174
- export function createDefaultMDMBundle(personEmails, orgUrl) {
175
- const generator = new MDMBundleGenerator({ orgUrl });
176
-
177
- for (const email of personEmails) {
178
- const templates = generator.generateForPerson(email);
179
- templates.forEach((t) => generator.addTemplate(t));
180
- }
181
-
182
- return generator;
183
- }
1
+ // Config bundles for rolling Stratos out to many developers (MDM / scripts).
2
+ //
3
+ // Claude Code reports through hooks (see src/claude-code/), so a bundle is:
4
+ // - the hook block for ~/.claude/settings.json or managed-settings.json
5
+ // - the per-developer ~/.stratos/config.json (token + URL)
6
+ // Tokens come from Stratos enrollment; they are never invented here.
7
+
8
+ import { platform } from "node:os";
9
+ import { readFileSync, writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { buildClaudeCodeHooks, MANAGED_SETTINGS_PATHS } from "./claude-code/settings.js";
12
+
13
+ const SDK_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
14
+ const SURFACES = ["claude-code"];
15
+
16
+ export class ConfigTemplate {
17
+ constructor(opts = {}) {
18
+ if (!opts.personEmail) throw new Error("ConfigTemplate: personEmail is required");
19
+ if (!opts.token) throw new Error("ConfigTemplate: token is required (issue one from Stratos enrollment)");
20
+ this.personEmail = opts.personEmail;
21
+ this.surface = opts.surface || "claude-code";
22
+ if (!SURFACES.includes(this.surface)) throw new Error(`ConfigTemplate: unsupported surface '${this.surface}'`);
23
+ this.token = opts.token;
24
+ this.url = (opts.url || "http://localhost:4000").replace(/\/+$/, "");
25
+ this.osType = opts.osType || platform();
26
+ this.recordContent = opts.recordContent === true;
27
+ // Paths of node and the SDK on the *target* machines, if they differ from this one.
28
+ this.hookPaths = { node: opts.nodePath, cli: opts.cliPath };
29
+ }
30
+
31
+ hooks() {
32
+ const o = {};
33
+ if (this.hookPaths.node) o.node = this.hookPaths.node;
34
+ if (this.hookPaths.cli) o.cli = this.hookPaths.cli;
35
+ return buildClaudeCodeHooks(o);
36
+ }
37
+
38
+ /** Contents of ~/.claude/settings.json additions (merge into existing hooks). */
39
+ toSettingsJson() {
40
+ return { hooks: this.hooks() };
41
+ }
42
+
43
+ /** Contents for managed-settings.json, enforced for every user on the machine. */
44
+ toManagedSettings() {
45
+ return { hooks: this.hooks() };
46
+ }
47
+
48
+ managedSettingsPath() {
49
+ return MANAGED_SETTINGS_PATHS[this.osType] || MANAGED_SETTINGS_PATHS.linux;
50
+ }
51
+
52
+ /** Contents of ~/.stratos/config.json for this developer. */
53
+ toStratosConfig() {
54
+ return {
55
+ token: this.token,
56
+ url: this.url,
57
+ appName: `claude-code:${this.personEmail.split("@")[0]}`,
58
+ recordInputs: this.recordContent,
59
+ recordOutputs: this.recordContent,
60
+ };
61
+ }
62
+
63
+ toBundle() {
64
+ return {
65
+ version: "2.0",
66
+ person: this.personEmail,
67
+ surface: this.surface,
68
+ os: this.osType,
69
+ claude_settings: this.toSettingsJson(),
70
+ managed_settings: { path: this.managedSettingsPath(), content: this.toManagedSettings() },
71
+ stratos_config: { path: "~/.stratos/config.json", content: this.toStratosConfig() },
72
+ metadata: { created_at: new Date().toISOString(), sdk_version: SDK_VERSION },
73
+ };
74
+ }
75
+
76
+ save(dirPath) {
77
+ const name = `stratos-config-${this.personEmail.replace(/@.*/, "")}-${this.surface}.json`;
78
+ const jsonPath = join(dirPath, name);
79
+ writeFileSync(jsonPath, JSON.stringify(this.toBundle(), null, 2), { encoding: "utf8", mode: 0o600 });
80
+ return { jsonPath };
81
+ }
82
+ }
83
+
84
+ export class MDMBundleGenerator {
85
+ constructor(opts = {}) {
86
+ this.orgUrl = opts.orgUrl || "http://localhost:4000";
87
+ this.adminToken = opts.adminToken;
88
+ this.templates = [];
89
+ }
90
+
91
+ addTemplate(template) {
92
+ this.templates.push(template);
93
+ }
94
+
95
+ /** @param {Record<string,string>} tokens surface → enrollment token for this person */
96
+ generateForPerson(personEmail, tokens, opts = {}) {
97
+ return Object.entries(tokens).map(
98
+ ([surface, token]) => new ConfigTemplate({ ...opts, personEmail, surface, token, url: this.orgUrl })
99
+ );
100
+ }
101
+
102
+ toManifest() {
103
+ return {
104
+ version: "2.0",
105
+ org_url: this.orgUrl,
106
+ bundles: this.templates.map((t) => t.toBundle()),
107
+ metadata: {
108
+ created_at: new Date().toISOString(),
109
+ sdk_version: SDK_VERSION,
110
+ total_bundles: this.templates.length,
111
+ total_people: new Set(this.templates.map((t) => t.personEmail)).size,
112
+ },
113
+ };
114
+ }
115
+
116
+ async postToEnrollment(enrollmentUrl) {
117
+ if (!this.adminToken) {
118
+ console.error("[mdm-bundle] Admin token required to post enrollment");
119
+ return false;
120
+ }
121
+ try {
122
+ const res = await fetch(`${enrollmentUrl.replace(/\/+$/, "")}/api/mdm/bundle`, {
123
+ method: "POST",
124
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.adminToken}` },
125
+ body: JSON.stringify(this.toManifest()),
126
+ signal: AbortSignal.timeout(10000),
127
+ });
128
+ if (!res.ok) {
129
+ console.error(`[mdm-bundle] Enrollment returned ${res.status}`);
130
+ return false;
131
+ }
132
+ console.log("[mdm-bundle] Bundle posted to enrollment successfully");
133
+ return true;
134
+ } catch (err) {
135
+ console.error(`[mdm-bundle] Failed to post: ${err.message}`);
136
+ return false;
137
+ }
138
+ }
139
+ }
140
+
141
+ /** @param {Record<string,string>} tokensByEmail email → claude-code enrollment token */
142
+ export function createDefaultMDMBundle(tokensByEmail, orgUrl) {
143
+ const generator = new MDMBundleGenerator({ orgUrl });
144
+ for (const [email, token] of Object.entries(tokensByEmail)) {
145
+ generator.generateForPerson(email, { "claude-code": token }).forEach((t) => generator.addTemplate(t));
146
+ }
147
+ return generator;
148
+ }