@suwujs/king-ai 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +96 -0
  2. package/dist/src/agent-config-validation.d.ts +9 -0
  3. package/dist/src/agent-config-validation.js +30 -0
  4. package/dist/src/api.d.ts +4 -0
  5. package/dist/src/api.js +48 -0
  6. package/dist/src/attachments.d.ts +45 -0
  7. package/dist/src/attachments.js +322 -0
  8. package/dist/src/cli.d.ts +20 -0
  9. package/dist/src/cli.js +1697 -0
  10. package/dist/src/config.d.ts +3 -0
  11. package/dist/src/config.js +20 -0
  12. package/dist/src/cron.d.ts +11 -0
  13. package/dist/src/cron.js +65 -0
  14. package/dist/src/daemon.d.ts +36 -0
  15. package/dist/src/daemon.js +373 -0
  16. package/dist/src/engine.d.ts +32 -0
  17. package/dist/src/engine.js +1014 -0
  18. package/dist/src/heartbeat.d.ts +18 -0
  19. package/dist/src/heartbeat.js +28 -0
  20. package/dist/src/host-api.d.ts +40 -0
  21. package/dist/src/host-api.js +59 -0
  22. package/dist/src/host-control.d.ts +48 -0
  23. package/dist/src/host-control.js +1279 -0
  24. package/dist/src/host-export.d.ts +50 -0
  25. package/dist/src/host-export.js +187 -0
  26. package/dist/src/host-feedback.d.ts +78 -0
  27. package/dist/src/host-feedback.js +178 -0
  28. package/dist/src/host-home.d.ts +13 -0
  29. package/dist/src/host-home.js +54 -0
  30. package/dist/src/host-ledger.d.ts +261 -0
  31. package/dist/src/host-ledger.js +554 -0
  32. package/dist/src/host-loop-events.d.ts +69 -0
  33. package/dist/src/host-loop-events.js +288 -0
  34. package/dist/src/host-permission.d.ts +36 -0
  35. package/dist/src/host-permission.js +180 -0
  36. package/dist/src/host-policy.d.ts +15 -0
  37. package/dist/src/host-policy.js +36 -0
  38. package/dist/src/host-run-executor.d.ts +13 -0
  39. package/dist/src/host-run-executor.js +221 -0
  40. package/dist/src/host-run-heartbeat.d.ts +40 -0
  41. package/dist/src/host-run-heartbeat.js +103 -0
  42. package/dist/src/host-run-layout.d.ts +17 -0
  43. package/dist/src/host-run-layout.js +387 -0
  44. package/dist/src/host-run-meta.d.ts +41 -0
  45. package/dist/src/host-run-meta.js +115 -0
  46. package/dist/src/host-run-spec.d.ts +149 -0
  47. package/dist/src/host-run-spec.js +465 -0
  48. package/dist/src/host-runs.d.ts +77 -0
  49. package/dist/src/host-runs.js +195 -0
  50. package/dist/src/host-sdk.d.ts +412 -0
  51. package/dist/src/host-sdk.js +628 -0
  52. package/dist/src/host-server.d.ts +26 -0
  53. package/dist/src/host-server.js +921 -0
  54. package/dist/src/host-timeline.d.ts +24 -0
  55. package/dist/src/host-timeline.js +161 -0
  56. package/dist/src/jsonl.d.ts +13 -0
  57. package/dist/src/jsonl.js +47 -0
  58. package/dist/src/lifecycle.d.ts +5 -0
  59. package/dist/src/lifecycle.js +18 -0
  60. package/dist/src/message-routing.d.ts +32 -0
  61. package/dist/src/message-routing.js +119 -0
  62. package/dist/src/paths.d.ts +19 -0
  63. package/dist/src/paths.js +26 -0
  64. package/dist/src/project-profile.d.ts +49 -0
  65. package/dist/src/project-profile.js +356 -0
  66. package/dist/src/remediation.d.ts +14 -0
  67. package/dist/src/remediation.js +114 -0
  68. package/dist/src/remote-devices.d.ts +41 -0
  69. package/dist/src/remote-devices.js +156 -0
  70. package/dist/src/remote-diagnostics.d.ts +39 -0
  71. package/dist/src/remote-diagnostics.js +199 -0
  72. package/dist/src/remote-ssh.d.ts +39 -0
  73. package/dist/src/remote-ssh.js +129 -0
  74. package/dist/src/run-stream.d.ts +57 -0
  75. package/dist/src/run-stream.js +119 -0
  76. package/dist/src/runner.d.ts +131 -0
  77. package/dist/src/runner.js +1161 -0
  78. package/dist/src/runtime-data.d.ts +68 -0
  79. package/dist/src/runtime-data.js +172 -0
  80. package/dist/src/service.d.ts +114 -0
  81. package/dist/src/service.js +631 -0
  82. package/dist/src/shared-skills.d.ts +26 -0
  83. package/dist/src/shared-skills.js +85 -0
  84. package/dist/src/shim.d.ts +1 -0
  85. package/dist/src/shim.js +64 -0
  86. package/dist/src/skill-check.d.ts +17 -0
  87. package/dist/src/skill-check.js +158 -0
  88. package/dist/src/sse.d.ts +9 -0
  89. package/dist/src/sse.js +36 -0
  90. package/dist/src/team-routing.d.ts +55 -0
  91. package/dist/src/team-routing.js +131 -0
  92. package/dist/src/team-workflow.d.ts +78 -0
  93. package/dist/src/team-workflow.js +253 -0
  94. package/dist/src/text.d.ts +7 -0
  95. package/dist/src/text.js +27 -0
  96. package/dist/src/types.d.ts +98 -0
  97. package/dist/src/types.js +1 -0
  98. package/dist/src/usage.d.ts +116 -0
  99. package/dist/src/usage.js +350 -0
  100. package/dist/src/workspace.d.ts +9 -0
  101. package/dist/src/workspace.js +56 -0
  102. package/dist/src/worktree.d.ts +47 -0
  103. package/dist/src/worktree.js +201 -0
  104. package/package.json +63 -0
@@ -0,0 +1,356 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { basename, join, relative, resolve } from "node:path";
4
+ function hasFile(projectPath, name) {
5
+ return existsSync(join(projectPath, name));
6
+ }
7
+ function readPackageJson(projectPath) {
8
+ const file = join(projectPath, "package.json");
9
+ if (!existsSync(file))
10
+ return null;
11
+ try {
12
+ return JSON.parse(readFileSync(file, "utf8"));
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ function packageDeps(projectPath) {
19
+ const pkg = readPackageJson(projectPath);
20
+ const deps = typeof pkg?.dependencies === "object" && pkg.dependencies ? pkg.dependencies : {};
21
+ const devDeps = typeof pkg?.devDependencies === "object" && pkg.devDependencies ? pkg.devDependencies : {};
22
+ return { ...deps, ...devDeps };
23
+ }
24
+ export function detectLanguages(projectPath) {
25
+ const languages = [];
26
+ if (hasFile(projectPath, "package.json")) {
27
+ if (hasFile(projectPath, "tsconfig.json") || hasFile(projectPath, "tsconfig.base.json") || hasFile(projectPath, "tsconfig.build.json"))
28
+ languages.push("typescript");
29
+ languages.push("javascript");
30
+ }
31
+ if (hasFile(projectPath, "Cargo.toml"))
32
+ languages.push("rust");
33
+ if (hasFile(projectPath, "go.mod"))
34
+ languages.push("go");
35
+ if (hasFile(projectPath, "requirements.txt") || hasFile(projectPath, "pyproject.toml") || hasFile(projectPath, "setup.py"))
36
+ languages.push("python");
37
+ if (hasFile(projectPath, "Gemfile"))
38
+ languages.push("ruby");
39
+ if (hasFile(projectPath, "pom.xml") || hasFile(projectPath, "build.gradle"))
40
+ languages.push("java");
41
+ if (hasFile(projectPath, "Package.swift"))
42
+ languages.push("swift");
43
+ if (hasFile(projectPath, "pubspec.yaml"))
44
+ languages.push("dart");
45
+ return languages;
46
+ }
47
+ export function detectPackageManagers(projectPath) {
48
+ const managers = [];
49
+ if (hasFile(projectPath, "pnpm-lock.yaml") || hasFile(projectPath, "pnpm-workspace.yaml"))
50
+ managers.push("pnpm");
51
+ else if (hasFile(projectPath, "yarn.lock"))
52
+ managers.push("yarn");
53
+ else if (hasFile(projectPath, "package-lock.json"))
54
+ managers.push("npm");
55
+ if (hasFile(projectPath, "Cargo.lock"))
56
+ managers.push("cargo");
57
+ if (hasFile(projectPath, "go.sum"))
58
+ managers.push("go");
59
+ if (hasFile(projectPath, "Pipfile.lock") || hasFile(projectPath, "poetry.lock"))
60
+ managers.push("pip");
61
+ return managers;
62
+ }
63
+ export function detectFrameworks(projectPath) {
64
+ const deps = packageDeps(projectPath);
65
+ const frameworks = [];
66
+ if ("next" in deps)
67
+ frameworks.push("nextjs");
68
+ if ("react" in deps)
69
+ frameworks.push("react");
70
+ if ("vue" in deps)
71
+ frameworks.push("vue");
72
+ if ("svelte" in deps || "@sveltejs/kit" in deps)
73
+ frameworks.push("svelte");
74
+ if ("express" in deps)
75
+ frameworks.push("express");
76
+ if ("fastify" in deps)
77
+ frameworks.push("fastify");
78
+ if ("hono" in deps)
79
+ frameworks.push("hono");
80
+ if ("expo" in deps)
81
+ frameworks.push("expo");
82
+ if ("vite" in deps)
83
+ frameworks.push("vite");
84
+ return frameworks;
85
+ }
86
+ export function detectCI(projectPath) {
87
+ const ci = [];
88
+ const workflows = join(projectPath, ".github", "workflows");
89
+ if (existsSync(workflows) && statSync(workflows).isDirectory())
90
+ ci.push("github-actions");
91
+ if (hasFile(projectPath, ".gitlab-ci.yml"))
92
+ ci.push("gitlab-ci");
93
+ if (hasFile(projectPath, ".circleci/config.yml"))
94
+ ci.push("circleci");
95
+ if (hasFile(projectPath, "Jenkinsfile"))
96
+ ci.push("jenkins");
97
+ if (hasFile(projectPath, "Dockerfile") || hasFile(projectPath, "docker-compose.yml"))
98
+ ci.push("docker");
99
+ if (hasFile(projectPath, "vercel.json") || hasFile(projectPath, ".vercel"))
100
+ ci.push("vercel");
101
+ if (hasFile(projectPath, "wrangler.toml"))
102
+ ci.push("cloudflare");
103
+ return ci;
104
+ }
105
+ export function detectTests(projectPath) {
106
+ const deps = packageDeps(projectPath);
107
+ const tests = [];
108
+ if ("vitest" in deps)
109
+ tests.push("vitest");
110
+ if ("jest" in deps)
111
+ tests.push("jest");
112
+ if ("mocha" in deps)
113
+ tests.push("mocha");
114
+ if ("playwright" in deps || "@playwright/test" in deps)
115
+ tests.push("playwright");
116
+ if ("cypress" in deps)
117
+ tests.push("cypress");
118
+ if (hasFile(projectPath, "pytest.ini") || hasFile(projectPath, "conftest.py"))
119
+ tests.push("pytest");
120
+ if (hasFile(projectPath, "test") || hasFile(projectPath, "tests"))
121
+ tests.push("node-test");
122
+ return Array.from(new Set(tests));
123
+ }
124
+ export function detectGitHubRemote(projectPath) {
125
+ try {
126
+ const remote = execSync("git remote get-url origin", {
127
+ cwd: projectPath,
128
+ encoding: "utf8",
129
+ stdio: ["ignore", "pipe", "ignore"],
130
+ timeout: 5000
131
+ }).trim();
132
+ if (remote.includes("github.com"))
133
+ return remote;
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ return undefined;
139
+ }
140
+ export function scanReadme(projectPath) {
141
+ for (const name of ["README.md", "readme.md", "README.rst", "README"]) {
142
+ const file = join(projectPath, name);
143
+ if (existsSync(file) && statSync(file).isFile())
144
+ return readTextSnippet(file, 500);
145
+ }
146
+ return undefined;
147
+ }
148
+ export function detectCodeRoots(projectPath) {
149
+ const roots = ["src", "app", "apps", "packages", "services", "backend", "frontend", "web", "api", "lib", "cmd", "gui-worker"];
150
+ return roots.filter((name) => {
151
+ const dir = join(projectPath, name);
152
+ return existsSync(dir) && statSync(dir).isDirectory();
153
+ });
154
+ }
155
+ export function detectPackageScripts(projectPath) {
156
+ const pkg = readPackageJson(projectPath);
157
+ const scripts = pkg?.scripts;
158
+ if (!scripts || typeof scripts !== "object")
159
+ return [];
160
+ return Object.keys(scripts);
161
+ }
162
+ function readTextSnippet(filePath, maxChars = 1600) {
163
+ try {
164
+ return readFileSync(filePath, "utf8").slice(0, maxChars);
165
+ }
166
+ catch {
167
+ return "";
168
+ }
169
+ }
170
+ function extractHeadings(content) {
171
+ return content
172
+ .split("\n")
173
+ .map((line) => line.trim())
174
+ .filter((line) => /^(#{1,3}\s+|[-*]\s+\[[ xX]\]\s+)/.test(line))
175
+ .map((line) => line.replace(/^#{1,3}\s+/, "").trim())
176
+ .slice(0, 8);
177
+ }
178
+ function titleFromContent(filePath, content) {
179
+ const heading = content
180
+ .split("\n")
181
+ .map((line) => line.trim())
182
+ .find((line) => /^#\s+/.test(line));
183
+ return heading ? heading.replace(/^#\s+/, "").trim() : basename(filePath);
184
+ }
185
+ function scoreDocument(relativePath) {
186
+ const normalized = relativePath.replace(/\\/g, "/").toLowerCase();
187
+ if (/^readme(\.|$)/.test(normalized))
188
+ return { score: 100, kind: "readme" };
189
+ if (/(^|\/)(roadmap|todo|backlog|milestone|plan|vision|prd|strategy)/.test(normalized))
190
+ return { score: 95, kind: "roadmap" };
191
+ if (/^agents\.md$|^claude\.md$/.test(normalized))
192
+ return { score: 85, kind: "notes" };
193
+ if (/^package\.json$|^pyproject\.toml$|^cargo\.toml$|^go\.mod$/.test(normalized))
194
+ return { score: 70, kind: "manifest" };
195
+ if (/^docs\/.+\.(md|mdx|rst|txt)$/.test(normalized))
196
+ return { score: 60, kind: "docs" };
197
+ if (/\.(md|mdx|rst|txt)$/.test(normalized))
198
+ return { score: 40, kind: "notes" };
199
+ return { score: 0, kind: "notes" };
200
+ }
201
+ function walkDocsDir(root, dir, acc, depth = 0) {
202
+ if (depth > 3)
203
+ return;
204
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
205
+ if (entry.name.startsWith("."))
206
+ continue;
207
+ const fullPath = join(dir, entry.name);
208
+ if (entry.isDirectory()) {
209
+ walkDocsDir(root, fullPath, acc, depth + 1);
210
+ continue;
211
+ }
212
+ const rel = relative(root, fullPath);
213
+ if (/\.(md|mdx|rst|txt|json|toml|ya?ml)$/i.test(rel))
214
+ acc.push(fullPath);
215
+ }
216
+ }
217
+ export function collectProjectDocs(projectPath) {
218
+ const candidates = new Map();
219
+ const addCandidate = (file) => {
220
+ const key = relative(projectPath, file).replace(/\\/g, "/").toLowerCase();
221
+ if (!candidates.has(key))
222
+ candidates.set(key, file);
223
+ };
224
+ for (const name of ["README.md", "readme.md", "README.rst", "README", "AGENTS.md", "CLAUDE.md", "ROADMAP.md", "ROADMAP", "TODO.md", "TODO", "CHANGELOG.md", "PLAN.md", "VISION.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"]) {
225
+ const file = join(projectPath, name);
226
+ if (existsSync(file) && statSync(file).isFile())
227
+ addCandidate(file);
228
+ }
229
+ const docsDir = join(projectPath, "docs");
230
+ if (existsSync(docsDir) && statSync(docsDir).isDirectory()) {
231
+ const docs = [];
232
+ walkDocsDir(projectPath, docsDir, docs);
233
+ for (const file of docs)
234
+ addCandidate(file);
235
+ }
236
+ return Array.from(candidates.values())
237
+ .map((filePath) => {
238
+ const rel = relative(projectPath, filePath).replace(/\\/g, "/");
239
+ const { score, kind } = scoreDocument(rel);
240
+ const excerpt = readTextSnippet(filePath);
241
+ return {
242
+ path: rel,
243
+ kind,
244
+ title: titleFromContent(filePath, excerpt),
245
+ excerpt,
246
+ headings: extractHeadings(excerpt),
247
+ score
248
+ };
249
+ })
250
+ .filter((doc) => doc.score > 0)
251
+ .sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
252
+ .slice(0, 10);
253
+ }
254
+ export function scanProject(projectPath) {
255
+ const absPath = resolve(projectPath);
256
+ const githubRemote = detectGitHubRemote(absPath);
257
+ const docsDir = join(absPath, "docs");
258
+ return {
259
+ path: absPath,
260
+ languages: detectLanguages(absPath),
261
+ packageManagers: detectPackageManagers(absPath),
262
+ frameworks: detectFrameworks(absPath),
263
+ ci: detectCI(absPath),
264
+ testFrameworks: detectTests(absPath),
265
+ hasReadme: hasFile(absPath, "README.md") || hasFile(absPath, "readme.md"),
266
+ hasAgentsMd: hasFile(absPath, "AGENTS.md"),
267
+ hasDocs: existsSync(docsDir) && statSync(docsDir).isDirectory(),
268
+ issueTracker: githubRemote ? "github" : "none",
269
+ githubRemote,
270
+ readmeExcerpt: scanReadme(absPath),
271
+ codeRoots: detectCodeRoots(absPath),
272
+ packageScripts: detectPackageScripts(absPath)
273
+ };
274
+ }
275
+ function inferProjectName(profile, docs) {
276
+ const pkg = readPackageJson(profile.path);
277
+ const name = typeof pkg?.name === "string" ? pkg.name.trim() : "";
278
+ if (name)
279
+ return name;
280
+ const readme = docs.find((doc) => doc.kind === "readme");
281
+ return readme?.title || basename(profile.path);
282
+ }
283
+ function inferProjectSummary(profile, docs) {
284
+ const pkg = readPackageJson(profile.path);
285
+ const description = typeof pkg?.description === "string" ? pkg.description.trim() : "";
286
+ if (description)
287
+ return description;
288
+ const readme = docs.find((doc) => doc.kind === "readme");
289
+ if (readme?.excerpt) {
290
+ const summary = readme.excerpt.replace(/\s+/g, " ").replace(/^#.+$/, "").trim().slice(0, 220);
291
+ if (summary)
292
+ return summary;
293
+ }
294
+ const stack = [profile.frameworks[0], profile.languages[0]].filter(Boolean).join(" / ");
295
+ return stack ? `${inferProjectName(profile, docs)} - ${stack} project` : `Maintain and advance ${inferProjectName(profile, docs)}`;
296
+ }
297
+ function buildStrategicThemes(profile, docs) {
298
+ const themes = new Set();
299
+ const roadmapDocs = docs.filter((doc) => doc.kind === "roadmap");
300
+ if (roadmapDocs.length > 0)
301
+ themes.add("Advance incomplete roadmap, plan, and backlog items closest to user value");
302
+ if (profile.hasReadme || profile.hasDocs)
303
+ themes.add("Keep README, docs, and changelog consistent with implementation");
304
+ if (profile.ci.length > 0 || profile.testFrameworks.length > 0)
305
+ themes.add("Keep tests, CI, build, and release pipelines operational");
306
+ if (profile.codeRoots.length > 0)
307
+ themes.add(`Review core code directories ${profile.codeRoots.join(", ")} for structural issues`);
308
+ themes.add(profile.issueTracker === "github" ? "Convert external issues and feedback into scoped local tasks" : "Mine backlog from code, docs, TODOs, and scripts when external issues are unavailable");
309
+ return Array.from(themes);
310
+ }
311
+ export function buildProjectIntent(profile) {
312
+ const docs = collectProjectDocs(profile.path);
313
+ const projectName = inferProjectName(profile, docs);
314
+ const summary = inferProjectSummary(profile, docs);
315
+ const strategicThemes = buildStrategicThemes(profile, docs);
316
+ const stack = [profile.frameworks[0], profile.languages[0]].filter(Boolean).join(" / ");
317
+ return {
318
+ projectName,
319
+ summary,
320
+ canonicalDocs: docs,
321
+ roadmapDocs: docs.filter((doc) => doc.kind === "roadmap"),
322
+ codeRoots: profile.codeRoots,
323
+ packageScripts: profile.packageScripts,
324
+ strategicThemes,
325
+ mission: [
326
+ `Continuously take over and advance ${projectName}${stack ? ` (${stack})` : ""}.`,
327
+ `Project summary: ${summary}`,
328
+ `Operating principles: ${strategicThemes.join("; ")}`
329
+ ].join(" ")
330
+ };
331
+ }
332
+ export function formatProjectProfile(profile, intent = buildProjectIntent(profile)) {
333
+ const lines = [
334
+ `Project profile: ${intent.projectName}`,
335
+ `path: ${profile.path}`,
336
+ `summary: ${intent.summary}`,
337
+ `languages: ${profile.languages.join(", ") || "none"}`,
338
+ `package managers: ${profile.packageManagers.join(", ") || "none"}`,
339
+ `frameworks: ${profile.frameworks.join(", ") || "none"}`,
340
+ `tests: ${profile.testFrameworks.join(", ") || "none"}`,
341
+ `ci: ${profile.ci.join(", ") || "none"}`,
342
+ `code roots: ${profile.codeRoots.join(", ") || "repo root"}`,
343
+ `package scripts: ${profile.packageScripts.join(", ") || "none"}`,
344
+ `issue tracker: ${profile.issueTracker}${profile.githubRemote ? ` (${profile.githubRemote})` : ""}`,
345
+ "canonical docs:",
346
+ ...(intent.canonicalDocs.length ? intent.canonicalDocs.map((doc) => ` - ${doc.path} [${doc.kind}] ${doc.title}`) : [" - none"]),
347
+ "strategic themes:",
348
+ ...intent.strategicThemes.map((theme) => ` - ${theme}`),
349
+ `mission: ${intent.mission}`
350
+ ];
351
+ return lines.join("\n");
352
+ }
353
+ export function runProjectProfile(projectPath) {
354
+ const profile = scanProject(projectPath);
355
+ console.log(formatProjectProfile(profile));
356
+ }
@@ -0,0 +1,14 @@
1
+ export type RemediationSeverity = "info" | "warning" | "error";
2
+ export type RemediationCategory = "missing_engine" | "auth" | "quota" | "rate_limit" | "context" | "session" | "runtime" | "unknown";
3
+ export interface RemediationAdvice {
4
+ engine?: string;
5
+ category: RemediationCategory;
6
+ severity: RemediationSeverity;
7
+ summary: string;
8
+ detail?: string;
9
+ actions: string[];
10
+ }
11
+ export declare function engineInstallAdvice(engine: string): RemediationAdvice;
12
+ export declare function engineRemediationAdvice(engine: string, detail: string): RemediationAdvice;
13
+ export declare function formatRemediationAdvice(advice: RemediationAdvice): string;
14
+ export declare function formatRemediationBlock(advice: RemediationAdvice): string;
@@ -0,0 +1,114 @@
1
+ function has(text, pattern) {
2
+ return pattern.test(text);
3
+ }
4
+ export function engineInstallAdvice(engine) {
5
+ return {
6
+ engine,
7
+ category: "missing_engine",
8
+ severity: "error",
9
+ summary: `${engine} CLI is not on PATH`,
10
+ actions: [
11
+ `Install the ${engine} CLI.`,
12
+ `Run ${engine} once in a local terminal to finish login/setup.`,
13
+ "Restart or re-run: king-ai agent computer --doctor"
14
+ ]
15
+ };
16
+ }
17
+ export function engineRemediationAdvice(engine, detail) {
18
+ const text = detail || "unknown failure";
19
+ const lower = text.toLowerCase();
20
+ if (has(lower, /context window|context length|context_length_exceeded|maximum context|prompt is too long|input is too long|too many tokens/)) {
21
+ return {
22
+ engine,
23
+ category: "context",
24
+ severity: "warning",
25
+ summary: `${engine} session context is full`,
26
+ detail: text,
27
+ actions: [
28
+ "The daemon resets the affected engine session automatically.",
29
+ "Wake the agent again to continue with a fresh session.",
30
+ "If this repeats, reduce prompt/history size or ask the runtime to provide a shorter preamble."
31
+ ]
32
+ };
33
+ }
34
+ if (has(lower, /no (?:low|high) surrogate|unpaired surrogate|lone surrogate|surrogate in string|request body is not valid json/)) {
35
+ return {
36
+ engine,
37
+ category: "session",
38
+ severity: "warning",
39
+ summary: `${engine} session was poisoned by malformed text`,
40
+ detail: text,
41
+ actions: [
42
+ "The daemon resets the affected engine session automatically.",
43
+ "Wake the agent again after the malformed message is removed or sanitized."
44
+ ]
45
+ };
46
+ }
47
+ if (has(lower, /\bquota\b|credit|billing|subscription|usage limit|insufficient_quota|resource_exhausted/)) {
48
+ return {
49
+ engine,
50
+ category: "quota",
51
+ severity: "error",
52
+ summary: `${engine} quota or billing limit is blocking runs`,
53
+ detail: text,
54
+ actions: [
55
+ `Open ${engine} locally and refresh quota, billing, credits, or subscription state.`,
56
+ "Re-run: king-ai agent computer --doctor",
57
+ "Wake the agent again after the quota issue is fixed."
58
+ ]
59
+ };
60
+ }
61
+ if (has(lower, /\b429\b|\b503\b|too many requests|rate.?limit|overloaded|service (temporarily )?unavailable/)) {
62
+ return {
63
+ engine,
64
+ category: "rate_limit",
65
+ severity: "warning",
66
+ summary: `${engine} is temporarily rate-limited or unavailable`,
67
+ detail: text,
68
+ actions: [
69
+ "Wait for the provider limit to clear.",
70
+ "Re-run: king-ai agent computer --doctor",
71
+ "Wake the agent again after the backoff period."
72
+ ]
73
+ };
74
+ }
75
+ if (has(lower, /auth|login|log(?:ged)? in|sign(?:ed)? in|token|api key|unauthorized|forbidden|\b401\b|\b403\b/)) {
76
+ return {
77
+ engine,
78
+ category: "auth",
79
+ severity: "error",
80
+ summary: `${engine} authentication is not ready`,
81
+ detail: text,
82
+ actions: [
83
+ `Open ${engine} locally and sign in again.`,
84
+ "Make sure the daemon process has the same PATH and home config as your terminal.",
85
+ "Re-run: king-ai agent computer --doctor"
86
+ ]
87
+ };
88
+ }
89
+ return {
90
+ engine,
91
+ category: "unknown",
92
+ severity: "warning",
93
+ summary: `${engine} failed; inspect daemon logs`,
94
+ detail: text,
95
+ actions: [
96
+ "Check the daemon terminal or service logs for the full error.",
97
+ "Re-run: king-ai agent computer --doctor",
98
+ "Wake the agent again after fixing the local engine problem."
99
+ ]
100
+ };
101
+ }
102
+ export function formatRemediationAdvice(advice) {
103
+ return [
104
+ `${advice.summary}.`,
105
+ ...advice.actions
106
+ ].join(" ");
107
+ }
108
+ export function formatRemediationBlock(advice) {
109
+ return [
110
+ `${advice.severity}: ${advice.summary}`,
111
+ advice.detail ? ` detail: ${advice.detail}` : "",
112
+ ...advice.actions.map((action) => ` - ${action}`)
113
+ ].filter(Boolean).join("\n");
114
+ }
@@ -0,0 +1,41 @@
1
+ export declare const DEVICES_PATH: string;
2
+ export interface RemoteAppConfig {
3
+ installMarkers?: string[];
4
+ logRoots?: string[];
5
+ errorPatterns?: string[];
6
+ }
7
+ export interface RemoteServiceCommand {
8
+ type?: string;
9
+ command: string;
10
+ }
11
+ export interface RemoteDevice {
12
+ id: string;
13
+ name?: string;
14
+ host: string;
15
+ port?: number;
16
+ user: string;
17
+ password?: string;
18
+ passwordEnv?: string;
19
+ identityFile?: string;
20
+ defaultApp?: string;
21
+ apps?: Record<string, RemoteAppConfig>;
22
+ databases?: Record<string, RemoteServiceCommand>;
23
+ redis?: Record<string, RemoteServiceCommand>;
24
+ }
25
+ export interface RemoteDevicesConfig {
26
+ defaultDevice?: string;
27
+ devices: RemoteDevice[];
28
+ }
29
+ export type RemoteDeviceSummary = Omit<RemoteDevice, "password"> & {
30
+ auth: "password" | "passwordEnv" | "identityFile" | "ssh-agent";
31
+ hasPassword: boolean;
32
+ };
33
+ export declare function loadRemoteDevicesConfig(path?: string): Promise<RemoteDevicesConfig>;
34
+ export declare function saveRemoteDevicesConfig(config: RemoteDevicesConfig, path?: string): Promise<RemoteDevicesConfig>;
35
+ export declare function summarizeRemoteDevice(device: RemoteDevice): RemoteDeviceSummary;
36
+ export declare function listRemoteDeviceSummaries(config: RemoteDevicesConfig): RemoteDeviceSummary[];
37
+ export declare function findRemoteDevice(config: RemoteDevicesConfig, idOrHost?: string): RemoteDevice;
38
+ export declare function upsertRemoteDevice(config: RemoteDevicesConfig, raw: unknown): RemoteDevicesConfig;
39
+ export declare function deleteRemoteDevice(config: RemoteDevicesConfig, id: string): RemoteDevicesConfig;
40
+ export declare function setDefaultRemoteDevice(config: RemoteDevicesConfig, id: string): RemoteDevicesConfig;
41
+ export declare function normalizeRemoteDevicesConfig(raw: unknown): RemoteDevicesConfig;
@@ -0,0 +1,156 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { CONFIG_DIR } from "./paths.js";
3
+ import { join } from "node:path";
4
+ export const DEVICES_PATH = join(CONFIG_DIR, "devices.json");
5
+ export async function loadRemoteDevicesConfig(path = DEVICES_PATH) {
6
+ try {
7
+ const parsed = JSON.parse(await readFile(path, "utf8"));
8
+ return normalizeRemoteDevicesConfig(parsed);
9
+ }
10
+ catch (err) {
11
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
12
+ return { devices: [] };
13
+ }
14
+ throw err;
15
+ }
16
+ }
17
+ export async function saveRemoteDevicesConfig(config, path = DEVICES_PATH) {
18
+ const normalized = normalizeRemoteDevicesConfig(config);
19
+ await mkdir(CONFIG_DIR, { recursive: true });
20
+ await writeFile(path, JSON.stringify(normalized, null, 2) + "\n", "utf8");
21
+ await chmod(path, 0o600).catch(() => undefined);
22
+ return normalized;
23
+ }
24
+ export function summarizeRemoteDevice(device) {
25
+ const auth = device.password ? "password" : device.passwordEnv ? "passwordEnv" : device.identityFile ? "identityFile" : "ssh-agent";
26
+ const { password: _password, ...rest } = device;
27
+ return {
28
+ ...rest,
29
+ auth,
30
+ hasPassword: Boolean(device.password)
31
+ };
32
+ }
33
+ export function listRemoteDeviceSummaries(config) {
34
+ return config.devices.map(summarizeRemoteDevice);
35
+ }
36
+ export function findRemoteDevice(config, idOrHost) {
37
+ const key = (idOrHost || config.defaultDevice || "").trim();
38
+ if (!key)
39
+ throw new Error("remote device is required");
40
+ const device = config.devices.find((entry) => entry.id === key || entry.host === key);
41
+ if (!device)
42
+ throw new Error(`remote device not found: ${key}`);
43
+ return device;
44
+ }
45
+ export function upsertRemoteDevice(config, raw) {
46
+ const device = normalizeRemoteDevice(raw, "device");
47
+ const devices = config.devices.filter((entry) => entry.id !== device.id);
48
+ devices.push(device);
49
+ return normalizeRemoteDevicesConfig({
50
+ defaultDevice: config.defaultDevice || device.id,
51
+ devices
52
+ });
53
+ }
54
+ export function deleteRemoteDevice(config, id) {
55
+ const key = id.trim();
56
+ if (!key)
57
+ throw new Error("remote device id is required");
58
+ const devices = config.devices.filter((entry) => entry.id !== key);
59
+ return normalizeRemoteDevicesConfig({
60
+ defaultDevice: config.defaultDevice === key ? devices[0]?.id : config.defaultDevice,
61
+ devices
62
+ });
63
+ }
64
+ export function setDefaultRemoteDevice(config, id) {
65
+ const device = findRemoteDevice(config, id);
66
+ return normalizeRemoteDevicesConfig({ ...config, defaultDevice: device.id });
67
+ }
68
+ export function normalizeRemoteDevicesConfig(raw) {
69
+ if (!raw || typeof raw !== "object")
70
+ throw new Error("remote devices config must be an object");
71
+ const input = raw;
72
+ if (!Array.isArray(input.devices))
73
+ throw new Error("remote devices config must include devices array");
74
+ const devices = input.devices.map((item, index) => normalizeRemoteDevice(item, `devices[${index}]`));
75
+ const ids = new Set();
76
+ for (const device of devices) {
77
+ if (ids.has(device.id))
78
+ throw new Error(`duplicate remote device id: ${device.id}`);
79
+ ids.add(device.id);
80
+ }
81
+ const defaultDevice = typeof input.defaultDevice === "string" && input.defaultDevice.trim() ? input.defaultDevice.trim() : undefined;
82
+ if (defaultDevice && !ids.has(defaultDevice))
83
+ throw new Error(`default remote device not found: ${defaultDevice}`);
84
+ return { ...(defaultDevice ? { defaultDevice } : {}), devices };
85
+ }
86
+ function normalizeRemoteDevice(raw, label) {
87
+ if (!raw || typeof raw !== "object")
88
+ throw new Error(`${label} must be an object`);
89
+ const input = raw;
90
+ const id = requiredString(input.id, `${label}.id`);
91
+ const host = requiredString(input.host, `${label}.host`);
92
+ const user = requiredString(input.user, `${label}.user`);
93
+ const port = optionalPort(input.port, `${label}.port`);
94
+ return {
95
+ id,
96
+ ...(optionalString(input.name) ? { name: optionalString(input.name) } : {}),
97
+ host,
98
+ ...(port ? { port } : {}),
99
+ user,
100
+ ...(optionalString(input.password) ? { password: optionalString(input.password) } : {}),
101
+ ...(optionalString(input.passwordEnv) ? { passwordEnv: optionalString(input.passwordEnv) } : {}),
102
+ ...(optionalString(input.identityFile) ? { identityFile: optionalString(input.identityFile) } : {}),
103
+ ...(optionalString(input.defaultApp) ? { defaultApp: optionalString(input.defaultApp) } : {}),
104
+ ...(input.apps && typeof input.apps === "object" ? { apps: normalizeApps(input.apps, `${label}.apps`) } : {}),
105
+ ...(input.databases && typeof input.databases === "object" ? { databases: normalizeServiceCommands(input.databases, `${label}.databases`) } : {}),
106
+ ...(input.redis && typeof input.redis === "object" ? { redis: normalizeServiceCommands(input.redis, `${label}.redis`) } : {})
107
+ };
108
+ }
109
+ function normalizeApps(raw, label) {
110
+ const result = {};
111
+ for (const [name, value] of Object.entries(raw)) {
112
+ if (!value || typeof value !== "object")
113
+ throw new Error(`${label}.${name} must be an object`);
114
+ const app = value;
115
+ result[name] = {
116
+ ...(app.installMarkers !== undefined ? { installMarkers: normalizeStringArray(app.installMarkers, `${label}.${name}.installMarkers`) } : {}),
117
+ ...(app.logRoots !== undefined ? { logRoots: normalizeStringArray(app.logRoots, `${label}.${name}.logRoots`) } : {}),
118
+ ...(app.errorPatterns !== undefined ? { errorPatterns: normalizeStringArray(app.errorPatterns, `${label}.${name}.errorPatterns`) } : {})
119
+ };
120
+ }
121
+ return result;
122
+ }
123
+ function normalizeServiceCommands(raw, label) {
124
+ const result = {};
125
+ for (const [name, value] of Object.entries(raw)) {
126
+ if (!value || typeof value !== "object")
127
+ throw new Error(`${label}.${name} must be an object`);
128
+ const service = value;
129
+ result[name] = {
130
+ ...(optionalString(service.type) ? { type: optionalString(service.type) } : {}),
131
+ command: requiredString(service.command, `${label}.${name}.command`)
132
+ };
133
+ }
134
+ return result;
135
+ }
136
+ function normalizeStringArray(raw, label) {
137
+ if (!Array.isArray(raw))
138
+ throw new Error(`${label} must be an array`);
139
+ return raw.map((item, index) => requiredString(item, `${label}[${index}]`));
140
+ }
141
+ function requiredString(raw, label) {
142
+ if (typeof raw !== "string" || !raw.trim())
143
+ throw new Error(`${label} must be a non-empty string`);
144
+ return raw.trim();
145
+ }
146
+ function optionalString(raw) {
147
+ return typeof raw === "string" && raw.trim() ? raw.trim() : undefined;
148
+ }
149
+ function optionalPort(raw, label) {
150
+ if (raw === undefined || raw === null || raw === "")
151
+ return undefined;
152
+ const port = typeof raw === "number" ? raw : Number.parseInt(String(raw), 10);
153
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
154
+ throw new Error(`${label} must be between 1 and 65535`);
155
+ return port;
156
+ }