runwork 0.5.1 → 0.6.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/dist/agents/__tests__/claude-code-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +153 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.js +280 -0
- package/dist/agents/__tests__/codex-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-stats.test.js +262 -0
- package/dist/agents/__tests__/cursor-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/cursor-stats.test.js +183 -0
- package/dist/agents/__tests__/graceful-degradation.test.d.ts +1 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +123 -0
- package/dist/agents/__tests__/instruction-hint.test.d.ts +1 -0
- package/dist/agents/__tests__/instruction-hint.test.js +69 -0
- package/dist/agents/__tests__/intro-skill.test.d.ts +1 -0
- package/dist/agents/__tests__/intro-skill.test.js +135 -0
- package/dist/agents/__tests__/schema-validation.test.d.ts +1 -0
- package/dist/agents/__tests__/schema-validation.test.js +212 -0
- package/dist/agents/claude-code.d.ts +16 -1
- package/dist/agents/claude-code.js +344 -21
- package/dist/agents/claude-desktop.d.ts +10 -1
- package/dist/agents/claude-desktop.js +250 -8
- package/dist/agents/cline.d.ts +13 -0
- package/dist/agents/cline.js +88 -0
- package/dist/agents/codex.d.ts +7 -2
- package/dist/agents/codex.js +130 -18
- package/dist/agents/cursor.d.ts +6 -1
- package/dist/agents/cursor.js +164 -7
- package/dist/agents/detect.js +30 -7
- package/dist/agents/gemini.d.ts +6 -3
- package/dist/agents/gemini.js +57 -20
- package/dist/agents/generic-adapter.d.ts +23 -0
- package/dist/agents/generic-adapter.js +106 -0
- package/dist/agents/intro-skill.d.ts +36 -0
- package/dist/agents/intro-skill.js +358 -0
- package/dist/agents/registry.d.ts +53 -0
- package/dist/agents/registry.js +219 -0
- package/dist/agents/types.d.ts +44 -1
- package/dist/agents/types.js +11 -4
- package/dist/agents/utils/instruction-hint.d.ts +11 -0
- package/dist/agents/utils/instruction-hint.js +62 -0
- package/dist/agents/utils/json-config.js +3 -3
- package/dist/agents/windsurf.d.ts +2 -0
- package/dist/agents/windsurf.js +26 -9
- package/dist/api/client.d.ts +85 -2
- package/dist/api/client.js +40 -10
- package/dist/auth/__tests__/login-flow.test.d.ts +1 -0
- package/dist/auth/__tests__/login-flow.test.js +125 -0
- package/dist/auth/login-flow.d.ts +16 -0
- package/dist/auth/login-flow.js +101 -1
- package/dist/commands/clone.d.ts +9 -1
- package/dist/commands/clone.js +40 -12
- package/dist/commands/deploy.js +27 -4
- package/dist/commands/dev.js +35 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/info.js +6 -2
- package/dist/commands/init.d.ts +10 -2
- package/dist/commands/init.js +58 -15
- package/dist/commands/integrations.js +47 -10
- package/dist/commands/login.js +31 -4
- package/dist/commands/mcp.js +143 -15
- package/dist/commands/setup.js +47 -122
- package/dist/commands/skills.js +230 -12
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +398 -65
- package/dist/commands/welcome.js +6 -3
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/checks.test.d.ts +1 -0
- package/dist/health/__tests__/checks.test.js +223 -0
- package/dist/health/checks.d.ts +20 -0
- package/dist/health/checks.js +392 -0
- package/dist/health/runner.d.ts +2 -0
- package/dist/health/runner.js +26 -0
- package/dist/health/types.d.ts +12 -0
- package/dist/health/types.js +1 -0
- package/dist/index.js +32 -0
- package/dist/sync/__tests__/change-detect.test.d.ts +1 -0
- package/dist/sync/__tests__/change-detect.test.js +123 -0
- package/dist/sync/__tests__/hash.test.d.ts +1 -0
- package/dist/sync/__tests__/hash.test.js +18 -0
- package/dist/sync/change-detect.d.ts +19 -0
- package/dist/sync/change-detect.js +136 -0
- package/dist/sync/conflict-ui.d.ts +5 -0
- package/dist/sync/conflict-ui.js +78 -0
- package/dist/sync/executor.d.ts +21 -0
- package/dist/sync/executor.js +98 -0
- package/dist/sync/hash.d.ts +1 -0
- package/dist/sync/hash.js +4 -0
- package/dist/sync/types.d.ts +24 -0
- package/dist/sync/types.js +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/ui/__tests__/banner.test.js +17 -1
- package/dist/ui/banner.d.ts +1 -1
- package/dist/ui/banner.js +27 -2
- package/dist/utils/__tests__/resolve.test.d.ts +1 -0
- package/dist/utils/__tests__/resolve.test.js +95 -0
- package/dist/utils/__tests__/sqlite.test.d.ts +1 -0
- package/dist/utils/__tests__/sqlite.test.js +77 -0
- package/dist/utils/agent-guidance.d.ts +32 -0
- package/dist/utils/agent-guidance.js +106 -0
- package/dist/utils/fs.js +2 -2
- package/dist/utils/help-json.d.ts +56 -0
- package/dist/utils/help-json.js +85 -0
- package/dist/utils/resolve.d.ts +12 -0
- package/dist/utils/resolve.js +52 -0
- package/dist/utils/setup-state.d.ts +3 -0
- package/dist/utils/setup-state.js +19 -0
- package/dist/utils/sqlite.d.ts +7 -0
- package/dist/utils/sqlite.js +24 -0
- package/dist/utils/which.d.ts +6 -0
- package/dist/utils/which.js +18 -0
- package/package.json +7 -3
- package/dist/agents/copilot-cli.d.ts +0 -10
- package/dist/agents/copilot-cli.js +0 -46
- package/dist/agents/copilot-vscode.d.ts +0 -10
- package/dist/agents/copilot-vscode.js +0 -37
package/dist/agents/types.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ export interface McpServerEntry {
|
|
|
3
3
|
url: string;
|
|
4
4
|
transport: 'sse' | 'streamable-http';
|
|
5
5
|
headers?: Record<string, string>;
|
|
6
|
+
description?: string;
|
|
6
7
|
}
|
|
7
8
|
export interface SkillFile {
|
|
8
9
|
name: string;
|
|
9
10
|
filename: string;
|
|
10
11
|
content: string;
|
|
11
12
|
description: string;
|
|
13
|
+
appId?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface AgentAdapter {
|
|
14
16
|
/** Display name, e.g. "Claude Code" */
|
|
@@ -21,13 +23,54 @@ export interface AgentAdapter {
|
|
|
21
23
|
writeMcpServers(servers: McpServerEntry[], scope: 'project' | 'user'): Promise<void>;
|
|
22
24
|
/** Write skill files to agent's skills directory */
|
|
23
25
|
writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
|
|
26
|
+
/** Write a minimal Runwork hint to the agent's instruction file (CLAUDE.md, AGENTS.md, etc.) */
|
|
27
|
+
writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
|
|
24
28
|
/** Whether this agent supports skills/commands */
|
|
25
29
|
supportsSkills(): boolean;
|
|
26
30
|
/** Whether this agent supports MCP config at the given scope */
|
|
27
31
|
supportsMcpScope(scope: 'project' | 'user'): boolean;
|
|
32
|
+
/** Write team-managed instructions (separate marker block from auto-generated hint) */
|
|
33
|
+
writeTeamInstructions?(instructions: string, scope: 'project' | 'user'): Promise<void>;
|
|
34
|
+
/** Write agent-specific config overrides from workspace admin (model, permissions) */
|
|
35
|
+
writeAgentConfig?(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
|
|
36
|
+
/** Read passive usage stats from local agent files (best-effort, no hooks) */
|
|
37
|
+
readUsageStats?(lastSyncAt: string | null): Promise<AgentUsageStats | null>;
|
|
38
|
+
/** Read which skills were used locally since last sync (best-effort) */
|
|
39
|
+
readSkillUsage?(lastSyncAt: string | null): Promise<SkillUsageEntry[] | null>;
|
|
40
|
+
/** Get the agent's version string (best-effort) */
|
|
41
|
+
readVersion?(): Promise<string | null>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentConfigOverride {
|
|
44
|
+
extraInstructions?: string;
|
|
45
|
+
modelPreference?: string;
|
|
46
|
+
permissionRules?: {
|
|
47
|
+
allow?: string[];
|
|
48
|
+
deny?: string[];
|
|
49
|
+
defaultMode?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface AgentUsageStats {
|
|
53
|
+
hasNewActivity: boolean;
|
|
54
|
+
sessionCount: number;
|
|
55
|
+
messageCount: number;
|
|
56
|
+
toolCallCount: number;
|
|
57
|
+
tokensUsed: number;
|
|
58
|
+
aiLinesAdded: number;
|
|
59
|
+
aiLinesRemoved: number;
|
|
60
|
+
lastActiveAt: string | null;
|
|
61
|
+
modelsUsed?: string[];
|
|
62
|
+
mcpToolCount?: number;
|
|
63
|
+
extra?: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
export interface SkillUsageEntry {
|
|
66
|
+
skillName: string;
|
|
67
|
+
count: number;
|
|
68
|
+
lastUsedAt: string;
|
|
28
69
|
}
|
|
29
70
|
/** Prefix for MCP server entries managed by runwork */
|
|
30
|
-
export declare const RUNWORK_MCP_PREFIX = "
|
|
71
|
+
export declare const RUNWORK_MCP_PREFIX = "Runwork: ";
|
|
72
|
+
/** Legacy prefix used before human-readable names were introduced */
|
|
73
|
+
export declare const RUNWORK_MCP_PREFIX_LEGACY = "runwork-";
|
|
31
74
|
/**
|
|
32
75
|
* Build a SKILL.md file with YAML frontmatter.
|
|
33
76
|
* Ensures the content always has proper frontmatter for agent discovery.
|
package/dist/agents/types.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/** Prefix for MCP server entries managed by runwork */
|
|
2
|
-
export const RUNWORK_MCP_PREFIX = '
|
|
2
|
+
export const RUNWORK_MCP_PREFIX = 'Runwork: ';
|
|
3
|
+
/** Legacy prefix used before human-readable names were introduced */
|
|
4
|
+
export const RUNWORK_MCP_PREFIX_LEGACY = 'runwork-';
|
|
3
5
|
/**
|
|
4
6
|
* Build a SKILL.md file with YAML frontmatter.
|
|
5
7
|
* Ensures the content always has proper frontmatter for agent discovery.
|
|
6
8
|
*/
|
|
7
9
|
export function buildSkillMd(skill) {
|
|
8
|
-
|
|
10
|
+
const description = skill.description || `${skill.name} skill`;
|
|
11
|
+
// If content already has frontmatter, ensure description is present
|
|
9
12
|
if (skill.content.trimStart().startsWith('---\n')) {
|
|
10
|
-
|
|
13
|
+
const hasDescription = /^description:\s*.+$/m.test(skill.content);
|
|
14
|
+
if (hasDescription)
|
|
15
|
+
return skill.content;
|
|
16
|
+
// Inject description into existing frontmatter
|
|
17
|
+
return skill.content.replace(/^---\n/, `---\nname: ${skill.name}\ndescription: ${description}\n`);
|
|
11
18
|
}
|
|
12
|
-
return `---\nname: ${skill.name}\ndescription: ${
|
|
19
|
+
return `---\nname: ${skill.name}\ndescription: ${description}\n---\n\n${skill.content}`;
|
|
13
20
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append or replace a Runwork instruction hint in an agent's instruction file.
|
|
3
|
+
* If the file contains existing Runwork markers, replaces that section.
|
|
4
|
+
* Otherwise appends the hint at the end.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeHintToFile(filePath: string, hint: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Append or replace team instructions in an agent's instruction file.
|
|
9
|
+
* Uses separate markers from the auto-generated hint so both coexist.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeTeamInstructionsToFile(filePath: string, instructions: string): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
const START_MARKER = '<!-- runwork:start -->';
|
|
4
|
+
const END_MARKER = '<!-- runwork:end -->';
|
|
5
|
+
const TEAM_START_MARKER = '<!-- runwork-team:start -->';
|
|
6
|
+
const TEAM_END_MARKER = '<!-- runwork-team:end -->';
|
|
7
|
+
/**
|
|
8
|
+
* Append or replace a Runwork instruction hint in an agent's instruction file.
|
|
9
|
+
* If the file contains existing Runwork markers, replaces that section.
|
|
10
|
+
* Otherwise appends the hint at the end.
|
|
11
|
+
*/
|
|
12
|
+
export function writeHintToFile(filePath, hint) {
|
|
13
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
14
|
+
let content = '';
|
|
15
|
+
if (existsSync(filePath)) {
|
|
16
|
+
content = readFileSync(filePath, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
const startIdx = content.indexOf(START_MARKER);
|
|
19
|
+
const endIdx = content.indexOf(END_MARKER);
|
|
20
|
+
if (startIdx >= 0 && endIdx >= 0) {
|
|
21
|
+
// Replace existing section
|
|
22
|
+
content = content.substring(0, startIdx) + hint + content.substring(endIdx + END_MARKER.length);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Append with spacing
|
|
26
|
+
if (content.length > 0 && !content.endsWith('\n')) {
|
|
27
|
+
content += '\n';
|
|
28
|
+
}
|
|
29
|
+
if (content.length > 0) {
|
|
30
|
+
content += '\n';
|
|
31
|
+
}
|
|
32
|
+
content += hint + '\n';
|
|
33
|
+
}
|
|
34
|
+
writeFileSync(filePath, content);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Append or replace team instructions in an agent's instruction file.
|
|
38
|
+
* Uses separate markers from the auto-generated hint so both coexist.
|
|
39
|
+
*/
|
|
40
|
+
export function writeTeamInstructionsToFile(filePath, instructions) {
|
|
41
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
42
|
+
let content = '';
|
|
43
|
+
if (existsSync(filePath)) {
|
|
44
|
+
content = readFileSync(filePath, 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
const block = `${TEAM_START_MARKER}\n${instructions}\n${TEAM_END_MARKER}`;
|
|
47
|
+
const startIdx = content.indexOf(TEAM_START_MARKER);
|
|
48
|
+
const endIdx = content.indexOf(TEAM_END_MARKER);
|
|
49
|
+
if (startIdx >= 0 && endIdx >= 0) {
|
|
50
|
+
content = content.substring(0, startIdx) + block + content.substring(endIdx + TEAM_END_MARKER.length);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
if (content.length > 0 && !content.endsWith('\n')) {
|
|
54
|
+
content += '\n';
|
|
55
|
+
}
|
|
56
|
+
if (content.length > 0) {
|
|
57
|
+
content += '\n';
|
|
58
|
+
}
|
|
59
|
+
content += block + '\n';
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(filePath, content);
|
|
62
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
|
-
import { RUNWORK_MCP_PREFIX } from '../types.js';
|
|
3
|
+
import { RUNWORK_MCP_PREFIX, RUNWORK_MCP_PREFIX_LEGACY } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Read a JSON config file. Returns empty object if file doesn't exist.
|
|
6
6
|
*/
|
|
@@ -29,9 +29,9 @@ export function writeJsonConfig(filePath, config) {
|
|
|
29
29
|
export function mergeJsonMcpServers(filePath, servers, topKey) {
|
|
30
30
|
const config = readJsonConfig(filePath);
|
|
31
31
|
const existing = (config[topKey] || {});
|
|
32
|
-
// Remove all
|
|
32
|
+
// Remove all Runwork-managed entries (current and legacy prefixes)
|
|
33
33
|
for (const key of Object.keys(existing)) {
|
|
34
|
-
if (key.startsWith(RUNWORK_MCP_PREFIX)) {
|
|
34
|
+
if (key.startsWith(RUNWORK_MCP_PREFIX) || key.startsWith(RUNWORK_MCP_PREFIX_LEGACY)) {
|
|
35
35
|
delete existing[key];
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -7,4 +7,6 @@ export declare class WindsurfAdapter implements AgentAdapter {
|
|
|
7
7
|
supportsSkills(): boolean;
|
|
8
8
|
writeMcpServers(servers: McpServerEntry[], _scope: 'project' | 'user'): Promise<void>;
|
|
9
9
|
writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
|
|
10
|
+
writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
|
|
11
|
+
writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
|
|
10
12
|
}
|
package/dist/agents/windsurf.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir, platform } from 'os';
|
|
4
|
-
import {
|
|
4
|
+
import { whichBinary } from '../utils/which.js';
|
|
5
5
|
import { mergeJsonMcpServers } from './utils/json-config.js';
|
|
6
|
+
function getWindsurfDataDir() {
|
|
7
|
+
if (platform() === 'win32') {
|
|
8
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Codeium', 'windsurf');
|
|
9
|
+
}
|
|
10
|
+
return join(homedir(), '.codeium', 'windsurf');
|
|
11
|
+
}
|
|
6
12
|
function getConfigPath() {
|
|
7
|
-
return join(
|
|
13
|
+
return join(getWindsurfDataDir(), 'mcp_config.json');
|
|
8
14
|
}
|
|
9
15
|
export class WindsurfAdapter {
|
|
10
16
|
name = 'Windsurf';
|
|
@@ -13,13 +19,7 @@ export class WindsurfAdapter {
|
|
|
13
19
|
const os = platform();
|
|
14
20
|
if (os === 'darwin' && existsSync('/Applications/Windsurf.app'))
|
|
15
21
|
return true;
|
|
16
|
-
|
|
17
|
-
execFileSync('which', ['windsurf'], { stdio: 'pipe' });
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
22
|
+
return !!whichBinary('windsurf');
|
|
23
23
|
}
|
|
24
24
|
supportsMcpScope(scope) {
|
|
25
25
|
return scope === 'user';
|
|
@@ -47,4 +47,21 @@ export class WindsurfAdapter {
|
|
|
47
47
|
writeFileSync(join(rulesDir, `${skill.filename}.md`), content);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
async writeTeamInstructions(instructions, scope) {
|
|
51
|
+
const filePath = scope === 'project'
|
|
52
|
+
? join(process.cwd(), '.windsurf', 'rules', 'runwork-team.md')
|
|
53
|
+
: join(getWindsurfDataDir(), 'rules', 'runwork-team.md');
|
|
54
|
+
const content = `---\ntrigger: always_on\ndescription: "Team instructions from Runwork workspace"\n---\n\n${instructions}`;
|
|
55
|
+
mkdirSync(join(filePath, '..'), { recursive: true });
|
|
56
|
+
writeFileSync(filePath, content);
|
|
57
|
+
}
|
|
58
|
+
async writeInstructionHint(hint, scope) {
|
|
59
|
+
// Windsurf uses rules files
|
|
60
|
+
const filePath = scope === 'project'
|
|
61
|
+
? join(process.cwd(), '.windsurf', 'rules', 'runwork.md')
|
|
62
|
+
: join(getWindsurfDataDir(), 'rules', 'runwork.md');
|
|
63
|
+
const content = `---\ntrigger: always\n---\n\n${hint}`;
|
|
64
|
+
mkdirSync(join(filePath, '..'), { recursive: true });
|
|
65
|
+
writeFileSync(filePath, content);
|
|
66
|
+
}
|
|
50
67
|
}
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { Credentials, AppInfo, WorkspaceInfo, DevSession, WorkspaceAllData, WorkspaceSkill, ExternalSkillRegistration, McpServerConfig, RegistryEntity, WorkflowInfo, TriggerWorkflowResponse, WorkflowInstancesData, WorkflowInstanceDetailData, ScheduleInfo, TriggerScheduleResponse, ScheduleHistoryData, WorkspaceEndpoint, FileStorageInfo, ListBucketFilesData, ComponentInfo, ApiKeyInfo, ApiKeyCreateResponse, AgentInfo, AgentStatusResponse, AgentExecutionRecord } from '../types.js';
|
|
2
|
+
export interface AgentConfigOverride {
|
|
3
|
+
extraInstructions?: string;
|
|
4
|
+
modelPreference?: string;
|
|
5
|
+
permissionRules?: {
|
|
6
|
+
allow?: string[];
|
|
7
|
+
deny?: string[];
|
|
8
|
+
defaultMode?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface OnboardingConfigResponse {
|
|
12
|
+
config: {
|
|
13
|
+
teamInstructions: string | null;
|
|
14
|
+
agentConfigs: Record<string, AgentConfigOverride> | null;
|
|
15
|
+
} | null;
|
|
16
|
+
}
|
|
2
17
|
export declare class ApiClient {
|
|
3
18
|
private baseUrl;
|
|
4
19
|
private apiKey;
|
|
@@ -63,11 +78,13 @@ export declare class ApiClient {
|
|
|
63
78
|
filePath: string;
|
|
64
79
|
fileContents: string;
|
|
65
80
|
}>, deletedFiles?: string[]): Promise<void>;
|
|
66
|
-
searchIntegrations(query: string, limit?: number): Promise<{
|
|
81
|
+
searchIntegrations(query: string, limit?: number, workspaceId?: string): Promise<{
|
|
67
82
|
results: Array<{
|
|
68
83
|
id: string;
|
|
69
84
|
name: string;
|
|
70
85
|
provider: string;
|
|
86
|
+
connectedByUser?: boolean;
|
|
87
|
+
usedByTeammates?: boolean;
|
|
71
88
|
docs?: {
|
|
72
89
|
name: string;
|
|
73
90
|
baseUrl: string;
|
|
@@ -85,6 +102,17 @@ export declare class ApiClient {
|
|
|
85
102
|
status?: string;
|
|
86
103
|
createdAt?: string;
|
|
87
104
|
}>>;
|
|
105
|
+
getTeamIntegrationUsage(workspaceId: string): Promise<Array<{
|
|
106
|
+
canonicalId: string;
|
|
107
|
+
integrationId: string;
|
|
108
|
+
userCount: number;
|
|
109
|
+
users: Array<{
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
avatarUrl?: string | null;
|
|
113
|
+
}>;
|
|
114
|
+
connectedByMe: boolean;
|
|
115
|
+
}>>;
|
|
88
116
|
/**
|
|
89
117
|
* Get git remote URL for an app.
|
|
90
118
|
* Both canonical ({baseUrl}/api/git/{workspaceId}/{appId}) and
|
|
@@ -92,7 +120,7 @@ export declare class ApiClient {
|
|
|
92
120
|
*/
|
|
93
121
|
getWorkspaceAll(workspaceId: string): Promise<WorkspaceAllData>;
|
|
94
122
|
getGitRemoteUrl(workspaceId: string, appId: string): string;
|
|
95
|
-
listWorkspaceSkills(workspaceId: string): Promise<WorkspaceSkill[]>;
|
|
123
|
+
listWorkspaceSkills(workspaceId: string, includeContent?: boolean): Promise<WorkspaceSkill[]>;
|
|
96
124
|
listExternalSkills(workspaceId: string): Promise<ExternalSkillRegistration[]>;
|
|
97
125
|
importExternalSkill(workspaceId: string, skill: {
|
|
98
126
|
name: string;
|
|
@@ -113,6 +141,54 @@ export declare class ApiClient {
|
|
|
113
141
|
transport: 'sse' | 'streamable-http';
|
|
114
142
|
}): Promise<McpServerConfig>;
|
|
115
143
|
removeMcpServer(workspaceId: string, serverId: string): Promise<void>;
|
|
144
|
+
searchCommunitySkills(query: string, limit?: number, includeContent?: boolean): Promise<{
|
|
145
|
+
query: string;
|
|
146
|
+
count: number;
|
|
147
|
+
skills: Array<{
|
|
148
|
+
id: string;
|
|
149
|
+
title: string;
|
|
150
|
+
description: string | null;
|
|
151
|
+
source: string;
|
|
152
|
+
refs: {
|
|
153
|
+
skills_sh: string;
|
|
154
|
+
github: string;
|
|
155
|
+
raw: string | null;
|
|
156
|
+
};
|
|
157
|
+
install_count: number;
|
|
158
|
+
relevance_score: number;
|
|
159
|
+
content: string | null;
|
|
160
|
+
}>;
|
|
161
|
+
}>;
|
|
162
|
+
getCommunitySkillContent(source: string, skillId: string): Promise<{
|
|
163
|
+
id: string;
|
|
164
|
+
title: string;
|
|
165
|
+
description: string | null;
|
|
166
|
+
source: string;
|
|
167
|
+
content: string | null;
|
|
168
|
+
refs: {
|
|
169
|
+
skills_sh: string;
|
|
170
|
+
github: string;
|
|
171
|
+
raw: string | null;
|
|
172
|
+
};
|
|
173
|
+
install_count: number;
|
|
174
|
+
}>;
|
|
175
|
+
searchCommunityMcpServers(query: string, limit?: number): Promise<{
|
|
176
|
+
servers: Array<{
|
|
177
|
+
name: string;
|
|
178
|
+
description: string;
|
|
179
|
+
title: string | null;
|
|
180
|
+
version: string;
|
|
181
|
+
websiteUrl: string | null;
|
|
182
|
+
remotes: Array<{
|
|
183
|
+
type: 'sse' | 'streamable-http';
|
|
184
|
+
url: string;
|
|
185
|
+
}>;
|
|
186
|
+
keywords: string[];
|
|
187
|
+
publisher: string | null;
|
|
188
|
+
}>;
|
|
189
|
+
nextCursor: string | null;
|
|
190
|
+
count: number;
|
|
191
|
+
}>;
|
|
116
192
|
listEntities(workspaceId: string): Promise<RegistryEntity[]>;
|
|
117
193
|
listEntityRecords(workspaceId: string, entityName: string, opts?: {
|
|
118
194
|
limit?: number;
|
|
@@ -183,6 +259,13 @@ export declare class ApiClient {
|
|
|
183
259
|
getAgentStatus(workspaceId: string, agentName: string, appId: string): Promise<AgentStatusResponse>;
|
|
184
260
|
listAgentExecutions(workspaceId: string): Promise<AgentExecutionRecord[]>;
|
|
185
261
|
getAgentExecutionStream(workspaceId: string, sessionId: string): Promise<string>;
|
|
262
|
+
getOnboardingConfig(workspaceId: string): Promise<OnboardingConfigResponse>;
|
|
263
|
+
reportTelemetry(workspaceId: string, events: Array<{
|
|
264
|
+
eventType: string;
|
|
265
|
+
agentSlug?: string;
|
|
266
|
+
metadata?: Record<string, unknown>;
|
|
267
|
+
timestamp: string;
|
|
268
|
+
}>): Promise<void>;
|
|
186
269
|
callIntegrationProxy(integrationDbId: string, method: string, path: string, opts?: {
|
|
187
270
|
body?: unknown;
|
|
188
271
|
headers?: Record<string, string>;
|
package/dist/api/client.js
CHANGED
|
@@ -133,8 +133,10 @@ export class ApiClient {
|
|
|
133
133
|
body: JSON.stringify({ files, deletedFiles }),
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
-
async searchIntegrations(query, limit = 20) {
|
|
136
|
+
async searchIntegrations(query, limit = 20, workspaceId) {
|
|
137
137
|
const params = new URLSearchParams({ q: query, limit: String(limit) });
|
|
138
|
+
if (workspaceId)
|
|
139
|
+
params.set('workspaceId', workspaceId);
|
|
138
140
|
const res = await this.request(`/api/integrations/search?${params}`);
|
|
139
141
|
return res.data;
|
|
140
142
|
}
|
|
@@ -142,6 +144,10 @@ export class ApiClient {
|
|
|
142
144
|
const res = await this.request(`/api/workspaces/${workspaceId}/integrations`);
|
|
143
145
|
return res.data.integrations;
|
|
144
146
|
}
|
|
147
|
+
async getTeamIntegrationUsage(workspaceId) {
|
|
148
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/integrations/team-usage`);
|
|
149
|
+
return res.data.usage;
|
|
150
|
+
}
|
|
145
151
|
/**
|
|
146
152
|
* Get git remote URL for an app.
|
|
147
153
|
* Both canonical ({baseUrl}/api/git/{workspaceId}/{appId}) and
|
|
@@ -155,36 +161,52 @@ export class ApiClient {
|
|
|
155
161
|
return `${this.baseUrl}/api/git/${workspaceId}/${appId}`;
|
|
156
162
|
}
|
|
157
163
|
// --- Workspace Chat API (skills & MCP) ---
|
|
158
|
-
async listWorkspaceSkills(workspaceId) {
|
|
159
|
-
const
|
|
164
|
+
async listWorkspaceSkills(workspaceId, includeContent = false) {
|
|
165
|
+
const query = includeContent ? '?includeContent=true' : '';
|
|
166
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/skills${query}`);
|
|
160
167
|
return res.data.skills;
|
|
161
168
|
}
|
|
162
169
|
async listExternalSkills(workspaceId) {
|
|
163
|
-
const res = await this.request(`/api/
|
|
170
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/external-skills`);
|
|
164
171
|
return res.data.skills;
|
|
165
172
|
}
|
|
166
173
|
async importExternalSkill(workspaceId, skill) {
|
|
167
|
-
const res = await this.request(`/api/
|
|
174
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/external-skills`, { method: 'POST', body: JSON.stringify(skill) });
|
|
168
175
|
return res.data.skill;
|
|
169
176
|
}
|
|
170
177
|
async updateExternalSkill(workspaceId, skillId, skill) {
|
|
171
|
-
const res = await this.request(`/api/
|
|
178
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/external-skills/${skillId}`, { method: 'PUT', body: JSON.stringify(skill) });
|
|
172
179
|
return res.data.skill;
|
|
173
180
|
}
|
|
174
181
|
async getAppSkillContent(workspaceId, appId) {
|
|
175
|
-
const res = await this.request(`/api/
|
|
182
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/apps/${appId}/skill`);
|
|
176
183
|
return res.data.skill;
|
|
177
184
|
}
|
|
178
185
|
async listMcpServers(workspaceId) {
|
|
179
|
-
const res = await this.request(`/api/
|
|
186
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/mcp-servers`);
|
|
180
187
|
return res.data.servers;
|
|
181
188
|
}
|
|
182
189
|
async addMcpServer(workspaceId, server) {
|
|
183
|
-
const res = await this.request(`/api/
|
|
190
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/mcp-servers`, { method: 'POST', body: JSON.stringify(server) });
|
|
184
191
|
return res.data.server;
|
|
185
192
|
}
|
|
186
193
|
async removeMcpServer(workspaceId, serverId) {
|
|
187
|
-
await this.request(`/api/
|
|
194
|
+
await this.request(`/api/workspaces/${workspaceId}/mcp-servers/${serverId}`, { method: 'DELETE' });
|
|
195
|
+
}
|
|
196
|
+
// --- Community Marketplace ---
|
|
197
|
+
async searchCommunitySkills(query, limit = 10, includeContent = false) {
|
|
198
|
+
const res = await this.request(`/api/community/skills/search?q=${encodeURIComponent(query)}&limit=${limit}&include_content=${includeContent}`);
|
|
199
|
+
return res.data;
|
|
200
|
+
}
|
|
201
|
+
async getCommunitySkillContent(source, skillId) {
|
|
202
|
+
// source is "owner/repo", keep the slash as a path separator (route is /skills/:owner/:repo/:skillId)
|
|
203
|
+
const encodedSource = source.split('/').map(encodeURIComponent).join('/');
|
|
204
|
+
const res = await this.request(`/api/community/skills/${encodedSource}/${encodeURIComponent(skillId)}`);
|
|
205
|
+
return res.data;
|
|
206
|
+
}
|
|
207
|
+
async searchCommunityMcpServers(query, limit = 20) {
|
|
208
|
+
const res = await this.request(`/api/community/mcp-servers/search?q=${encodeURIComponent(query)}&limit=${limit}`);
|
|
209
|
+
return res.data;
|
|
188
210
|
}
|
|
189
211
|
// --- Workspace Registry: Entities ---
|
|
190
212
|
async listEntities(workspaceId) {
|
|
@@ -324,6 +346,14 @@ export class ApiClient {
|
|
|
324
346
|
const res = await this.request(`/api/workspaces/${workspaceId}/agent-executions/${encodeURIComponent(sessionId)}/stream`);
|
|
325
347
|
return res.data.stream;
|
|
326
348
|
}
|
|
349
|
+
// --- Team Telemetry ---
|
|
350
|
+
async getOnboardingConfig(workspaceId) {
|
|
351
|
+
const res = await this.request(`/api/workspaces/${workspaceId}/onboarding-config`);
|
|
352
|
+
return res.data;
|
|
353
|
+
}
|
|
354
|
+
async reportTelemetry(workspaceId, events) {
|
|
355
|
+
await this.request(`/api/workspaces/${workspaceId}/team/telemetry`, { method: 'POST', body: JSON.stringify({ events }) });
|
|
356
|
+
}
|
|
327
357
|
// --- Integrations: Proxy Call ---
|
|
328
358
|
async callIntegrationProxy(integrationDbId, method, path, opts) {
|
|
329
359
|
const targetPath = opts?.query ? `${path}?${opts.query}` : path;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
// Mock only the side-effect dependencies, not ApiClient
|
|
3
|
+
vi.mock('../store.js', () => ({
|
|
4
|
+
saveCredentials: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock('../../git/credentials.js', () => ({
|
|
7
|
+
configureGitCredentials: vi.fn().mockResolvedValue(undefined),
|
|
8
|
+
}));
|
|
9
|
+
import { saveCredentials } from '../store.js';
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.clearAllMocks();
|
|
12
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
13
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
14
|
+
});
|
|
15
|
+
describe('performLoginNoOpen', () => {
|
|
16
|
+
it('with printOnly prints URL and returns null without polling', async () => {
|
|
17
|
+
// Mock ApiClient at the instance level via fetch
|
|
18
|
+
const originalFetch = globalThis.fetch;
|
|
19
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
20
|
+
ok: true,
|
|
21
|
+
status: 200,
|
|
22
|
+
json: () => Promise.resolve({
|
|
23
|
+
data: { sessionId: 'sess-123', loginUrl: 'https://runwork.ai/cli-auth?session=sess-123' },
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
globalThis.fetch = mockFetch;
|
|
27
|
+
try {
|
|
28
|
+
const { performLoginNoOpen } = await import('../login-flow.js');
|
|
29
|
+
const result = await performLoginNoOpen('https://runwork.ai', true);
|
|
30
|
+
expect(console.log).toHaveBeenCalledWith('https://runwork.ai/cli-auth?session=sess-123');
|
|
31
|
+
expect(result).toBeNull();
|
|
32
|
+
expect(saveCredentials).not.toHaveBeenCalled();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
globalThis.fetch = originalFetch;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('performLoginWithApiKey', () => {
|
|
40
|
+
it('validates key by listing workspaces and saves credentials', async () => {
|
|
41
|
+
const originalFetch = globalThis.fetch;
|
|
42
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
43
|
+
ok: true,
|
|
44
|
+
status: 200,
|
|
45
|
+
json: () => Promise.resolve({
|
|
46
|
+
data: [{ id: 'ws-1', name: 'My Workspace' }],
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
globalThis.fetch = mockFetch;
|
|
50
|
+
try {
|
|
51
|
+
const { performLoginWithApiKey } = await import('../login-flow.js');
|
|
52
|
+
const result = await performLoginWithApiKey('rw_test123');
|
|
53
|
+
expect(saveCredentials).toHaveBeenCalledWith(expect.objectContaining({
|
|
54
|
+
apiKey: 'rw_test123',
|
|
55
|
+
defaultWorkspaceId: 'ws-1',
|
|
56
|
+
defaultWorkspaceName: 'My Workspace',
|
|
57
|
+
}));
|
|
58
|
+
expect(result.apiKey).toBe('rw_test123');
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
globalThis.fetch = originalFetch;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it('saves credentials with custom base URL', async () => {
|
|
65
|
+
const originalFetch = globalThis.fetch;
|
|
66
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
67
|
+
ok: true,
|
|
68
|
+
status: 200,
|
|
69
|
+
json: () => Promise.resolve({ data: [{ id: 'ws-1', name: 'WS' }] }),
|
|
70
|
+
});
|
|
71
|
+
globalThis.fetch = mockFetch;
|
|
72
|
+
try {
|
|
73
|
+
const { performLoginWithApiKey } = await import('../login-flow.js');
|
|
74
|
+
const result = await performLoginWithApiKey('rw_key', 'https://custom.runwork.dev');
|
|
75
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('https://custom.runwork.dev'), expect.anything());
|
|
76
|
+
expect(result.baseUrl).toBe('https://custom.runwork.dev');
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
globalThis.fetch = originalFetch;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('handles no workspaces (sets no default)', async () => {
|
|
83
|
+
const originalFetch = globalThis.fetch;
|
|
84
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
85
|
+
ok: true,
|
|
86
|
+
status: 200,
|
|
87
|
+
json: () => Promise.resolve({ data: [] }),
|
|
88
|
+
});
|
|
89
|
+
globalThis.fetch = mockFetch;
|
|
90
|
+
try {
|
|
91
|
+
const { performLoginWithApiKey } = await import('../login-flow.js');
|
|
92
|
+
const result = await performLoginWithApiKey('rw_key');
|
|
93
|
+
expect(saveCredentials).toHaveBeenCalledWith(expect.objectContaining({
|
|
94
|
+
apiKey: 'rw_key',
|
|
95
|
+
defaultWorkspaceId: undefined,
|
|
96
|
+
defaultWorkspaceName: undefined,
|
|
97
|
+
}));
|
|
98
|
+
expect(result.apiKey).toBe('rw_key');
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
globalThis.fetch = originalFetch;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
it('exits on invalid API key', async () => {
|
|
105
|
+
const originalFetch = globalThis.fetch;
|
|
106
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
107
|
+
ok: false,
|
|
108
|
+
status: 401,
|
|
109
|
+
json: () => Promise.resolve({ error: 'Unauthorized' }),
|
|
110
|
+
text: () => Promise.resolve('Unauthorized'),
|
|
111
|
+
});
|
|
112
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation(((code) => {
|
|
113
|
+
throw new Error(`process.exit(${code})`);
|
|
114
|
+
}));
|
|
115
|
+
try {
|
|
116
|
+
const { performLoginWithApiKey } = await import('../login-flow.js');
|
|
117
|
+
await expect(performLoginWithApiKey('bad-key')).rejects.toThrow('process.exit(1)');
|
|
118
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Invalid API key'));
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
globalThis.fetch = originalFetch;
|
|
122
|
+
mockExit.mockRestore();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -8,3 +8,19 @@ import type { Credentials } from '../types.js';
|
|
|
8
8
|
* Returns the credentials on success, exits process on timeout.
|
|
9
9
|
*/
|
|
10
10
|
export declare function performLogin(baseUrl?: string): Promise<Credentials>;
|
|
11
|
+
/**
|
|
12
|
+
* Initiate a login session without opening the browser.
|
|
13
|
+
* Prints the login URL to stdout and polls for completion.
|
|
14
|
+
* If printOnly is true, prints the URL and returns null (exits without polling).
|
|
15
|
+
*/
|
|
16
|
+
export declare function performLoginNoOpen(baseUrl?: string, printOnly?: boolean): Promise<Credentials | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Resume a previously initiated login session by providing the login URL.
|
|
19
|
+
* Extracts the session ID from the URL and polls until authenticated.
|
|
20
|
+
*/
|
|
21
|
+
export declare function performLoginFromUrl(loginUrl: string): Promise<Credentials>;
|
|
22
|
+
/**
|
|
23
|
+
* Authenticate directly with an API key (no browser flow).
|
|
24
|
+
* Validates the key by calling the API, then saves credentials.
|
|
25
|
+
*/
|
|
26
|
+
export declare function performLoginWithApiKey(apiKey: string, baseUrl?: string): Promise<Credentials>;
|