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 +25 -5
- package/chunk-IIHUCTI3.js +102 -0
- package/chunk-IIHUCTI3.js.map +1 -0
- package/chunk-S6UDV4I6.js +43 -0
- package/chunk-S6UDV4I6.js.map +1 -0
- package/cli.js +289 -51
- package/cli.js.map +1 -1
- package/daemon.js +3014 -7
- package/daemon.js.map +1 -1
- package/package.json +1 -1
- package/paths-5IQQ6WBD.js +13 -0
- package/paths-5IQQ6WBD.js.map +1 -0
- package/chunk-Z3MYKB53.js +0 -3135
- package/chunk-Z3MYKB53.js.map +0 -1
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 (
|
|
50
|
+
# Start the daemon (runs in background)
|
|
39
51
|
kindflow start
|
|
40
52
|
|
|
41
|
-
# Start with the web UI
|
|
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
|
-
#
|
|
48
|
-
kindflow
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
12
|
+
import { defineCommand as defineCommand6, runMain } from "citty";
|
|
20
13
|
|
|
21
14
|
// src/commands/start.ts
|
|
22
15
|
import { defineCommand } from "citty";
|
|
23
|
-
import {
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
|
87
|
-
import { mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
88
|
-
import { resolve as
|
|
89
|
-
var createCommand =
|
|
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 =
|
|
106
|
-
if (
|
|
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
|
|
117
|
-
var listCommand =
|
|
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 =
|
|
376
|
+
var main = defineCommand6({
|
|
141
377
|
meta: {
|
|
142
378
|
name: "kindflow",
|
|
143
|
-
version: "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
|
}
|