Skip to content
This repository was archived by the owner on May 9, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ BACKEND_URL=https://api.tinyhumans.ai
JWT_TOKEN= # Your JWT token (app Settings > Developer)
DEV_JWT_TOKEN= # Alias for JWT_TOKEN

# Gmail (live testing with OAuth)
# OAuth credentials are managed via the REPL 'oauth' command.
# No manual env vars needed for managed OAuth.
# Live test scripts — auth mode
# AUTH_MODE= # "oauth" or "self_hosted" (skips interactive prompt)

# Notion (live testing)
# Uses OAuth (managed) or self-hosted auth via REPL setup.
# NOTION_API_KEY= # Only for self-hosted/text auth mode
# NOTION_API_KEY= # Self-hosted: Notion internal integration token (ntn_...)
# NOTION_INTEGRATION_ID= # Encrypted OAuth: 24-char hex integration ID from callback
# CLIENT_KEY_SHARE= # Encrypted OAuth: base64 client key share from callback

# Gmail (live testing)
# GMAIL_CLIENT_ID= # Self-hosted: Google Cloud OAuth client ID
# GMAIL_CLIENT_SECRET= # Self-hosted: Google Cloud OAuth client secret
# GMAIL_REFRESH_TOKEN= # Self-hosted: OAuth2 refresh token
# GMAIL_INTEGRATION_ID= # Encrypted OAuth: 24-char hex integration ID from callback
# (CLIENT_KEY_SHARE is shared with Notion above)

# Telegram (requires TDLib)
TELEGRAM_API_ID=
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ dev/js-harness/**/*.js

# Local model files (large GGUF binaries)
.models/
.fastembed_cache/
37 changes: 37 additions & 0 deletions dev/test-harness/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,43 @@ export async function setSetupComplete(skillId: string, complete: boolean = true
});
}

/**
* Complete the auth flow for a skill (self_hosted / text mode).
* Sends `auth/complete` RPC with mode and credentials.
*/
export async function authComplete(
skillId: string,
mode: string,
credentials: Record<string, unknown>,
): Promise<unknown> {
return skillRpc(skillId, 'auth/complete', { mode, credentials });
}

/**
* Complete the OAuth flow for a skill (managed mode).
* Sends `oauth/complete` RPC with credential info and optional clientKeyShare
* for encrypted OAuth.
*/
export async function oauthComplete(
skillId: string,
args: {
credentialId: string;
provider: string;
grantedScopes?: string[];
accountLabel?: string;
clientKeyShare?: string;
},
): Promise<unknown> {
return skillRpc(skillId, 'oauth/complete', args);
}

/**
* Trigger a sync on a running skill.
*/
export async function triggerSync(skillId: string): Promise<unknown> {
return skillRpc(skillId, 'sync', {});
}

/**
* Read a file from the skill's data directory.
*/
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"lint:fix": "eslint . --ext .ts --fix",
"prepare": "husky",
"core:build": "cargo build --manifest-path openhuman/Cargo.toml --bin openhuman-core",
"dev:runtime": "(cd openhuman && cargo build) && node scripts/dev-runtime.mjs",
"core:run": "cargo run --manifest-path openhuman/Cargo.toml --bin openhuman-core -- skills run --skills-dir ./skills",
"core:list": "cargo run --manifest-path openhuman/Cargo.toml --bin openhuman-core -- skills list --skills-dir ./skills",
"core:test": "cargo run --manifest-path openhuman/Cargo.toml --bin openhuman-core -- skills test --skills-dir ./skills",
Expand All @@ -52,6 +53,7 @@
"prettier": "^3.4.2",
"rimraf": "^6.1.2",
"socket.io-client": "^4.8.3",
"ts-node": "^10.9.2",
"tsx": "^4.19.0",
"typescript": "~5.8.3",
"ws": "^8.18.0"
Expand Down
117 changes: 117 additions & 0 deletions scripts/dev-runtime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env node
/**
* dev-runtime.mjs — starts the skills runtime with .env loaded.
*
* Loads .env from the repo root, then spawns the openhuman-core binary
* with all env vars forwarded so skills can read BACKEND_URL, JWT_TOKEN, etc.
*
* Usage:
* node scripts/dev-runtime.mjs
* node scripts/dev-runtime.mjs --port 7799
*/

import { spawn, execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = resolve(__dirname, '..');

// ---------------------------------------------------------------------------
// Load .env
// ---------------------------------------------------------------------------

const envFile = resolve(rootDir, '.env');
const envVars = { ...process.env };

if (existsSync(envFile)) {
const lines = readFileSync(envFile, 'utf-8').split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eqIdx = trimmed.indexOf('=');
if (eqIdx === -1) continue;
const key = trimmed.slice(0, eqIdx).trim();
const value = trimmed.slice(eqIdx + 1).trim();
// Don't override vars already set in the shell
if (!process.env[key] && value) {
envVars[key] = value;
}
}
console.log(`\x1b[2m Loaded .env from ${envFile}\x1b[0m`);
} else {
console.log(`\x1b[33m No .env file found at ${envFile}\x1b[0m`);
}

// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------

const args = process.argv.slice(2);
let port = 7799;
const portIdx = args.indexOf('--port');
if (portIdx !== -1 && args[portIdx + 1]) {
port = parseInt(args[portIdx + 1], 10);
}

const SKILLS_DIR = resolve(rootDir, 'skills');
const CORE_BINARY = resolve(rootDir, 'openhuman', 'target', 'debug', 'openhuman-core');

// ---------------------------------------------------------------------------
// Checks
// ---------------------------------------------------------------------------

if (!existsSync(CORE_BINARY)) {
console.error(`\x1b[31m Error: openhuman-core binary not found at ${CORE_BINARY}\x1b[0m`);
console.error(` Run: cd openhuman && cargo build`);
process.exit(1);
}

if (!existsSync(SKILLS_DIR)) {
console.error(`\x1b[31m Error: compiled skills not found at ${SKILLS_DIR}\x1b[0m`);
console.error(` Run: yarn build`);
process.exit(1);
}

// ---------------------------------------------------------------------------
// Kill existing process on the port
// ---------------------------------------------------------------------------

try {
execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null`, { stdio: 'ignore' });
await new Promise((r) => setTimeout(r, 500));
} catch {
// Nothing running on that port
}

// ---------------------------------------------------------------------------
// Start
// ---------------------------------------------------------------------------

console.log(`\n\x1b[36m Skills Runtime (dev)\x1b[0m`);
console.log(`\x1b[2m Port: ${port}\x1b[0m`);
console.log(`\x1b[2m Skills dir: ${SKILLS_DIR}\x1b[0m`);
console.log(`\x1b[2m Backend: ${envVars.BACKEND_URL || '(not set — will use default)'}\x1b[0m`);
console.log(`\x1b[2m JWT: ${envVars.JWT_TOKEN ? `<${envVars.JWT_TOKEN.length} chars>` : '(not set)'}\x1b[0m`);
console.log();

const child = spawn(CORE_BINARY, [
'skills', 'run',
'--skills-dir', SKILLS_DIR,
'--port', String(port),
], {
stdio: ['ignore', 'inherit', 'inherit'],
env: {
...envVars,
RUST_LOG: envVars.RUST_LOG || 'info',
},
});

child.on('exit', (code) => {
process.exit(code ?? 1);
});

// Forward signals for clean shutdown
process.on('SIGINT', () => child.kill('SIGINT'));
process.on('SIGTERM', () => child.kill('SIGTERM'));
Loading
Loading