@stamn/agent 0.7.0 → 0.8.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.
@@ -266,7 +266,7 @@ var WSClient = class {
266
266
  const payload = {
267
267
  agentId: this.options.config.agentId,
268
268
  status,
269
- version: "0.7.0",
269
+ version: "0.8.0",
270
270
  platform: `${process.platform}-${process.arch}`,
271
271
  nodeVersion: process.versions.node
272
272
  };
@@ -373,4 +373,4 @@ export {
373
373
  WSClient,
374
374
  SpendClient
375
375
  };
376
- //# sourceMappingURL=chunk-J723GUNU.js.map
376
+ //# sourceMappingURL=chunk-6EKA3TV7.js.map
@@ -0,0 +1,8 @@
1
+ import { Command } from '@oclif/core';
2
+
3
+ declare class Login extends Command {
4
+ static description: string;
5
+ run(): Promise<void>;
6
+ }
7
+
8
+ export { Login as default };
@@ -0,0 +1,34 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ ConfigStore
4
+ } from "../chunk-KQC5O25P.js";
5
+ import "../chunk-JVIBUEVN.js";
6
+
7
+ // src/commands/login.ts
8
+ import { Command } from "@oclif/core";
9
+ var Login = class extends Command {
10
+ static description = "Log in to Stamn via the dashboard (device code flow)";
11
+ async run() {
12
+ if (!process.stdout.isTTY) {
13
+ this.error(
14
+ "Interactive login requires a terminal. Use `stamn start` with an existing config."
15
+ );
16
+ }
17
+ const { runDeviceLogin } = await import("../ui/device-login.js");
18
+ try {
19
+ const result = await runDeviceLogin();
20
+ const store = new ConfigStore();
21
+ store.set("apiKey", result.apiKey);
22
+ store.set("agentId", result.agentId);
23
+ store.set("agentName", result.agentName);
24
+ this.log(`
25
+ Agent "${result.agentName}" ready. Run \`stamn start\` to connect.`);
26
+ } catch (err) {
27
+ this.error(err.message || "Login cancelled.");
28
+ }
29
+ }
30
+ };
31
+ export {
32
+ Login as default
33
+ };
34
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import { Command } from '@oclif/core';\nimport { ConfigStore } from '../config/config-store.js';\n\nexport default class Login extends Command {\n static override description =\n 'Log in to Stamn via the dashboard (device code flow)';\n\n async run(): Promise<void> {\n if (!process.stdout.isTTY) {\n this.error(\n 'Interactive login requires a terminal. Use `stamn start` with an existing config.',\n );\n }\n\n const { runDeviceLogin } = await import('../ui/device-login.js');\n\n try {\n const result = await runDeviceLogin();\n const store = new ConfigStore();\n store.set('apiKey', result.apiKey);\n store.set('agentId', result.agentId);\n store.set('agentName', result.agentName);\n this.log(`\\nAgent \"${result.agentName}\" ready. Run \\`stamn start\\` to connect.`);\n } catch (err) {\n this.error((err as Error).message || 'Login cancelled.');\n }\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,eAAe;AAGxB,IAAqB,QAArB,cAAmC,QAAQ;AAAA,EACzC,OAAgB,cACd;AAAA,EAEF,MAAM,MAAqB;AACzB,QAAI,CAAC,QAAQ,OAAO,OAAO;AACzB,WAAK;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,uBAAuB;AAE/D,QAAI;AACF,YAAM,SAAS,MAAM,eAAe;AACpC,YAAM,QAAQ,IAAI,YAAY;AAC9B,YAAM,IAAI,UAAU,OAAO,MAAM;AACjC,YAAM,IAAI,WAAW,OAAO,OAAO;AACnC,YAAM,IAAI,aAAa,OAAO,SAAS;AACvC,WAAK,IAAI;AAAA,SAAY,OAAO,SAAS,0CAA0C;AAAA,IACjF,SAAS,KAAK;AACZ,WAAK,MAAO,IAAc,WAAW,kBAAkB;AAAA,IACzD;AAAA,EACF;AACF;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  SpendClient,
4
4
  WSClient,
5
5
  createLogger
6
- } from "../chunk-J723GUNU.js";
6
+ } from "../chunk-6EKA3TV7.js";
7
7
  import {
8
8
  ConfigStore
9
9
  } from "../chunk-KQC5O25P.js";
@@ -6,7 +6,7 @@ import {
6
6
  SpendClient,
7
7
  WSClient,
8
8
  createLogger
9
- } from "../chunk-J723GUNU.js";
9
+ } from "../chunk-6EKA3TV7.js";
10
10
  import {
11
11
  ConfigStore
12
12
  } from "../chunk-KQC5O25P.js";
@@ -38,19 +38,19 @@ var Start = class _Start extends Command {
38
38
  if (!config.apiKey || !config.agentId) {
39
39
  if (flags.daemon || !process.stdout.isTTY) {
40
40
  this.error(
41
- "Not registered. Run `stamn start` interactively first."
41
+ "Not registered. Run `stamn login` interactively first."
42
42
  );
43
43
  }
44
- const { runSetup } = await import("../ui/setup.js");
44
+ const { runDeviceLogin } = await import("../ui/device-login.js");
45
45
  try {
46
- const result = await runSetup();
46
+ const result = await runDeviceLogin();
47
47
  configStore.set("apiKey", result.apiKey);
48
48
  configStore.set("agentId", result.agentId);
49
49
  configStore.set("agentName", result.agentName);
50
50
  config.apiKey = result.apiKey;
51
51
  config.agentId = result.agentId;
52
52
  } catch (err) {
53
- this.error(err.message || "Setup cancelled.");
53
+ this.error(err.message || "Login cancelled.");
54
54
  }
55
55
  }
56
56
  const dm = new DaemonManager();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/start.ts"],"sourcesContent":["import { Command, Flags } from '@oclif/core';\nimport { ConfigStore } from '../config/config-store.js';\nimport { SERVER_URL, type AgentConfig } from '../config/config-schema.js';\nimport { createLogger } from '../logging/logger.js';\nimport { WSClient } from '../ws/ws-client.js';\nimport { SpendClient } from '../spend/spend-client.js';\nimport { DaemonManager } from '../daemon/daemon-manager.js';\n\nexport default class Start extends Command {\n static override description = 'Start the Stamn agent daemon';\n\n static override flags = {\n daemon: Flags.boolean({\n char: 'd',\n description: 'Run as background daemon',\n default: false,\n }),\n 'log-level': Flags.string({\n description: 'Override log level',\n options: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'],\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Start);\n const configStore = new ConfigStore();\n const config = { ...configStore.getAll() };\n\n // Apply flag overrides\n if (flags['log-level'])\n config.logLevel = flags['log-level'] as AgentConfig['logLevel'];\n\n // Interactive setup when not registered\n if (!config.apiKey || !config.agentId) {\n if (flags.daemon || !process.stdout.isTTY) {\n this.error(\n 'Not registered. Run `stamn start` interactively first.',\n );\n }\n\n const { runSetup } = await import('../ui/setup.js');\n try {\n const result = await runSetup();\n configStore.set('apiKey', result.apiKey);\n configStore.set('agentId', result.agentId);\n configStore.set('agentName', result.agentName);\n config.apiKey = result.apiKey;\n config.agentId = result.agentId;\n } catch (err) {\n this.error((err as Error).message || 'Setup cancelled.');\n }\n }\n\n // Check for existing daemon\n const dm = new DaemonManager();\n const { running, pid } = dm.isRunning();\n if (running) {\n this.error(`Daemon already running (PID ${pid})`);\n }\n\n // Daemonize if requested\n if (flags.daemon) {\n const { daemonizeProcess } = await import('../daemon/process.js');\n await daemonizeProcess();\n }\n\n // Write PID\n dm.writePid(process.pid);\n\n const logger = createLogger(config);\n logger.info(\n { agentId: config.agentId, server: SERVER_URL },\n 'Starting Stamn agent',\n );\n\n // Create WebSocket client\n const client = new WSClient({\n config,\n logger,\n onCommand: (command, params) => {\n logger.info({ command, params }, 'Received command');\n if (command === 'shutdown') {\n shutdown();\n }\n },\n onDisconnect: () => {\n logger.warn('Disconnected from server');\n },\n onConnected: () => {\n logger.info('Agent is online and ready — spend capability active');\n },\n });\n\n // Create spend client — available for plugin/task integration\n const spendClient = new SpendClient(client, logger);\n\n // Expose on process for external plugin access\n (globalThis as Record<string, unknown>).__stamnSpendClient = spendClient;\n\n // Graceful shutdown\n const shutdown = () => {\n logger.info('Shutting down...');\n client.disconnect();\n dm.removePid();\n process.exit(0);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n // Connect\n client.connect();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,aAAa;AAQ/B,IAAqB,QAArB,MAAqB,eAAc,QAAQ;AAAA,EACzC,OAAgB,cAAc;AAAA,EAE9B,OAAgB,QAAQ;AAAA,IACtB,QAAQ,MAAM,QAAQ;AAAA,MACpB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,IACD,aAAa,MAAM,OAAO;AAAA,MACxB,aAAa;AAAA,MACb,SAAS,CAAC,SAAS,SAAS,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,MAAK;AACxC,UAAM,cAAc,IAAI,YAAY;AACpC,UAAM,SAAS,EAAE,GAAG,YAAY,OAAO,EAAE;AAGzC,QAAI,MAAM,WAAW;AACnB,aAAO,WAAW,MAAM,WAAW;AAGrC,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,SAAS;AACrC,UAAI,MAAM,UAAU,CAAC,QAAQ,OAAO,OAAO;AACzC,aAAK;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAEA,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,gBAAgB;AAClD,UAAI;AACF,cAAM,SAAS,MAAM,SAAS;AAC9B,oBAAY,IAAI,UAAU,OAAO,MAAM;AACvC,oBAAY,IAAI,WAAW,OAAO,OAAO;AACzC,oBAAY,IAAI,aAAa,OAAO,SAAS;AAC7C,eAAO,SAAS,OAAO;AACvB,eAAO,UAAU,OAAO;AAAA,MAC1B,SAAS,KAAK;AACZ,aAAK,MAAO,IAAc,WAAW,kBAAkB;AAAA,MACzD;AAAA,IACF;AAGA,UAAM,KAAK,IAAI,cAAc;AAC7B,UAAM,EAAE,SAAS,IAAI,IAAI,GAAG,UAAU;AACtC,QAAI,SAAS;AACX,WAAK,MAAM,+BAA+B,GAAG,GAAG;AAAA,IAClD;AAGA,QAAI,MAAM,QAAQ;AAChB,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAsB;AAChE,YAAM,iBAAiB;AAAA,IACzB;AAGA,OAAG,SAAS,QAAQ,GAAG;AAEvB,UAAM,SAAS,aAAa,MAAM;AAClC,WAAO;AAAA,MACL,EAAE,SAAS,OAAO,SAAS,QAAQ,WAAW;AAAA,MAC9C;AAAA,IACF;AAGA,UAAM,SAAS,IAAI,SAAS;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,WAAW,CAAC,SAAS,WAAW;AAC9B,eAAO,KAAK,EAAE,SAAS,OAAO,GAAG,kBAAkB;AACnD,YAAI,YAAY,YAAY;AAC1B,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,MACA,cAAc,MAAM;AAClB,eAAO,KAAK,0BAA0B;AAAA,MACxC;AAAA,MACA,aAAa,MAAM;AACjB,eAAO,KAAK,0DAAqD;AAAA,MACnE;AAAA,IACF,CAAC;AAGD,UAAM,cAAc,IAAI,YAAY,QAAQ,MAAM;AAGlD,IAAC,WAAuC,qBAAqB;AAG7D,UAAM,WAAW,MAAM;AACrB,aAAO,KAAK,kBAAkB;AAC9B,aAAO,WAAW;AAClB,SAAG,UAAU;AACb,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,GAAG,WAAW,QAAQ;AAC9B,YAAQ,GAAG,UAAU,QAAQ;AAG7B,WAAO,QAAQ;AAAA,EACjB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/commands/start.ts"],"sourcesContent":["import { Command, Flags } from '@oclif/core';\nimport { ConfigStore } from '../config/config-store.js';\nimport { SERVER_URL, type AgentConfig } from '../config/config-schema.js';\nimport { createLogger } from '../logging/logger.js';\nimport { WSClient } from '../ws/ws-client.js';\nimport { SpendClient } from '../spend/spend-client.js';\nimport { DaemonManager } from '../daemon/daemon-manager.js';\n\nexport default class Start extends Command {\n static override description = 'Start the Stamn agent daemon';\n\n static override flags = {\n daemon: Flags.boolean({\n char: 'd',\n description: 'Run as background daemon',\n default: false,\n }),\n 'log-level': Flags.string({\n description: 'Override log level',\n options: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'],\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Start);\n const configStore = new ConfigStore();\n const config = { ...configStore.getAll() };\n\n // Apply flag overrides\n if (flags['log-level'])\n config.logLevel = flags['log-level'] as AgentConfig['logLevel'];\n\n // Interactive login when not registered\n if (!config.apiKey || !config.agentId) {\n if (flags.daemon || !process.stdout.isTTY) {\n this.error(\n 'Not registered. Run `stamn login` interactively first.',\n );\n }\n\n const { runDeviceLogin } = await import('../ui/device-login.js');\n try {\n const result = await runDeviceLogin();\n configStore.set('apiKey', result.apiKey);\n configStore.set('agentId', result.agentId);\n configStore.set('agentName', result.agentName);\n config.apiKey = result.apiKey;\n config.agentId = result.agentId;\n } catch (err) {\n this.error((err as Error).message || 'Login cancelled.');\n }\n }\n\n // Check for existing daemon\n const dm = new DaemonManager();\n const { running, pid } = dm.isRunning();\n if (running) {\n this.error(`Daemon already running (PID ${pid})`);\n }\n\n // Daemonize if requested\n if (flags.daemon) {\n const { daemonizeProcess } = await import('../daemon/process.js');\n await daemonizeProcess();\n }\n\n // Write PID\n dm.writePid(process.pid);\n\n const logger = createLogger(config);\n logger.info(\n { agentId: config.agentId, server: SERVER_URL },\n 'Starting Stamn agent',\n );\n\n // Create WebSocket client\n const client = new WSClient({\n config,\n logger,\n onCommand: (command, params) => {\n logger.info({ command, params }, 'Received command');\n if (command === 'shutdown') {\n shutdown();\n }\n },\n onDisconnect: () => {\n logger.warn('Disconnected from server');\n },\n onConnected: () => {\n logger.info('Agent is online and ready — spend capability active');\n },\n });\n\n // Create spend client — available for plugin/task integration\n const spendClient = new SpendClient(client, logger);\n\n // Expose on process for external plugin access\n (globalThis as Record<string, unknown>).__stamnSpendClient = spendClient;\n\n // Graceful shutdown\n const shutdown = () => {\n logger.info('Shutting down...');\n client.disconnect();\n dm.removePid();\n process.exit(0);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n // Connect\n client.connect();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,aAAa;AAQ/B,IAAqB,QAArB,MAAqB,eAAc,QAAQ;AAAA,EACzC,OAAgB,cAAc;AAAA,EAE9B,OAAgB,QAAQ;AAAA,IACtB,QAAQ,MAAM,QAAQ;AAAA,MACpB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,IACD,aAAa,MAAM,OAAO;AAAA,MACxB,aAAa;AAAA,MACb,SAAS,CAAC,SAAS,SAAS,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,MAAK;AACxC,UAAM,cAAc,IAAI,YAAY;AACpC,UAAM,SAAS,EAAE,GAAG,YAAY,OAAO,EAAE;AAGzC,QAAI,MAAM,WAAW;AACnB,aAAO,WAAW,MAAM,WAAW;AAGrC,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,SAAS;AACrC,UAAI,MAAM,UAAU,CAAC,QAAQ,OAAO,OAAO;AACzC,aAAK;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAEA,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,uBAAuB;AAC/D,UAAI;AACF,cAAM,SAAS,MAAM,eAAe;AACpC,oBAAY,IAAI,UAAU,OAAO,MAAM;AACvC,oBAAY,IAAI,WAAW,OAAO,OAAO;AACzC,oBAAY,IAAI,aAAa,OAAO,SAAS;AAC7C,eAAO,SAAS,OAAO;AACvB,eAAO,UAAU,OAAO;AAAA,MAC1B,SAAS,KAAK;AACZ,aAAK,MAAO,IAAc,WAAW,kBAAkB;AAAA,MACzD;AAAA,IACF;AAGA,UAAM,KAAK,IAAI,cAAc;AAC7B,UAAM,EAAE,SAAS,IAAI,IAAI,GAAG,UAAU;AACtC,QAAI,SAAS;AACX,WAAK,MAAM,+BAA+B,GAAG,GAAG;AAAA,IAClD;AAGA,QAAI,MAAM,QAAQ;AAChB,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,wBAAsB;AAChE,YAAM,iBAAiB;AAAA,IACzB;AAGA,OAAG,SAAS,QAAQ,GAAG;AAEvB,UAAM,SAAS,aAAa,MAAM;AAClC,WAAO;AAAA,MACL,EAAE,SAAS,OAAO,SAAS,QAAQ,WAAW;AAAA,MAC9C;AAAA,IACF;AAGA,UAAM,SAAS,IAAI,SAAS;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,WAAW,CAAC,SAAS,WAAW;AAC9B,eAAO,KAAK,EAAE,SAAS,OAAO,GAAG,kBAAkB;AACnD,YAAI,YAAY,YAAY;AAC1B,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,MACA,cAAc,MAAM;AAClB,eAAO,KAAK,0BAA0B;AAAA,MACxC;AAAA,MACA,aAAa,MAAM;AACjB,eAAO,KAAK,0DAAqD;AAAA,MACnE;AAAA,IACF,CAAC;AAGD,UAAM,cAAc,IAAI,YAAY,QAAQ,MAAM;AAGlD,IAAC,WAAuC,qBAAqB;AAG7D,UAAM,WAAW,MAAM;AACrB,aAAO,KAAK,kBAAkB;AAC9B,aAAO,WAAW;AAClB,SAAG,UAAU;AACb,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,GAAG,WAAW,QAAQ;AAC9B,YAAQ,GAAG,UAAU,QAAQ;AAG7B,WAAO,QAAQ;AAAA,EACjB;AACF;","names":[]}
@@ -6,13 +6,13 @@ import { execSync } from "child_process";
6
6
  var Update = class extends Command {
7
7
  static description = "Update the Stamn agent to the latest version";
8
8
  async run() {
9
- this.log(`Current version: ${"0.7.0"}`);
9
+ this.log(`Current version: ${"0.8.0"}`);
10
10
  this.log("Checking for updates...");
11
11
  try {
12
12
  const latest = execSync("npm view @stamn/agent version", {
13
13
  encoding: "utf-8"
14
14
  }).trim();
15
- if (latest === "0.7.0") {
15
+ if (latest === "0.8.0") {
16
16
  this.log("Already on the latest version.");
17
17
  return;
18
18
  }
@@ -0,0 +1,8 @@
1
+ interface LoginResult {
2
+ agentId: string;
3
+ apiKey: string;
4
+ agentName: string;
5
+ }
6
+ declare function runDeviceLogin(): Promise<LoginResult>;
7
+
8
+ export { type LoginResult, runDeviceLogin };
@@ -0,0 +1,153 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+
3
+ // src/ui/device-login.tsx
4
+ import { render } from "ink";
5
+
6
+ // src/ui/components/device-login-wizard.tsx
7
+ import { useState, useEffect } from "react";
8
+ import { Box, Text } from "ink";
9
+ import { Spinner, StatusMessage } from "@inkjs/ui";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var POLL_INTERVAL_MS = 5e3;
12
+ function DeviceLoginWizard({ onComplete, onError }) {
13
+ const [step, setStep] = useState("requesting");
14
+ const [flow, setFlow] = useState(null);
15
+ useEffect(() => {
16
+ let cancelled = false;
17
+ const initiate = async () => {
18
+ try {
19
+ const { SERVER_URL } = await import("../config-schema-BR4LGISX.js");
20
+ const res = await fetch(`${SERVER_URL}/v1/auth/device-codes`, {
21
+ method: "POST"
22
+ });
23
+ if (!res.ok) {
24
+ const body = await res.text();
25
+ throw new Error(body || `HTTP ${res.status}`);
26
+ }
27
+ const json = await res.json();
28
+ if (!cancelled) {
29
+ setFlow(json.data);
30
+ setStep("waiting");
31
+ }
32
+ } catch (err) {
33
+ if (cancelled) return;
34
+ const msg = err.message;
35
+ if (msg === "fetch failed" || msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
36
+ onError("Could not connect to Stamn. Check your network.");
37
+ } else {
38
+ onError(msg);
39
+ }
40
+ }
41
+ };
42
+ initiate();
43
+ return () => {
44
+ cancelled = true;
45
+ };
46
+ }, []);
47
+ useEffect(() => {
48
+ if (step !== "waiting" || !flow) return;
49
+ let cancelled = false;
50
+ const poll = async () => {
51
+ try {
52
+ const { SERVER_URL } = await import("../config-schema-BR4LGISX.js");
53
+ const res = await fetch(
54
+ `${SERVER_URL}/v1/auth/device-codes/${flow.deviceCode}`
55
+ );
56
+ if (!res.ok) return;
57
+ const json = await res.json();
58
+ if (cancelled) return;
59
+ if (json.data.status === "approved" && json.data.apiKey) {
60
+ setStep("registering");
61
+ await registerAgent(json.data.apiKey);
62
+ } else if (json.data.status === "expired") {
63
+ onError("Login code expired. Run `stamn login` again.");
64
+ }
65
+ } catch {
66
+ }
67
+ };
68
+ const interval = setInterval(poll, POLL_INTERVAL_MS);
69
+ poll();
70
+ return () => {
71
+ cancelled = true;
72
+ clearInterval(interval);
73
+ };
74
+ }, [step, flow]);
75
+ const registerAgent = async (apiKey) => {
76
+ try {
77
+ const { SERVER_URL } = await import("../config-schema-BR4LGISX.js");
78
+ const res = await fetch(`${SERVER_URL}/v1/agents/register`, {
79
+ method: "POST",
80
+ headers: {
81
+ "Content-Type": "application/json",
82
+ Authorization: `Bearer ${apiKey}`
83
+ },
84
+ body: JSON.stringify({
85
+ platform: `${process.platform}-${process.arch}`
86
+ })
87
+ });
88
+ if (!res.ok) {
89
+ const body = await res.text();
90
+ throw new Error(body || `HTTP ${res.status}`);
91
+ }
92
+ const json = await res.json();
93
+ setStep("done");
94
+ onComplete({
95
+ agentId: json.data.id,
96
+ apiKey,
97
+ agentName: json.data.name
98
+ });
99
+ } catch (err) {
100
+ onError(err.message || "Registration failed");
101
+ }
102
+ };
103
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
104
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
105
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "stamn" }),
106
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " login" })
107
+ ] }),
108
+ step === "requesting" && /* @__PURE__ */ jsx(Spinner, { label: "Requesting login code..." }),
109
+ step === "waiting" && flow && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
110
+ /* @__PURE__ */ jsxs(Text, { children: [
111
+ "Open",
112
+ " ",
113
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: flow.verificationUri })
114
+ ] }),
115
+ /* @__PURE__ */ jsxs(Text, { children: [
116
+ "and enter code:",
117
+ " ",
118
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: flow.userCode })
119
+ ] }),
120
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Spinner, { label: "Waiting for approval..." }) }),
121
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Ctrl+C to cancel" }) })
122
+ ] }),
123
+ step === "registering" && /* @__PURE__ */ jsx(Spinner, { label: "Approved! Registering agent..." }),
124
+ step === "done" && /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: "Logged in and agent registered." })
125
+ ] });
126
+ }
127
+
128
+ // src/ui/device-login.tsx
129
+ import { jsx as jsx2 } from "react/jsx-runtime";
130
+ function runDeviceLogin() {
131
+ return new Promise((resolve, reject) => {
132
+ const { unmount, waitUntilExit } = render(
133
+ /* @__PURE__ */ jsx2(
134
+ DeviceLoginWizard,
135
+ {
136
+ onComplete: (result) => {
137
+ unmount();
138
+ resolve(result);
139
+ },
140
+ onError: (message) => {
141
+ unmount();
142
+ reject(new Error(message));
143
+ }
144
+ }
145
+ )
146
+ );
147
+ waitUntilExit().catch(reject);
148
+ });
149
+ }
150
+ export {
151
+ runDeviceLogin
152
+ };
153
+ //# sourceMappingURL=device-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/ui/device-login.tsx","../../src/ui/components/device-login-wizard.tsx"],"sourcesContent":["import { render } from 'ink';\nimport { DeviceLoginWizard } from './components/device-login-wizard.js';\n\nexport interface LoginResult {\n agentId: string;\n apiKey: string;\n agentName: string;\n}\n\nexport function runDeviceLogin(): Promise<LoginResult> {\n return new Promise((resolve, reject) => {\n const { unmount, waitUntilExit } = render(\n <DeviceLoginWizard\n onComplete={(result) => {\n unmount();\n resolve(result);\n }}\n onError={(message) => {\n unmount();\n reject(new Error(message));\n }}\n />,\n );\n\n waitUntilExit().catch(reject);\n });\n}\n","import { useState, useEffect } from 'react';\nimport { Box, Text } from 'ink';\nimport { Spinner, StatusMessage } from '@inkjs/ui';\n\ntype Step = 'requesting' | 'waiting' | 'registering' | 'done';\n\ninterface DeviceFlowData {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n expiresIn: number;\n}\n\ninterface Props {\n onComplete: (result: {\n agentId: string;\n apiKey: string;\n agentName: string;\n }) => void;\n onError: (message: string) => void;\n}\n\ndeclare const AGENT_VERSION: string;\n\nconst POLL_INTERVAL_MS = 5_000;\n\nexport function DeviceLoginWizard({ onComplete, onError }: Props) {\n const [step, setStep] = useState<Step>('requesting');\n const [flow, setFlow] = useState<DeviceFlowData | null>(null);\n\n // Step 1: Initiate device flow\n useEffect(() => {\n let cancelled = false;\n\n const initiate = async () => {\n try {\n const { SERVER_URL } = await import(\n '../../config/config-schema.js'\n );\n const res = await fetch(`${SERVER_URL}/v1/auth/device-codes`, {\n method: 'POST',\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(body || `HTTP ${res.status}`);\n }\n\n const json = (await res.json()) as { data: DeviceFlowData };\n if (!cancelled) {\n setFlow(json.data);\n setStep('waiting');\n }\n } catch (err) {\n if (cancelled) return;\n const msg = (err as Error).message;\n if (\n msg === 'fetch failed' ||\n msg.includes('ECONNREFUSED') ||\n msg.includes('ENOTFOUND')\n ) {\n onError('Could not connect to Stamn. Check your network.');\n } else {\n onError(msg);\n }\n }\n };\n\n initiate();\n return () => {\n cancelled = true;\n };\n }, []);\n\n // Step 2: Poll for approval\n useEffect(() => {\n if (step !== 'waiting' || !flow) return;\n\n let cancelled = false;\n\n const poll = async () => {\n try {\n const { SERVER_URL } = await import(\n '../../config/config-schema.js'\n );\n const res = await fetch(\n `${SERVER_URL}/v1/auth/device-codes/${flow.deviceCode}`,\n );\n\n if (!res.ok) return;\n\n const json = (await res.json()) as {\n data: { status: string; apiKey?: string };\n };\n\n if (cancelled) return;\n\n if (json.data.status === 'approved' && json.data.apiKey) {\n setStep('registering');\n await registerAgent(json.data.apiKey);\n } else if (json.data.status === 'expired') {\n onError('Login code expired. Run `stamn login` again.');\n }\n } catch {\n // Silently retry on network errors during polling\n }\n };\n\n const interval = setInterval(poll, POLL_INTERVAL_MS);\n // First poll immediately\n poll();\n\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [step, flow]);\n\n const registerAgent = async (apiKey: string) => {\n try {\n const { SERVER_URL } = await import(\n '../../config/config-schema.js'\n );\n const res = await fetch(`${SERVER_URL}/v1/agents/register`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n platform: `${process.platform}-${process.arch}`,\n }),\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(body || `HTTP ${res.status}`);\n }\n\n const json = (await res.json()) as {\n data: { id: string; name: string };\n };\n\n setStep('done');\n onComplete({\n agentId: json.data.id,\n apiKey,\n agentName: json.data.name,\n });\n } catch (err) {\n onError((err as Error).message || 'Registration failed');\n }\n };\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n stamn\n </Text>\n <Text dimColor> login</Text>\n </Box>\n\n {step === 'requesting' && (\n <Spinner label=\"Requesting login code...\" />\n )}\n\n {step === 'waiting' && flow && (\n <Box flexDirection=\"column\">\n <Text>\n Open{' '}\n <Text bold color=\"cyan\">\n {flow.verificationUri}\n </Text>\n </Text>\n <Text>\n and enter code:{' '}\n <Text bold color=\"yellow\">\n {flow.userCode}\n </Text>\n </Text>\n <Box marginTop={1}>\n <Spinner label=\"Waiting for approval...\" />\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Press Ctrl+C to cancel</Text>\n </Box>\n </Box>\n )}\n\n {step === 'registering' && (\n <Spinner label=\"Approved! Registering agent...\" />\n )}\n\n {step === 'done' && (\n <StatusMessage variant=\"success\">\n Logged in and agent registered.\n </StatusMessage>\n )}\n </Box>\n );\n}\n"],"mappings":"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","names":["jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamn/agent",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Stamn Agent Daemon CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",