kindflow 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -28,24 +28,39 @@ Kindflow is an opinionated platform for running AI agents as independent OS proc
28
28
 
29
29
  ## Install
30
30
 
31
+ ```bash
32
+ curl -fsSL https://raw.githubusercontent.com/surajpratap/kindflow/main/install.sh | sh
33
+ ```
34
+
35
+ Or install directly:
36
+
31
37
  ```bash
32
38
  npm install -g kindflow
33
39
  ```
34
40
 
41
+ ### Prerequisites
42
+
43
+ - [Node.js](https://nodejs.org) >= 18
44
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
45
+ - [bun](https://bun.sh) (for running agent scripts)
46
+
35
47
  ## Quick Start
36
48
 
37
49
  ```bash
38
- # Start the daemon (API server on port 7777)
50
+ # Start the daemon (runs in background)
39
51
  kindflow start
40
52
 
41
- # Start with the web UI (port 7001)
53
+ # Start with the web UI
42
54
  kindflow start --web
43
55
 
44
56
  # Create an agent
45
57
  kindflow create my-first-agent
46
58
 
47
- # List all agents
48
- kindflow list
59
+ # Check status
60
+ kindflow status
61
+
62
+ # Stop the daemon
63
+ kindflow stop
49
64
  ```
50
65
 
51
66
  Open http://localhost:7001 to access the dashboard, create agents, and configure them with AI.
@@ -146,11 +161,16 @@ All config and credentials are stored in `~/.kindflow/` (never in the repo):
146
161
  ## CLI Reference
147
162
 
148
163
  ```
149
- kindflow start [options] Start the daemon
164
+ kindflow start [options] Start the daemon (background)
150
165
  --web Also start the web UI (port 7001)
151
166
  --port <n> Override the API port
152
167
  --web-port <n> Override the web UI port
153
168
  --agents-dir <path> Override the agents directory
169
+ --foreground Run in the foreground (don't daemonize)
170
+
171
+ kindflow stop Stop the daemon
172
+
173
+ kindflow status Show daemon status (PID, ports, logs)
154
174
 
155
175
  kindflow create <name> Create a new agent from template
156
176
 
@@ -0,0 +1,102 @@
1
+ // ../daemon/src/agent-scanner.ts
2
+ import { resolve, join } from "path";
3
+ import { readdirSync, readFileSync, writeFileSync, existsSync, cpSync, rmSync } from "fs";
4
+ var generateSlug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
5
+ var scanAgents = (agentsDir) => {
6
+ if (!existsSync(agentsDir)) return [];
7
+ const agents = [];
8
+ for (const name of readdirSync(agentsDir, { withFileTypes: true })) {
9
+ if (!name.isDirectory()) continue;
10
+ const configPath = join(agentsDir, name.name, "agent", "config.json");
11
+ if (!existsSync(configPath)) continue;
12
+ try {
13
+ const raw = readFileSync(configPath, "utf-8");
14
+ const config = JSON.parse(raw);
15
+ const dir = join(agentsDir, name.name);
16
+ agents.push({
17
+ id: name.name,
18
+ dir,
19
+ config,
20
+ sdkOptions: {
21
+ cwd: join(dir, "agent"),
22
+ model: config.model ?? void 0,
23
+ permissionMode: config.permissionMode ?? "bypassPermissions",
24
+ allowDangerouslySkipPermissions: config.permissionMode === "bypassPermissions"
25
+ }
26
+ });
27
+ } catch {
28
+ console.warn(`[scanner] Failed to read config for ${name.name}, skipping`);
29
+ }
30
+ }
31
+ return agents;
32
+ };
33
+ var readAgent = (agentsDir, id) => {
34
+ const dir = resolve(agentsDir, id);
35
+ const configPath = join(dir, "agent", "config.json");
36
+ if (!existsSync(configPath)) return void 0;
37
+ try {
38
+ const raw = readFileSync(configPath, "utf-8");
39
+ const config = JSON.parse(raw);
40
+ return {
41
+ id,
42
+ dir,
43
+ config,
44
+ sdkOptions: {
45
+ cwd: join(dir, "agent"),
46
+ model: config.model ?? void 0,
47
+ permissionMode: config.permissionMode ?? "bypassPermissions",
48
+ allowDangerouslySkipPermissions: config.permissionMode === "bypassPermissions"
49
+ }
50
+ };
51
+ } catch {
52
+ return void 0;
53
+ }
54
+ };
55
+ var writeAgentConfig = (agentsDir, id, config) => {
56
+ const configPath = resolve(agentsDir, id, "agent", "config.json");
57
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
58
+ };
59
+ var createAgentFromTemplate = (agentsDir, templateDir, id, name, autoStart = true) => {
60
+ const agentDir = resolve(agentsDir, id);
61
+ cpSync(templateDir, agentDir, { recursive: true });
62
+ const config = {
63
+ name,
64
+ autoStart,
65
+ model: null,
66
+ permissionMode: "bypassPermissions",
67
+ env: {}
68
+ };
69
+ writeAgentConfig(agentsDir, id, config);
70
+ return {
71
+ id,
72
+ dir: agentDir,
73
+ config,
74
+ sdkOptions: {
75
+ cwd: join(agentDir, "agent"),
76
+ permissionMode: "bypassPermissions",
77
+ allowDangerouslySkipPermissions: true
78
+ }
79
+ };
80
+ };
81
+ var listAgentCommands = (agentsDir, id) => {
82
+ const commandsDir = resolve(agentsDir, id, "agent", ".claude", "commands");
83
+ if (!existsSync(commandsDir)) return [];
84
+ return readdirSync(commandsDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
85
+ };
86
+ var deleteAgentDir = (agentsDir, id) => {
87
+ const agentDir = resolve(agentsDir, id);
88
+ if (existsSync(agentDir)) {
89
+ rmSync(agentDir, { recursive: true, force: true });
90
+ }
91
+ };
92
+
93
+ export {
94
+ generateSlug,
95
+ scanAgents,
96
+ readAgent,
97
+ writeAgentConfig,
98
+ createAgentFromTemplate,
99
+ listAgentCommands,
100
+ deleteAgentDir
101
+ };
102
+ //# sourceMappingURL=chunk-IIHUCTI3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../daemon/src/agent-scanner.ts"],"sourcesContent":["import { resolve, join } from 'path';\nimport { readdirSync, readFileSync, writeFileSync, existsSync, cpSync, rmSync, mkdirSync } from 'fs';\nimport type { AgentConfig, AgentFileConfig } from '@repo/types';\n\n/**\n * Generate a URL-safe slug from a human-readable name.\n * Lowercases, replaces non-alphanumeric chars with hyphens, strips leading/trailing hyphens.\n *\n * @param name - the raw name to slugify\n * @returns a lowercase hyphen-separated slug\n */\nexport const generateSlug = (name: string): string =>\n name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n\n/**\n * Scan the agents directory and discover all agents.\n * An agent is any directory that has an agent/config.json file.\n *\n * @param agentsDir - absolute path to the agents directory\n * @returns array of AgentConfig objects\n */\nexport const scanAgents = (agentsDir: string): AgentConfig[] => {\n if (!existsSync(agentsDir)) return [];\n\n const agents: AgentConfig[] = [];\n\n for (const name of readdirSync(agentsDir, { withFileTypes: true })) {\n if (!name.isDirectory()) continue;\n\n const configPath = join(agentsDir, name.name, 'agent', 'config.json');\n if (!existsSync(configPath)) continue;\n\n try {\n const raw = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(raw) as AgentFileConfig;\n const dir = join(agentsDir, name.name);\n\n agents.push({\n id: name.name,\n dir,\n config,\n sdkOptions: {\n cwd: join(dir, 'agent'),\n model: config.model ?? undefined,\n permissionMode: (config.permissionMode as 'bypassPermissions') ?? 'bypassPermissions',\n allowDangerouslySkipPermissions: config.permissionMode === 'bypassPermissions',\n },\n });\n } catch {\n console.warn(`[scanner] Failed to read config for ${name.name}, skipping`);\n }\n }\n\n return agents;\n};\n\n/**\n * Read a single agent's config from the filesystem.\n *\n * @param agentsDir - absolute path to the agents directory\n * @param id - the agent directory name\n * @returns AgentConfig or undefined if not found\n */\nexport const readAgent = (agentsDir: string, id: string): AgentConfig | undefined => {\n const dir = resolve(agentsDir, id);\n const configPath = join(dir, 'agent', 'config.json');\n if (!existsSync(configPath)) return undefined;\n\n try {\n const raw = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(raw) as AgentFileConfig;\n\n return {\n id,\n dir,\n config,\n sdkOptions: {\n cwd: join(dir, 'agent'),\n model: config.model ?? undefined,\n permissionMode: (config.permissionMode as 'bypassPermissions') ?? 'bypassPermissions',\n allowDangerouslySkipPermissions: config.permissionMode === 'bypassPermissions',\n },\n };\n } catch {\n return undefined;\n }\n};\n\n/**\n * Write an agent's config.json to disk.\n *\n * @param agentsDir - absolute path to the agents directory\n * @param id - the agent directory name\n * @param config - the config to write\n */\nexport const writeAgentConfig = (agentsDir: string, id: string, config: AgentFileConfig): void => {\n const configPath = resolve(agentsDir, id, 'agent', 'config.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n};\n\n/**\n * Create a new agent by copying the template and writing config.json.\n *\n * @param agentsDir - absolute path to the agents directory\n * @param templateDir - absolute path to the agent template directory\n * @param id - the agent directory name (slug)\n * @param name - human-readable agent name\n * @param autoStart - whether to auto-start on daemon boot\n * @returns the new AgentConfig\n */\nexport const createAgentFromTemplate = (\n agentsDir: string,\n templateDir: string,\n id: string,\n name: string,\n autoStart = true,\n): AgentConfig => {\n const agentDir = resolve(agentsDir, id);\n\n /* Copy template. */\n cpSync(templateDir, agentDir, { recursive: true });\n\n /* Write config.json with the name. */\n const config: AgentFileConfig = {\n name,\n autoStart,\n model: null,\n permissionMode: 'bypassPermissions',\n env: {},\n };\n\n writeAgentConfig(agentsDir, id, config);\n\n return {\n id,\n dir: agentDir,\n config,\n sdkOptions: {\n cwd: join(agentDir, 'agent'),\n permissionMode: 'bypassPermissions',\n allowDangerouslySkipPermissions: true,\n },\n };\n};\n\n/**\n * List available commands for an agent by scanning .claude/commands/ directory.\n *\n * @param agentsDir - absolute path to the agents directory\n * @param id - the agent directory name\n * @returns array of command names (without .md extension)\n */\nexport const listAgentCommands = (agentsDir: string, id: string): string[] => {\n const commandsDir = resolve(agentsDir, id, 'agent', '.claude', 'commands');\n if (!existsSync(commandsDir)) return [];\n\n return readdirSync(commandsDir)\n .filter((f) => f.endsWith('.md'))\n .map((f) => f.replace(/\\.md$/, ''));\n};\n\n/**\n * Delete an agent — removes the entire directory.\n *\n * @param agentsDir - absolute path to the agents directory\n * @param id - the agent directory name\n */\nexport const deleteAgentDir = (agentsDir: string, id: string): void => {\n const agentDir = resolve(agentsDir, id);\n if (existsSync(agentDir)) {\n rmSync(agentDir, { recursive: true, force: true });\n }\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,43 @@
1
+ // ../daemon/src/paths.ts
2
+ import { resolve, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { homedir } from "os";
5
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6
+ var detectMode = () => {
7
+ if (process.env["KINDFLOW_DEV"] === "1") return "dev";
8
+ if (import.meta.url.endsWith(".ts")) return "dev";
9
+ return "cli";
10
+ };
11
+ var devPaths = () => {
12
+ const repoRoot = resolve(__dirname, "../../..");
13
+ return {
14
+ agentsDir: resolve(repoRoot, "apps"),
15
+ templateDir: resolve(repoRoot, "templates/agent"),
16
+ dataDir: process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow"),
17
+ runnersDir: resolve(repoRoot, "packages"),
18
+ webDir: null
19
+ };
20
+ };
21
+ var cliPaths = () => {
22
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow");
23
+ return {
24
+ agentsDir: resolve(dataDir, "agents"),
25
+ templateDir: resolve(__dirname, "templates/agent"),
26
+ dataDir,
27
+ runnersDir: __dirname,
28
+ webDir: resolve(__dirname, "web")
29
+ };
30
+ };
31
+ var resolvePaths = (overrides) => {
32
+ const mode = detectMode();
33
+ const base = mode === "dev" ? devPaths() : cliPaths();
34
+ return { ...base, ...overrides };
35
+ };
36
+
37
+ export {
38
+ detectMode,
39
+ devPaths,
40
+ cliPaths,
41
+ resolvePaths
42
+ };
43
+ //# sourceMappingURL=chunk-S6UDV4I6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../daemon/src/paths.ts"],"sourcesContent":["/**\n * Centralized path configuration for the Kindflow daemon.\n *\n * In dev mode (monorepo), paths point to workspace directories.\n * In CLI mode (published npm package), paths point to ~/.kindflow/ and the package's dist/.\n */\n\nimport { resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { homedir } from 'os';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * All configurable paths the daemon needs to operate.\n * Replaces the old `repoRoot` parameter that assumed a monorepo layout.\n */\nexport interface KindflowPaths {\n /** Where agent directories live (each containing agent/ + master/). */\n agentsDir: string;\n\n /** Where agent templates are (copied on `kindflow create`). */\n templateDir: string;\n\n /** Where config.db and encryption keys live. */\n dataDir: string;\n\n /** Where compiled runner entry scripts are located. */\n runnersDir: string;\n\n /** Pre-built web UI directory, or null to skip web serving. */\n webDir: string | null;\n}\n\n/**\n * Detect whether the daemon is running in dev mode (monorepo) or CLI mode (published package).\n *\n * @returns 'dev' if running from TypeScript source, 'cli' if running from compiled JS\n */\nexport const detectMode = (): 'dev' | 'cli' => {\n if (process.env['KINDFLOW_DEV'] === '1') return 'dev';\n if (import.meta.url.endsWith('.ts')) return 'dev';\n return 'cli';\n};\n\n/**\n * Build the default KindflowPaths for dev mode (Turborepo monorepo).\n *\n * @returns paths pointing to the monorepo workspace directories\n */\nexport const devPaths = (): KindflowPaths => {\n const repoRoot = resolve(__dirname, '../../..');\n return {\n agentsDir: resolve(repoRoot, 'apps'),\n templateDir: resolve(repoRoot, 'templates/agent'),\n dataDir: process.env['KINDFLOW_DATA_DIR'] ?? resolve(homedir(), '.kindflow'),\n runnersDir: resolve(repoRoot, 'packages'),\n webDir: null,\n };\n};\n\n/**\n * Build the default KindflowPaths for CLI mode (globally installed npm package).\n *\n * @returns paths pointing to ~/.kindflow/ and the package's dist/\n */\nexport const cliPaths = (): KindflowPaths => {\n const dataDir = process.env['KINDFLOW_DATA_DIR'] ?? resolve(homedir(), '.kindflow');\n return {\n agentsDir: resolve(dataDir, 'agents'),\n templateDir: resolve(__dirname, 'templates/agent'),\n dataDir,\n runnersDir: __dirname,\n webDir: resolve(__dirname, 'web'),\n };\n};\n\n/**\n * Resolve KindflowPaths based on the current mode, with optional overrides.\n *\n * @param overrides - partial overrides for specific paths\n * @returns fully resolved KindflowPaths\n */\nexport const resolvePaths = (overrides?: Partial<KindflowPaths>): KindflowPaths => {\n const mode = detectMode();\n const base = mode === 'dev' ? devPaths() : cliPaths();\n return { ...base, ...overrides };\n};\n"],"mappings":";AAOA,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAExB,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AA4BjD,IAAM,aAAa,MAAqB;AAC7C,MAAI,QAAQ,IAAI,cAAc,MAAM,IAAK,QAAO;AAChD,MAAI,YAAY,IAAI,SAAS,KAAK,EAAG,QAAO;AAC5C,SAAO;AACT;AAOO,IAAM,WAAW,MAAqB;AAC3C,QAAM,WAAW,QAAQ,WAAW,UAAU;AAC9C,SAAO;AAAA,IACL,WAAW,QAAQ,UAAU,MAAM;AAAA,IACnC,aAAa,QAAQ,UAAU,iBAAiB;AAAA,IAChD,SAAS,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,QAAQ,GAAG,WAAW;AAAA,IAC3E,YAAY,QAAQ,UAAU,UAAU;AAAA,IACxC,QAAQ;AAAA,EACV;AACF;AAOO,IAAM,WAAW,MAAqB;AAC3C,QAAM,UAAU,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,QAAQ,GAAG,WAAW;AAClF,SAAO;AAAA,IACL,WAAW,QAAQ,SAAS,QAAQ;AAAA,IACpC,aAAa,QAAQ,WAAW,iBAAiB;AAAA,IACjD;AAAA,IACA,YAAY;AAAA,IACZ,QAAQ,QAAQ,WAAW,KAAK;AAAA,EAClC;AACF;AAQO,IAAM,eAAe,CAAC,cAAsD;AACjF,QAAM,OAAO,WAAW;AACxB,QAAM,OAAO,SAAS,QAAQ,SAAS,IAAI,SAAS;AACpD,SAAO,EAAE,GAAG,MAAM,GAAG,UAAU;AACjC;","names":[]}
package/cli.js CHANGED
@@ -1,28 +1,111 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- cliPaths,
4
3
  createAgentFromTemplate,
5
- detectMode,
6
- devPaths,
7
4
  generateSlug,
8
- resolvePaths,
9
- scanAgents,
10
- startDaemon
11
- } from "./chunk-Z3MYKB53.js";
12
- import "./chunk-3AMW7NXZ.js";
13
- import "./chunk-2YT53FBK.js";
14
- import "./chunk-2UX7Z5TH.js";
15
- import "./chunk-GZFR7ELC.js";
16
- import "./chunk-P5P73L35.js";
5
+ scanAgents
6
+ } from "./chunk-IIHUCTI3.js";
7
+ import {
8
+ resolvePaths
9
+ } from "./chunk-S6UDV4I6.js";
17
10
 
18
11
  // src/cli.ts
19
- import { defineCommand as defineCommand4, runMain } from "citty";
12
+ import { defineCommand as defineCommand6, runMain } from "citty";
20
13
 
21
14
  // src/commands/start.ts
22
15
  import { defineCommand } from "citty";
23
- import { mkdirSync } from "fs";
16
+ import { spawn } from "child_process";
17
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, openSync } from "fs";
24
18
  import { resolve, dirname } from "path";
25
19
  import { fileURLToPath } from "url";
20
+ import { homedir } from "os";
21
+ var pidFile = () => {
22
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow");
23
+ return resolve(dataDir, "daemon.pid");
24
+ };
25
+ var logFile = () => {
26
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow");
27
+ return resolve(dataDir, "daemon.log");
28
+ };
29
+ var isRunning = (pid) => {
30
+ try {
31
+ process.kill(pid, 0);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ };
37
+ var readPid = () => {
38
+ const file = pidFile();
39
+ if (!existsSync(file)) return null;
40
+ const pid = parseInt(readFileSync(file, "utf-8").trim(), 10);
41
+ if (isNaN(pid) || !isRunning(pid)) {
42
+ try {
43
+ unlinkSync(file);
44
+ } catch {
45
+ }
46
+ return null;
47
+ }
48
+ return pid;
49
+ };
50
+ var buildArgs = (args) => {
51
+ const result = ["start", "--foreground"];
52
+ if (args["web"]) result.push("--web");
53
+ if (args["port"]) result.push("--port", String(args["port"]));
54
+ if (args["webPort"]) result.push("--web-port", String(args["webPort"]));
55
+ if (args["agentsDir"]) result.push("--agents-dir", String(args["agentsDir"]));
56
+ return result;
57
+ };
58
+ var runForeground = async (args) => {
59
+ const { startDaemon } = await import("./daemon.js");
60
+ const { detectMode, devPaths, cliPaths } = await import("./paths-5IQQ6WBD.js");
61
+ const isDev = detectMode() === "dev";
62
+ const base = isDev ? devPaths() : cliPaths();
63
+ let webDir = null;
64
+ if (args["web"]) {
65
+ if (base.webDir) {
66
+ webDir = base.webDir;
67
+ } else {
68
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..");
69
+ webDir = resolve(repoRoot, "apps/web");
70
+ }
71
+ }
72
+ const paths = {
73
+ ...base,
74
+ webDir,
75
+ ...args["agentsDir"] ? { agentsDir: resolve(String(args["agentsDir"])) } : {}
76
+ };
77
+ mkdirSync(paths.agentsDir, { recursive: true });
78
+ const port = args["port"] ? parseInt(String(args["port"]), 10) : void 0;
79
+ const webPort = args["webPort"] ? parseInt(String(args["webPort"]), 10) : void 0;
80
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow");
81
+ mkdirSync(dataDir, { recursive: true });
82
+ writeFileSync(resolve(dataDir, "daemon.pid"), String(process.pid));
83
+ try {
84
+ const daemon = await startDaemon({ port, webPort, paths });
85
+ const handleSignal = async (signal) => {
86
+ console.log(`
87
+ [kindflow] Received ${signal}`);
88
+ try {
89
+ unlinkSync(resolve(dataDir, "daemon.pid"));
90
+ } catch {
91
+ }
92
+ await daemon.shutdown();
93
+ process.exit(0);
94
+ };
95
+ process.on("SIGINT", () => handleSignal("SIGINT"));
96
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
97
+ } catch (err) {
98
+ try {
99
+ unlinkSync(resolve(dataDir, "daemon.pid"));
100
+ } catch {
101
+ }
102
+ const msg = err instanceof Error ? err.message : String(err);
103
+ console.error(`
104
+ Failed to start: ${msg}
105
+ `);
106
+ process.exit(1);
107
+ }
108
+ };
26
109
  var startCommand = defineCommand({
27
110
  meta: {
28
111
  name: "start",
@@ -45,48 +128,201 @@ var startCommand = defineCommand({
45
128
  agentsDir: {
46
129
  type: "string",
47
130
  description: "Override the agents directory"
131
+ },
132
+ foreground: {
133
+ type: "boolean",
134
+ description: "Run in the foreground (do not daemonize)",
135
+ default: false
48
136
  }
49
137
  },
50
138
  async run({ args }) {
51
- const isDev = detectMode() === "dev";
52
- const base = isDev ? devPaths() : cliPaths();
53
- let webDir = null;
54
- if (args.web) {
55
- if (base.webDir) {
56
- webDir = base.webDir;
57
- } else {
58
- const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..");
59
- webDir = resolve(repoRoot, "apps/web");
139
+ if (args.foreground) {
140
+ console.log("");
141
+ console.log(" Kindflow \u2014 The opinionated agent platform");
142
+ console.log("");
143
+ await runForeground(args);
144
+ return;
145
+ }
146
+ const existingPid = readPid();
147
+ if (existingPid) {
148
+ console.log(` Kindflow is already running (PID ${existingPid})`);
149
+ console.log(` Run "kindflow stop" first, or "kindflow status" for details.`);
150
+ process.exit(1);
151
+ }
152
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve(homedir(), ".kindflow");
153
+ mkdirSync(dataDir, { recursive: true });
154
+ const log = logFile();
155
+ const out = openSync(log, "a");
156
+ const err = openSync(log, "a");
157
+ const kindflowBin = process.argv[1];
158
+ const childArgs = buildArgs(args);
159
+ const isDev = kindflowBin.endsWith(".ts");
160
+ const execArgs = isDev ? ["--import", "tsx", kindflowBin, ...childArgs] : [kindflowBin, ...childArgs];
161
+ const child = spawn(process.execPath, execArgs, {
162
+ detached: true,
163
+ stdio: ["ignore", out, err],
164
+ env: { ...process.env }
165
+ });
166
+ child.unref();
167
+ await new Promise((resolve5) => {
168
+ const timeout = setTimeout(() => {
169
+ resolve5();
170
+ }, 2e3);
171
+ child.on("exit", (code) => {
172
+ clearTimeout(timeout);
173
+ if (code !== 0) {
174
+ console.log("");
175
+ console.log(` Failed to start. Check logs: ${log}`);
176
+ console.log("");
177
+ try {
178
+ const content = readFileSync(log, "utf-8");
179
+ const lines = content.trim().split("\n").slice(-10);
180
+ for (const line of lines) {
181
+ console.log(` ${line}`);
182
+ }
183
+ } catch {
184
+ }
185
+ process.exit(1);
186
+ }
187
+ resolve5();
188
+ });
189
+ });
190
+ const pid = readPid();
191
+ if (pid) {
192
+ console.log("");
193
+ console.log(" Kindflow \u2014 The opinionated agent platform");
194
+ console.log("");
195
+ console.log(` Daemon running (PID ${pid})`);
196
+ console.log(` API: http://localhost:${args.port ?? "7777"}`);
197
+ if (args.web) {
198
+ console.log(` Web: http://localhost:${args.webPort ?? "7001"}`);
60
199
  }
200
+ console.log(` Logs: ${log}`);
201
+ console.log("");
202
+ console.log(` Run "kindflow stop" to shut down.`);
203
+ console.log("");
204
+ } else {
205
+ console.log("");
206
+ console.log(` Failed to start. Check logs: ${log}`);
207
+ console.log("");
208
+ process.exit(1);
209
+ }
210
+ }
211
+ });
212
+
213
+ // src/commands/stop.ts
214
+ import { defineCommand as defineCommand2 } from "citty";
215
+ import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync as unlinkSync2 } from "fs";
216
+ import { resolve as resolve2 } from "path";
217
+ import { homedir as homedir2 } from "os";
218
+ var isRunning2 = (pid) => {
219
+ try {
220
+ process.kill(pid, 0);
221
+ return true;
222
+ } catch {
223
+ return false;
224
+ }
225
+ };
226
+ var stopCommand = defineCommand2({
227
+ meta: {
228
+ name: "stop",
229
+ description: "Stop the Kindflow daemon"
230
+ },
231
+ run() {
232
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve2(homedir2(), ".kindflow");
233
+ const pidPath = resolve2(dataDir, "daemon.pid");
234
+ if (!existsSync2(pidPath)) {
235
+ console.log(" Kindflow is not running.");
236
+ return;
237
+ }
238
+ const pid = parseInt(readFileSync2(pidPath, "utf-8").trim(), 10);
239
+ if (isNaN(pid) || !isRunning2(pid)) {
240
+ console.log(" Kindflow is not running (stale PID file removed).");
241
+ try {
242
+ unlinkSync2(pidPath);
243
+ } catch {
244
+ }
245
+ return;
246
+ }
247
+ process.kill(pid, "SIGTERM");
248
+ console.log(` Stopping Kindflow (PID ${pid})...`);
249
+ const start = Date.now();
250
+ const maxWait = 1e4;
251
+ const poll = setInterval(() => {
252
+ if (!isRunning2(pid)) {
253
+ clearInterval(poll);
254
+ try {
255
+ unlinkSync2(pidPath);
256
+ } catch {
257
+ }
258
+ console.log(" Stopped.");
259
+ process.exit(0);
260
+ }
261
+ if (Date.now() - start > maxWait) {
262
+ clearInterval(poll);
263
+ console.log(` Process did not stop gracefully. Sending SIGKILL...`);
264
+ try {
265
+ process.kill(pid, "SIGKILL");
266
+ } catch {
267
+ }
268
+ try {
269
+ unlinkSync2(pidPath);
270
+ } catch {
271
+ }
272
+ console.log(" Killed.");
273
+ process.exit(0);
274
+ }
275
+ }, 200);
276
+ }
277
+ });
278
+
279
+ // src/commands/status.ts
280
+ import { defineCommand as defineCommand3 } from "citty";
281
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
282
+ import { resolve as resolve3 } from "path";
283
+ import { homedir as homedir3 } from "os";
284
+ var isRunning3 = (pid) => {
285
+ try {
286
+ process.kill(pid, 0);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ };
292
+ var statusCommand = defineCommand3({
293
+ meta: {
294
+ name: "status",
295
+ description: "Show daemon status"
296
+ },
297
+ run() {
298
+ const dataDir = process.env["KINDFLOW_DATA_DIR"] ?? resolve3(homedir3(), ".kindflow");
299
+ const pidPath = resolve3(dataDir, "daemon.pid");
300
+ const logPath = resolve3(dataDir, "daemon.log");
301
+ if (!existsSync3(pidPath)) {
302
+ console.log(" Kindflow is not running.");
303
+ return;
304
+ }
305
+ const pid = parseInt(readFileSync3(pidPath, "utf-8").trim(), 10);
306
+ if (isNaN(pid) || !isRunning3(pid)) {
307
+ console.log(" Kindflow is not running (stale PID file).");
308
+ return;
61
309
  }
62
- const paths = {
63
- ...base,
64
- webDir,
65
- ...args.agentsDir ? { agentsDir: resolve(args.agentsDir) } : {}
66
- };
67
- mkdirSync(paths.agentsDir, { recursive: true });
68
- const port = args.port ? parseInt(args.port, 10) : void 0;
69
- const webPort = args.webPort ? parseInt(args.webPort, 10) : void 0;
70
310
  console.log("");
71
- console.log(" Kindflow \u2014 The opinionated agent platform");
311
+ console.log(" Kindflow is running");
312
+ console.log("");
313
+ console.log(` PID: ${pid}`);
314
+ console.log(` Data: ${dataDir}`);
315
+ console.log(` Agents: ${resolve3(dataDir, "agents")}`);
316
+ console.log(` Logs: ${logPath}`);
72
317
  console.log("");
73
- const daemon = await startDaemon({ port, webPort, paths });
74
- const handleSignal = async (signal) => {
75
- console.log(`
76
- [kindflow] Received ${signal}`);
77
- await daemon.shutdown();
78
- process.exit(0);
79
- };
80
- process.on("SIGINT", () => handleSignal("SIGINT"));
81
- process.on("SIGTERM", () => handleSignal("SIGTERM"));
82
318
  }
83
319
  });
84
320
 
85
321
  // src/commands/create.ts
86
- import { defineCommand as defineCommand2 } from "citty";
87
- import { mkdirSync as mkdirSync2, existsSync } from "fs";
88
- import { resolve as resolve2 } from "path";
89
- var createCommand = defineCommand2({
322
+ import { defineCommand as defineCommand4 } from "citty";
323
+ import { mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
324
+ import { resolve as resolve4 } from "path";
325
+ var createCommand = defineCommand4({
90
326
  meta: {
91
327
  name: "create",
92
328
  description: "Create a new agent"
@@ -102,8 +338,8 @@ var createCommand = defineCommand2({
102
338
  const paths = resolvePaths();
103
339
  mkdirSync2(paths.agentsDir, { recursive: true });
104
340
  const slug = generateSlug(args.name);
105
- const agentDir = resolve2(paths.agentsDir, slug);
106
- if (existsSync(agentDir)) {
341
+ const agentDir = resolve4(paths.agentsDir, slug);
342
+ if (existsSync4(agentDir)) {
107
343
  console.error(`Agent "${slug}" already exists at ${agentDir}`);
108
344
  process.exit(1);
109
345
  }
@@ -113,8 +349,8 @@ var createCommand = defineCommand2({
113
349
  });
114
350
 
115
351
  // src/commands/list.ts
116
- import { defineCommand as defineCommand3 } from "citty";
117
- var listCommand = defineCommand3({
352
+ import { defineCommand as defineCommand5 } from "citty";
353
+ var listCommand = defineCommand5({
118
354
  meta: {
119
355
  name: "list",
120
356
  description: "List all agents"
@@ -137,14 +373,16 @@ var listCommand = defineCommand3({
137
373
  });
138
374
 
139
375
  // src/cli.ts
140
- var main = defineCommand4({
376
+ var main = defineCommand6({
141
377
  meta: {
142
378
  name: "kindflow",
143
- version: "0.3.0",
379
+ version: "0.4.0",
144
380
  description: "Kindflow \u2014 The opinionated agent platform"
145
381
  },
146
382
  subCommands: {
147
383
  start: startCommand,
384
+ stop: stopCommand,
385
+ status: statusCommand,
148
386
  create: createCommand,
149
387
  list: listCommand
150
388
  }