@julioventura/opensquad 0.1.17
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 +433 -0
- package/_opensquad/config/playwright.config.json +11 -0
- package/_opensquad/core/architect.agent.yaml +112 -0
- package/_opensquad/core/best-practices/_catalog.yaml +126 -0
- package/_opensquad/core/best-practices/blog-post.md +132 -0
- package/_opensquad/core/best-practices/blog-seo.md +127 -0
- package/_opensquad/core/best-practices/brand-resolution-checklist.md +172 -0
- package/_opensquad/core/best-practices/copywriting.md +441 -0
- package/_opensquad/core/best-practices/data-analysis.md +401 -0
- package/_opensquad/core/best-practices/email-newsletter.md +118 -0
- package/_opensquad/core/best-practices/email-sales.md +110 -0
- package/_opensquad/core/best-practices/image-design.md +348 -0
- package/_opensquad/core/best-practices/instagram-feed.md +235 -0
- package/_opensquad/core/best-practices/instagram-reels.md +112 -0
- package/_opensquad/core/best-practices/instagram-stories.md +107 -0
- package/_opensquad/core/best-practices/linkedin-article.md +116 -0
- package/_opensquad/core/best-practices/linkedin-post.md +121 -0
- package/_opensquad/core/best-practices/researching.md +349 -0
- package/_opensquad/core/best-practices/review.md +269 -0
- package/_opensquad/core/best-practices/run-recovery.md +61 -0
- package/_opensquad/core/best-practices/social-networks-publishing.md +327 -0
- package/_opensquad/core/best-practices/squad-creation-checklist.md +32 -0
- package/_opensquad/core/best-practices/strategist.md +344 -0
- package/_opensquad/core/best-practices/technical-writing.md +365 -0
- package/_opensquad/core/best-practices/twitter-post.md +105 -0
- package/_opensquad/core/best-practices/twitter-thread.md +122 -0
- package/_opensquad/core/best-practices/whatsapp-broadcast.md +107 -0
- package/_opensquad/core/best-practices/youtube-script.md +122 -0
- package/_opensquad/core/best-practices/youtube-shorts.md +112 -0
- package/_opensquad/core/defaults/youtube-video-assembly.json +84 -0
- package/_opensquad/core/prompts/build.prompt.md +613 -0
- package/_opensquad/core/prompts/design.prompt.md +606 -0
- package/_opensquad/core/prompts/discovery.prompt.md +377 -0
- package/_opensquad/core/prompts/sherlock-instagram.md +123 -0
- package/_opensquad/core/prompts/sherlock-linkedin.md +73 -0
- package/_opensquad/core/prompts/sherlock-shared.md +684 -0
- package/_opensquad/core/prompts/sherlock-twitter.md +78 -0
- package/_opensquad/core/prompts/sherlock-youtube.md +85 -0
- package/_opensquad/core/runner.pipeline.md +743 -0
- package/_opensquad/core/skills.engine.md +384 -0
- package/bin/opensquad.js +108 -0
- package/dashboard/index.html +15 -0
- package/dashboard/package-lock.json +1964 -0
- package/dashboard/package.json +28 -0
- package/dashboard/public/assets/avatars/Female1_1wave.png +0 -0
- package/dashboard/public/assets/avatars/Female1_2wave.png +0 -0
- package/dashboard/public/assets/avatars/Female1_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female1_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female2_1wave.png +0 -0
- package/dashboard/public/assets/avatars/Female2_2wave.png +0 -0
- package/dashboard/public/assets/avatars/Female2_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female2_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female3_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female3_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female3_wave.png +0 -0
- package/dashboard/public/assets/avatars/Female4_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female4_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female4_wave.png +0 -0
- package/dashboard/public/assets/avatars/Female5_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female5_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female5_wave.png +0 -0
- package/dashboard/public/assets/avatars/Female6_blink.png +0 -0
- package/dashboard/public/assets/avatars/Female6_talk.png +0 -0
- package/dashboard/public/assets/avatars/Female6_wave.png +0 -0
- package/dashboard/public/assets/avatars/Male1_1wave.png +0 -0
- package/dashboard/public/assets/avatars/Male1_2wave.png +0 -0
- package/dashboard/public/assets/avatars/Male1_blink.png +0 -0
- package/dashboard/public/assets/avatars/Male1_talk.png +0 -0
- package/dashboard/public/assets/avatars/Male2_1wave.png +0 -0
- package/dashboard/public/assets/avatars/Male2_2wave.png +0 -0
- package/dashboard/public/assets/avatars/Male2_blink.png +0 -0
- package/dashboard/public/assets/avatars/Male2_talk.png +0 -0
- package/dashboard/public/assets/avatars/Male3_blink.png +0 -0
- package/dashboard/public/assets/avatars/Male3_talk.png +0 -0
- package/dashboard/public/assets/avatars/Male3_wave.png +0 -0
- package/dashboard/public/assets/avatars/Male4_blink.png +0 -0
- package/dashboard/public/assets/avatars/Male4_talk.png +0 -0
- package/dashboard/public/assets/avatars/Male4_wave.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_black_down.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_black_down_coding-1.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_black_down_coding.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_black_up.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_white_down.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_white_down_coding-1.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_white_down_coding.png +0 -0
- package/dashboard/public/assets/desks/desktop_set_white_up.png +0 -0
- package/dashboard/public/assets/furniture/armchair_tan.png +0 -0
- package/dashboard/public/assets/furniture/armchair_tan_down.png +0 -0
- package/dashboard/public/assets/furniture/backpack_blue.png +0 -0
- package/dashboard/public/assets/furniture/backpack_red.png +0 -0
- package/dashboard/public/assets/furniture/blinds.png +0 -0
- package/dashboard/public/assets/furniture/blinds_large_closed_white.png +0 -0
- package/dashboard/public/assets/furniture/bookshelf.png +0 -0
- package/dashboard/public/assets/furniture/bookshelf_purple_tall.png +0 -0
- package/dashboard/public/assets/furniture/bulletin_board.png +0 -0
- package/dashboard/public/assets/furniture/clock.png +0 -0
- package/dashboard/public/assets/furniture/coffee_mug.png +0 -0
- package/dashboard/public/assets/furniture/coffee_mug_blue.png +0 -0
- package/dashboard/public/assets/furniture/coffee_table.png +0 -0
- package/dashboard/public/assets/furniture/coffeepot_right.png +0 -0
- package/dashboard/public/assets/furniture/coffeetable_black_horizontal.png +0 -0
- package/dashboard/public/assets/furniture/couch.png +0 -0
- package/dashboard/public/assets/furniture/couch_tan_down.png +0 -0
- package/dashboard/public/assets/furniture/cushion_blue.png +0 -0
- package/dashboard/public/assets/furniture/cushion_tan.png +0 -0
- package/dashboard/public/assets/furniture/desk_wood.png +0 -0
- package/dashboard/public/assets/furniture/fancy_rug.png +0 -0
- package/dashboard/public/assets/furniture/fancy_rug_wide.png +0 -0
- package/dashboard/public/assets/furniture/flowers1.png +0 -0
- package/dashboard/public/assets/furniture/flowers2.png +0 -0
- package/dashboard/public/assets/furniture/lamp_tan.png +0 -0
- package/dashboard/public/assets/furniture/lantern.png +0 -0
- package/dashboard/public/assets/furniture/monstera.png +0 -0
- package/dashboard/public/assets/furniture/monstera_small.png +0 -0
- package/dashboard/public/assets/furniture/picture_frame.png +0 -0
- package/dashboard/public/assets/furniture/plant1.png +0 -0
- package/dashboard/public/assets/furniture/plant2.png +0 -0
- package/dashboard/public/assets/furniture/plant3.png +0 -0
- package/dashboard/public/assets/furniture/plant_poof.png +0 -0
- package/dashboard/public/assets/furniture/plant_spindly.png +0 -0
- package/dashboard/public/assets/furniture/poster_blue.png +0 -0
- package/dashboard/public/assets/furniture/rug.png +0 -0
- package/dashboard/public/assets/furniture/succulent_blue.png +0 -0
- package/dashboard/public/assets/furniture/succulent_green.png +0 -0
- package/dashboard/public/assets/furniture/treasurechest_closed_gold.png +0 -0
- package/dashboard/public/assets/furniture/water_cooler_better.png +0 -0
- package/dashboard/public/assets/furniture/whiteboard.png +0 -0
- package/dashboard/public/assets/furniture/whiteboard_stand_graph.png +0 -0
- package/dashboard/public/assets/furniture/window_blinds_open.png +0 -0
- package/dashboard/src/App.tsx +46 -0
- package/dashboard/src/components/RunDashboardButton.tsx +92 -0
- package/dashboard/src/components/SquadCard.tsx +49 -0
- package/dashboard/src/components/SquadSelector.tsx +67 -0
- package/dashboard/src/components/StatusBadge.tsx +32 -0
- package/dashboard/src/components/StatusBar.tsx +116 -0
- package/dashboard/src/hooks/useSquadSocket.ts +135 -0
- package/dashboard/src/lib/formatTime.ts +16 -0
- package/dashboard/src/lib/normalizeState.ts +25 -0
- package/dashboard/src/main.tsx +10 -0
- package/dashboard/src/office/AgentSprite.ts +241 -0
- package/dashboard/src/office/OfficeScene.ts +153 -0
- package/dashboard/src/office/PhaserGame.tsx +80 -0
- package/dashboard/src/office/RoomBuilder.ts +190 -0
- package/dashboard/src/office/assetKeys.ts +150 -0
- package/dashboard/src/office/palette.ts +32 -0
- package/dashboard/src/plugin/squadWatcher.ts +397 -0
- package/dashboard/src/store/useSquadStore.ts +56 -0
- package/dashboard/src/styles/globals.css +36 -0
- package/dashboard/src/types/state.ts +63 -0
- package/dashboard/src/vite-env.d.ts +1 -0
- package/dashboard/tsconfig.json +24 -0
- package/dashboard/vite.config.ts +13 -0
- package/package.json +59 -0
- package/public/sfx/slide-transition-sfx.mp3 +0 -0
- package/skills/README.md +84 -0
- package/skills/apify/SKILL.md +55 -0
- package/skills/blotato/SKILL.md +63 -0
- package/skills/canva/SKILL.md +60 -0
- package/skills/higgsfield/SKILL.md +147 -0
- package/skills/image-ai-generator/SKILL.md +124 -0
- package/skills/image-ai-generator/scripts/generate.py +175 -0
- package/skills/image-creator/SKILL.md +166 -0
- package/skills/image-creator/editorial-slide-template.js +645 -0
- package/skills/image-fetcher/SKILL.md +91 -0
- package/skills/imgbb-uploader/SKILL.md +73 -0
- package/skills/imgbb-uploader/scripts/upload.js +125 -0
- package/skills/instagram-publisher/README.md +36 -0
- package/skills/instagram-publisher/SKILL.md +231 -0
- package/skills/instagram-publisher/scripts/publish-playwright.js +418 -0
- package/skills/instagram-publisher/scripts/publish.js +521 -0
- package/skills/opensquad-agent-creator/SKILL.md +192 -0
- package/skills/opensquad-skill-creator/SKILL.md +420 -0
- package/skills/opensquad-skill-creator/agents/analyzer.md +274 -0
- package/skills/opensquad-skill-creator/agents/comparator.md +202 -0
- package/skills/opensquad-skill-creator/agents/grader.md +223 -0
- package/skills/opensquad-skill-creator/assets/eval_review.html +146 -0
- package/skills/opensquad-skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/opensquad-skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/opensquad-skill-creator/references/schemas.md +430 -0
- package/skills/opensquad-skill-creator/references/skill-format.md +235 -0
- package/skills/opensquad-skill-creator/scripts/__init__.py +0 -0
- package/skills/opensquad-skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/opensquad-skill-creator/scripts/quick_validate.py +103 -0
- package/skills/opensquad-skill-creator/scripts/run_eval.py +310 -0
- package/skills/opensquad-skill-creator/scripts/utils.py +47 -0
- package/skills/pdf-extractor/SKILL.md +57 -0
- package/skills/pdf-extractor/scripts/extract.py +82 -0
- package/skills/resend/SKILL.md +80 -0
- package/skills/run-dashboard/README.md +93 -0
- package/skills/run-dashboard/SKILL.md +173 -0
- package/skills/run-dashboard/scripts/finalize-state.js +273 -0
- package/skills/run-dashboard/scripts/generate.js +1296 -0
- package/skills/run-dashboard/scripts/serve.js +135 -0
- package/skills/run-dashboard/templates/run-dashboard-simple.template.html +191 -0
- package/skills/run-dashboard/templates/run-dashboard.template.html +1164 -0
- package/skills/smtp-sender/SKILL.md +88 -0
- package/skills/smtp-sender/scripts/send.js +478 -0
- package/skills/template-designer/SKILL.md +201 -0
- package/skills/template-designer/base-templates/model-a.html +27 -0
- package/skills/template-designer/base-templates/model-b.html +31 -0
- package/skills/template-designer/base-templates/model-c.html +42 -0
- package/skills/youtube-publisher/SKILL.md +232 -0
- package/skills/youtube-publisher/scripts/publish.js +2078 -0
- package/src/agents-cli.js +158 -0
- package/src/agents.js +134 -0
- package/src/i18n.js +48 -0
- package/src/init.js +442 -0
- package/src/locales/en.json +79 -0
- package/src/locales/es.json +78 -0
- package/src/locales/pt-BR.json +78 -0
- package/src/logger.js +38 -0
- package/src/prompt.js +46 -0
- package/src/readme/README.md +146 -0
- package/src/runs.js +318 -0
- package/src/skills-cli.js +157 -0
- package/src/skills.js +146 -0
- package/src/supabase-cli.js +584 -0
- package/src/update.js +169 -0
- package/templates/_opensquad/.opensquad-version +1 -0
- package/templates/_opensquad/_investigations/.gitkeep +0 -0
- package/templates/ide-templates/antigravity/.agent/rules/opensquad.md +68 -0
- package/templates/ide-templates/antigravity/.agent/workflows/opensquad.md +102 -0
- package/templates/ide-templates/claude-code/.claude/skills/opensquad/SKILL.md +182 -0
- package/templates/ide-templates/claude-code/.mcp.json +8 -0
- package/templates/ide-templates/claude-code/CLAUDE.md +57 -0
- package/templates/ide-templates/codex/.agents/skills/opensquad/SKILL.md +6 -0
- package/templates/ide-templates/codex/AGENTS.md +120 -0
- package/templates/ide-templates/cursor/.cursor/commands/opensquad.md +9 -0
- package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
- package/templates/ide-templates/cursor/.cursor/rules/opensquad.mdc +62 -0
- package/templates/ide-templates/cursor/.cursorignore +3 -0
- package/templates/ide-templates/gemini-cli/.gemini/settings.json +8 -0
- package/templates/ide-templates/gemini-cli/.gemini/skills/opensquad/SKILL.md +186 -0
- package/templates/ide-templates/gemini-cli/GEMINI.md +57 -0
- package/templates/ide-templates/opencode/.opencode/commands/opensquad.md +9 -0
- package/templates/ide-templates/opencode/AGENTS.md +120 -0
- package/templates/ide-templates/qwen-code/.qwen/settings.json +8 -0
- package/templates/ide-templates/qwen-code/.qwen/skills/opensquad/SKILL.md +182 -0
- package/templates/ide-templates/qwen-code/QWEN.md +57 -0
- package/templates/ide-templates/trae/.trae/mcp.json +8 -0
- package/templates/ide-templates/trae/.trae/rules/opensquad.md +64 -0
- package/templates/ide-templates/vscode-copilot/.github/copilot-instructions.md +59 -0
- package/templates/ide-templates/vscode-copilot/.github/prompts/opensquad.prompt.md +209 -0
- package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
- package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
- package/templates/package.json +8 -0
- package/templates/squads/.gitkeep +0 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_WORKSPACE_SLUG = 'contexto-informatica';
|
|
6
|
+
const DEFAULT_WORKSPACE_NAME = 'Contexto Informática';
|
|
7
|
+
const DEFAULT_BUCKET = 'os-artifacts';
|
|
8
|
+
const REQUIRED_TABLES = [
|
|
9
|
+
'os_workspaces',
|
|
10
|
+
'os_squads',
|
|
11
|
+
'os_skills',
|
|
12
|
+
'os_runs',
|
|
13
|
+
'os_run_state_snapshots',
|
|
14
|
+
'os_artifacts',
|
|
15
|
+
'os_events',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export async function supabaseCli(subcommand, args = [], targetDir = process.cwd()) {
|
|
19
|
+
if (subcommand === 'doctor' || !subcommand) {
|
|
20
|
+
return runDoctor(targetDir);
|
|
21
|
+
}
|
|
22
|
+
if (subcommand === 'import') {
|
|
23
|
+
return runImport(targetDir, parseFlags(args));
|
|
24
|
+
}
|
|
25
|
+
if (subcommand === 'mirror') {
|
|
26
|
+
return runMirror(targetDir, parseFlags(args));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`
|
|
30
|
+
Unknown Supabase command: ${subcommand}
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
npx @julioventura/opensquad supabase doctor
|
|
34
|
+
npx @julioventura/opensquad supabase import [--dry-run]
|
|
35
|
+
npx @julioventura/opensquad supabase mirror --squad <name> --run-id <run-id>
|
|
36
|
+
`);
|
|
37
|
+
return { success: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runDoctor(targetDir = process.cwd()) {
|
|
41
|
+
console.log('\n Opensquad Supabase Doctor\n');
|
|
42
|
+
|
|
43
|
+
const env = await loadEnv(targetDir);
|
|
44
|
+
const checks = [];
|
|
45
|
+
|
|
46
|
+
const url = env.OS_SUPABASE_URL || env.SUPABASE_URL;
|
|
47
|
+
const serviceRoleKey = env.OS_SUPABASE_SERVICE_ROLE_KEY || env.SUPABASE_SERVICE_ROLE_KEY;
|
|
48
|
+
const dbUrl = env.OS_SUPABASE_DB_URL;
|
|
49
|
+
const bucket = env.OS_SUPABASE_STORAGE_BUCKET || DEFAULT_BUCKET;
|
|
50
|
+
|
|
51
|
+
checks.push(['OS_SUPABASE_URL', Boolean(url)]);
|
|
52
|
+
checks.push(['OS_SUPABASE_SERVICE_ROLE_KEY', Boolean(serviceRoleKey)]);
|
|
53
|
+
checks.push(['OS_SUPABASE_DB_URL', Boolean(dbUrl), 'optional for CLI, required for psql fallback']);
|
|
54
|
+
|
|
55
|
+
let client;
|
|
56
|
+
try {
|
|
57
|
+
client = await createSupabaseClient(env);
|
|
58
|
+
checks.push(['@supabase/supabase-js', true]);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
checks.push(['@supabase/supabase-js', false, err.message]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (client) {
|
|
64
|
+
for (const table of REQUIRED_TABLES) {
|
|
65
|
+
const result = await client.from(table).select('id').limit(1);
|
|
66
|
+
checks.push([table, isOk(result), formatSupabaseError(result)]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const bucketResult = await client.storage.getBucket(bucket);
|
|
70
|
+
checks.push([`storage bucket ${bucket}`, Boolean(bucketResult.data) && !bucketResult.error, formatSupabaseError(bucketResult)]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
printChecks(checks);
|
|
74
|
+
const success = checks.every((check) => check[1] || check[2]?.startsWith('optional'));
|
|
75
|
+
console.log(success ? '\n Supabase configuration looks ready.\n' : '\n Supabase configuration is incomplete.\n');
|
|
76
|
+
return { success };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function runImport(targetDir = process.cwd(), options = {}) {
|
|
80
|
+
const dryRun = Boolean(options['dry-run']);
|
|
81
|
+
const inventory = await buildInventory(targetDir);
|
|
82
|
+
|
|
83
|
+
console.log(`\n Opensquad Supabase Import${dryRun ? ' (dry run)' : ''}\n`);
|
|
84
|
+
printInventory(inventory);
|
|
85
|
+
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
console.log('\n Dry run complete. No Supabase data was changed.\n');
|
|
88
|
+
return { success: true, inventory };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const env = await loadEnv(targetDir);
|
|
92
|
+
const client = await createSupabaseClient(env);
|
|
93
|
+
const workspace = await ensureWorkspace(client, env);
|
|
94
|
+
|
|
95
|
+
await importSkills(client, workspace.id, inventory.skills);
|
|
96
|
+
await importSquads(client, workspace.id, inventory.squads);
|
|
97
|
+
await importMemoryArtifacts(client, workspace.id, inventory.memoryFiles);
|
|
98
|
+
await insertEvent(client, workspace.id, 'import.completed', {
|
|
99
|
+
counts: {
|
|
100
|
+
skills: inventory.skills.length,
|
|
101
|
+
squads: inventory.squads.length,
|
|
102
|
+
memoryFiles: inventory.memoryFiles.length,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log('\n Import complete.\n');
|
|
107
|
+
return { success: true, inventory };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function runMirror(targetDir = process.cwd(), options = {}) {
|
|
111
|
+
const squad = options.squad;
|
|
112
|
+
const runId = options['run-id'];
|
|
113
|
+
|
|
114
|
+
if (!squad || !runId) {
|
|
115
|
+
console.log('\n Usage: opensquad supabase mirror --squad <name> --run-id <run-id>\n');
|
|
116
|
+
return { success: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const env = await loadEnv(targetDir);
|
|
120
|
+
if ((env.OS_SUPABASE_MODE || '').toLowerCase() === 'off') {
|
|
121
|
+
console.log('\n Supabase mirror skipped because OS_SUPABASE_MODE=off.\n');
|
|
122
|
+
return { success: true, skipped: true };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const client = await createSupabaseClient(env);
|
|
126
|
+
const workspace = await ensureWorkspace(client, env);
|
|
127
|
+
const runDir = await findRunDir(targetDir, squad, runId);
|
|
128
|
+
if (!runDir) {
|
|
129
|
+
console.log(`\n Run folder not found for ${squad}/${runId}.\n`);
|
|
130
|
+
return { success: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const statePath = join(runDir, 'state.json');
|
|
134
|
+
const state = await readJsonFile(statePath);
|
|
135
|
+
const run = await upsertRun(client, workspace.id, squad, runId, runDir, state);
|
|
136
|
+
if (state) {
|
|
137
|
+
await insertStateSnapshot(client, workspace.id, run.id, squad, runId, statePath, state);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const artifacts = await collectArtifacts(targetDir, runDir, { limitBytes: Number(env.OS_SUPABASE_MIRROR_MAX_ARTIFACT_BYTES || 0) });
|
|
141
|
+
await upsertArtifacts(client, workspace.id, run.id, squad, runId, artifacts);
|
|
142
|
+
await insertEvent(client, workspace.id, 'mirror.completed', {
|
|
143
|
+
squad,
|
|
144
|
+
runId,
|
|
145
|
+
artifactCount: artifacts.length,
|
|
146
|
+
stateFound: Boolean(state),
|
|
147
|
+
}, run.id);
|
|
148
|
+
|
|
149
|
+
console.log(`\n Mirrored ${squad}/${runId}: ${artifacts.length} artifact metadata records.\n`);
|
|
150
|
+
return { success: true, artifacts };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function createSupabaseClient(env) {
|
|
154
|
+
const url = env.OS_SUPABASE_URL || env.SUPABASE_URL;
|
|
155
|
+
const key = env.OS_SUPABASE_SERVICE_ROLE_KEY || env.SUPABASE_SERVICE_ROLE_KEY;
|
|
156
|
+
if (!url) throw new Error('Missing OS_SUPABASE_URL.');
|
|
157
|
+
if (!key) throw new Error('Missing OS_SUPABASE_SERVICE_ROLE_KEY.');
|
|
158
|
+
|
|
159
|
+
let module;
|
|
160
|
+
try {
|
|
161
|
+
module = await import('@supabase/supabase-js');
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new Error('Missing dependency @supabase/supabase-js. Run npm install.', { cause: err });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return module.createClient(url, key, {
|
|
167
|
+
auth: {
|
|
168
|
+
persistSession: false,
|
|
169
|
+
autoRefreshToken: false,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function ensureWorkspace(client, env) {
|
|
175
|
+
const id = env.OS_SUPABASE_WORKSPACE_ID || undefined;
|
|
176
|
+
const slug = env.OS_SUPABASE_WORKSPACE_SLUG || DEFAULT_WORKSPACE_SLUG;
|
|
177
|
+
const name = env.OS_SUPABASE_WORKSPACE_NAME || DEFAULT_WORKSPACE_NAME;
|
|
178
|
+
const payload = id ? { id, slug, name } : { slug, name };
|
|
179
|
+
const onConflict = id ? 'id' : 'slug';
|
|
180
|
+
const { data, error } = await client
|
|
181
|
+
.from('os_workspaces')
|
|
182
|
+
.upsert(payload, { onConflict })
|
|
183
|
+
.select()
|
|
184
|
+
.single();
|
|
185
|
+
|
|
186
|
+
if (error) throw new Error(`Unable to upsert workspace: ${formatSupabaseError({ error, status: data?.status })}`);
|
|
187
|
+
return data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function importSkills(client, workspaceId, skills) {
|
|
191
|
+
if (skills.length === 0) return;
|
|
192
|
+
const rows = skills.map((skill) => ({
|
|
193
|
+
workspace_id: workspaceId,
|
|
194
|
+
slug: skill.slug,
|
|
195
|
+
name: skill.frontmatter.name || skill.slug,
|
|
196
|
+
type: skill.frontmatter.type || null,
|
|
197
|
+
version: skill.frontmatter.version || null,
|
|
198
|
+
source_path: skill.path,
|
|
199
|
+
frontmatter: skill.frontmatter,
|
|
200
|
+
content_hash: skill.hash,
|
|
201
|
+
metadata: { byteSize: skill.byteSize },
|
|
202
|
+
}));
|
|
203
|
+
const { error } = await client.from('os_skills').upsert(rows, { onConflict: 'workspace_id,slug' });
|
|
204
|
+
if (error) throw new Error(`Unable to import skills: ${formatSupabaseError({ error })}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function importSquads(client, workspaceId, squads) {
|
|
208
|
+
if (squads.length === 0) return;
|
|
209
|
+
const rows = squads.map((squad) => ({
|
|
210
|
+
workspace_id: workspaceId,
|
|
211
|
+
slug: squad.slug,
|
|
212
|
+
name: squad.name || squad.slug,
|
|
213
|
+
source_path: squad.path,
|
|
214
|
+
config: { squadYaml: squad.content },
|
|
215
|
+
content_hash: squad.hash,
|
|
216
|
+
metadata: { byteSize: squad.byteSize },
|
|
217
|
+
}));
|
|
218
|
+
const { error } = await client.from('os_squads').upsert(rows, { onConflict: 'workspace_id,slug' });
|
|
219
|
+
if (error) throw new Error(`Unable to import squads: ${formatSupabaseError({ error })}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function importMemoryArtifacts(client, workspaceId, memoryFiles) {
|
|
223
|
+
if (memoryFiles.length === 0) return;
|
|
224
|
+
const rows = memoryFiles.map((file) => ({
|
|
225
|
+
workspace_id: workspaceId,
|
|
226
|
+
relative_path: file.path,
|
|
227
|
+
artifact_type: 'memory',
|
|
228
|
+
byte_size: file.byteSize,
|
|
229
|
+
content_hash: file.hash,
|
|
230
|
+
metadata: { preview: file.preview },
|
|
231
|
+
}));
|
|
232
|
+
const { error } = await client.from('os_artifacts').upsert(rows, { onConflict: 'workspace_id,relative_path,content_hash' });
|
|
233
|
+
if (error) throw new Error(`Unable to import memory artifacts: ${formatSupabaseError({ error })}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function upsertRun(client, workspaceId, squad, runId, runDir, state) {
|
|
237
|
+
const { data: squadRecord } = await client
|
|
238
|
+
.from('os_squads')
|
|
239
|
+
.select('id')
|
|
240
|
+
.eq('workspace_id', workspaceId)
|
|
241
|
+
.eq('slug', squad)
|
|
242
|
+
.maybeSingle();
|
|
243
|
+
|
|
244
|
+
const payload = {
|
|
245
|
+
workspace_id: workspaceId,
|
|
246
|
+
squad_id: squadRecord?.id || null,
|
|
247
|
+
squad_slug: squad,
|
|
248
|
+
run_id: runId,
|
|
249
|
+
status: state?.status || 'unknown',
|
|
250
|
+
state: state || {},
|
|
251
|
+
source_path: normalizePath(runDir),
|
|
252
|
+
started_at: state?.startedAt || null,
|
|
253
|
+
completed_at: state?.completedAt || null,
|
|
254
|
+
failed_at: state?.failedAt || null,
|
|
255
|
+
};
|
|
256
|
+
const { data, error } = await client
|
|
257
|
+
.from('os_runs')
|
|
258
|
+
.upsert(payload, { onConflict: 'workspace_id,squad_slug,run_id' })
|
|
259
|
+
.select()
|
|
260
|
+
.single();
|
|
261
|
+
|
|
262
|
+
if (error) throw new Error(`Unable to upsert run: ${formatSupabaseError({ error })}`);
|
|
263
|
+
return data;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function insertStateSnapshot(client, workspaceId, runUuid, squad, runId, statePath, state) {
|
|
267
|
+
const payload = {
|
|
268
|
+
workspace_id: workspaceId,
|
|
269
|
+
run_uuid: runUuid,
|
|
270
|
+
squad_slug: squad,
|
|
271
|
+
run_id: runId,
|
|
272
|
+
status: state.status || 'unknown',
|
|
273
|
+
step: state.step || null,
|
|
274
|
+
state,
|
|
275
|
+
state_hash: hashText(JSON.stringify(state)),
|
|
276
|
+
source_path: normalizePath(statePath),
|
|
277
|
+
};
|
|
278
|
+
const { error } = await client.from('os_run_state_snapshots').insert(payload);
|
|
279
|
+
if (error) throw new Error(`Unable to insert state snapshot: ${formatSupabaseError({ error })}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function upsertArtifacts(client, workspaceId, runUuid, squad, runId, artifacts) {
|
|
283
|
+
if (artifacts.length === 0) return;
|
|
284
|
+
const rows = artifacts.map((artifact) => ({
|
|
285
|
+
workspace_id: workspaceId,
|
|
286
|
+
run_uuid: runUuid,
|
|
287
|
+
squad_slug: squad,
|
|
288
|
+
run_id: runId,
|
|
289
|
+
relative_path: artifact.path,
|
|
290
|
+
artifact_type: artifact.type,
|
|
291
|
+
mime_type: artifact.mimeType,
|
|
292
|
+
byte_size: artifact.byteSize,
|
|
293
|
+
content_hash: artifact.hash,
|
|
294
|
+
storage_bucket: null,
|
|
295
|
+
storage_path: null,
|
|
296
|
+
metadata: artifact.metadata,
|
|
297
|
+
}));
|
|
298
|
+
const { error } = await client.from('os_artifacts').upsert(rows, { onConflict: 'workspace_id,relative_path,content_hash' });
|
|
299
|
+
if (error) throw new Error(`Unable to upsert artifacts: ${formatSupabaseError({ error })}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function insertEvent(client, workspaceId, eventType, payload, runUuid = null) {
|
|
303
|
+
const { error } = await client.from('os_events').insert({
|
|
304
|
+
workspace_id: workspaceId,
|
|
305
|
+
run_uuid: runUuid,
|
|
306
|
+
event_type: eventType,
|
|
307
|
+
payload,
|
|
308
|
+
});
|
|
309
|
+
if (error) throw new Error(`Unable to insert event: ${formatSupabaseError({ error })}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function buildInventory(targetDir) {
|
|
313
|
+
const skills = [];
|
|
314
|
+
const squads = [];
|
|
315
|
+
const memoryFiles = [];
|
|
316
|
+
|
|
317
|
+
for (const skillDir of await listDirectories(join(targetDir, 'skills'))) {
|
|
318
|
+
const skillPath = join(targetDir, 'skills', skillDir, 'SKILL.md');
|
|
319
|
+
const file = await readTextFile(skillPath);
|
|
320
|
+
if (!file) continue;
|
|
321
|
+
skills.push({
|
|
322
|
+
slug: skillDir,
|
|
323
|
+
path: normalizePath(relative(targetDir, skillPath)),
|
|
324
|
+
content: file.content,
|
|
325
|
+
byteSize: file.byteSize,
|
|
326
|
+
hash: hashText(file.content),
|
|
327
|
+
frontmatter: parseFrontmatter(file.content),
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (const squadDir of await listDirectories(join(targetDir, 'squads'))) {
|
|
332
|
+
const squadPath = join(targetDir, 'squads', squadDir, 'squad.yaml');
|
|
333
|
+
const file = await readTextFile(squadPath);
|
|
334
|
+
if (!file) continue;
|
|
335
|
+
squads.push({
|
|
336
|
+
slug: squadDir,
|
|
337
|
+
name: extractYamlScalar(file.content, 'name') || extractYamlScalar(file.content, 'displayName') || squadDir,
|
|
338
|
+
path: normalizePath(relative(targetDir, squadPath)),
|
|
339
|
+
content: file.content,
|
|
340
|
+
byteSize: file.byteSize,
|
|
341
|
+
hash: hashText(file.content),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const memoryPath of [
|
|
346
|
+
join(targetDir, '_opensquad', '_memory', 'company.md'),
|
|
347
|
+
join(targetDir, '_opensquad', '_memory', 'preferences.md'),
|
|
348
|
+
join(targetDir, '_opensquad', '_memory', 'brands.md'),
|
|
349
|
+
]) {
|
|
350
|
+
const file = await readTextFile(memoryPath);
|
|
351
|
+
if (!file) continue;
|
|
352
|
+
memoryFiles.push({
|
|
353
|
+
path: normalizePath(relative(targetDir, memoryPath)),
|
|
354
|
+
byteSize: file.byteSize,
|
|
355
|
+
hash: hashText(file.content),
|
|
356
|
+
preview: file.content.slice(0, 500),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { skills, squads, memoryFiles };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function collectArtifacts(targetDir, runDir, options = {}) {
|
|
364
|
+
const files = await listFiles(runDir);
|
|
365
|
+
const artifacts = [];
|
|
366
|
+
for (const filePath of files) {
|
|
367
|
+
const info = await stat(filePath);
|
|
368
|
+
if (options.limitBytes > 0 && info.size > options.limitBytes) {
|
|
369
|
+
artifacts.push({
|
|
370
|
+
path: normalizePath(relative(targetDir, filePath)),
|
|
371
|
+
type: 'file',
|
|
372
|
+
mimeType: guessMimeType(filePath),
|
|
373
|
+
byteSize: info.size,
|
|
374
|
+
hash: null,
|
|
375
|
+
metadata: { hashSkipped: true, reason: 'larger_than_limit' },
|
|
376
|
+
});
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const content = await readFile(filePath);
|
|
380
|
+
artifacts.push({
|
|
381
|
+
path: normalizePath(relative(targetDir, filePath)),
|
|
382
|
+
type: artifactType(filePath),
|
|
383
|
+
mimeType: guessMimeType(filePath),
|
|
384
|
+
byteSize: info.size,
|
|
385
|
+
hash: hashBuffer(content),
|
|
386
|
+
metadata: {},
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return artifacts;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function findRunDir(targetDir, squad, runId) {
|
|
393
|
+
const candidates = [
|
|
394
|
+
join(targetDir, 'squads', squad, 'output', runId),
|
|
395
|
+
join(targetDir, 'squads', squad, 'output', 'archive', runId),
|
|
396
|
+
];
|
|
397
|
+
for (const candidate of candidates) {
|
|
398
|
+
try {
|
|
399
|
+
const info = await stat(candidate);
|
|
400
|
+
if (info.isDirectory()) return candidate;
|
|
401
|
+
} catch {
|
|
402
|
+
// Try next candidate
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function listDirectories(dir) {
|
|
409
|
+
try {
|
|
410
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
411
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
412
|
+
} catch {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function listFiles(dir) {
|
|
418
|
+
const files = [];
|
|
419
|
+
let entries;
|
|
420
|
+
try {
|
|
421
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
422
|
+
} catch {
|
|
423
|
+
return files;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const entry of entries) {
|
|
427
|
+
const fullPath = join(dir, entry.name);
|
|
428
|
+
if (entry.isDirectory()) {
|
|
429
|
+
files.push(...await listFiles(fullPath));
|
|
430
|
+
} else if (entry.isFile()) {
|
|
431
|
+
files.push(fullPath);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return files;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function readTextFile(path) {
|
|
438
|
+
try {
|
|
439
|
+
const content = await readFile(path, 'utf-8');
|
|
440
|
+
return { content, byteSize: Buffer.byteLength(content) };
|
|
441
|
+
} catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function readJsonFile(path) {
|
|
447
|
+
try {
|
|
448
|
+
return JSON.parse(await readFile(path, 'utf-8'));
|
|
449
|
+
} catch {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export async function loadEnv(targetDir = process.cwd()) {
|
|
455
|
+
const env = { ...process.env };
|
|
456
|
+
try {
|
|
457
|
+
const raw = await readFile(join(targetDir, '.env'), 'utf-8');
|
|
458
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
459
|
+
const trimmed = line.trim();
|
|
460
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
461
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
462
|
+
if (separatorIndex === -1) continue;
|
|
463
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
464
|
+
const value = trimmed.slice(separatorIndex + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
465
|
+
if (key && !(key in env)) env[key] = value;
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
// Optional .env
|
|
469
|
+
}
|
|
470
|
+
return env;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function parseFlags(args) {
|
|
474
|
+
const flags = {};
|
|
475
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
476
|
+
const token = args[index];
|
|
477
|
+
if (!token.startsWith('--')) continue;
|
|
478
|
+
const key = token.slice(2);
|
|
479
|
+
const next = args[index + 1];
|
|
480
|
+
if (!next || next.startsWith('--')) {
|
|
481
|
+
flags[key] = true;
|
|
482
|
+
} else {
|
|
483
|
+
flags[key] = next;
|
|
484
|
+
index += 1;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return flags;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function parseFrontmatter(content) {
|
|
491
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
492
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
493
|
+
if (!match) return {};
|
|
494
|
+
const result = {};
|
|
495
|
+
for (const line of match[1].split('\n')) {
|
|
496
|
+
const item = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
497
|
+
if (!item) continue;
|
|
498
|
+
const value = item[2].trim();
|
|
499
|
+
if (value && value !== '>' && value !== '|') result[item[1]] = stripQuotes(value);
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function extractYamlScalar(content, key) {
|
|
505
|
+
const match = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
506
|
+
return match ? stripQuotes(match[1].trim()) : null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function stripQuotes(value) {
|
|
510
|
+
return value.replace(/^['"]|['"]$/g, '');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function hashText(content) {
|
|
514
|
+
return hashBuffer(Buffer.from(content));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function hashBuffer(buffer) {
|
|
518
|
+
return createHash('sha256').update(buffer).digest('hex');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function normalizePath(path) {
|
|
522
|
+
return path.replace(/\\/g, '/');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function guessMimeType(path) {
|
|
526
|
+
const lower = path.toLowerCase();
|
|
527
|
+
if (lower.endsWith('.json')) return 'application/json';
|
|
528
|
+
if (lower.endsWith('.md')) return 'text/markdown';
|
|
529
|
+
if (lower.endsWith('.html')) return 'text/html';
|
|
530
|
+
if (lower.endsWith('.txt')) return 'text/plain';
|
|
531
|
+
if (lower.endsWith('.csv')) return 'text/csv';
|
|
532
|
+
if (lower.endsWith('.png')) return 'image/png';
|
|
533
|
+
if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg';
|
|
534
|
+
if (lower.endsWith('.mp4')) return 'video/mp4';
|
|
535
|
+
if (lower.endsWith('.mp3')) return 'audio/mpeg';
|
|
536
|
+
if (lower.endsWith('.pdf')) return 'application/pdf';
|
|
537
|
+
return 'application/octet-stream';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function artifactType(path) {
|
|
541
|
+
const mimeType = guessMimeType(path);
|
|
542
|
+
if (mimeType.startsWith('image/')) return 'image';
|
|
543
|
+
if (mimeType.startsWith('video/')) return 'video';
|
|
544
|
+
if (mimeType.startsWith('audio/')) return 'audio';
|
|
545
|
+
if (mimeType === 'text/html') return 'dashboard';
|
|
546
|
+
if (mimeType === 'application/json' || mimeType.startsWith('text/')) return 'text';
|
|
547
|
+
return 'file';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function printChecks(checks) {
|
|
551
|
+
for (const [name, ok, note] of checks) {
|
|
552
|
+
const status = ok ? 'OK' : 'MISSING';
|
|
553
|
+
console.log(` ${status.padEnd(7)} ${name}${note ? ` — ${note}` : ''}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function isOk(result) {
|
|
558
|
+
return !result?.error && (result?.status == null || result.status < 400);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function formatSupabaseError(result) {
|
|
562
|
+
const error = result?.error;
|
|
563
|
+
if (!error) return result?.statusText || '';
|
|
564
|
+
if (typeof error === 'string') return error;
|
|
565
|
+
const parts = [];
|
|
566
|
+
if (error.code) parts.push(error.code);
|
|
567
|
+
if (error.message) parts.push(error.message);
|
|
568
|
+
if (error.details) parts.push(error.details);
|
|
569
|
+
if (error.hint) parts.push(`hint: ${error.hint}`);
|
|
570
|
+
if (parts.length > 0) return parts.join(' — ');
|
|
571
|
+
if (result?.status || result?.statusText) {
|
|
572
|
+
return `${result.status || ''} ${result.statusText || ''}`.trim();
|
|
573
|
+
}
|
|
574
|
+
return JSON.stringify(error);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function printInventory(inventory) {
|
|
578
|
+
console.log(` Skills: ${inventory.skills.length}`);
|
|
579
|
+
console.log(` Squads: ${inventory.squads.length}`);
|
|
580
|
+
console.log(` Memory files: ${inventory.memoryFiles.length}`);
|
|
581
|
+
if (inventory.squads.length > 0) {
|
|
582
|
+
console.log(` Squad slugs: ${inventory.squads.map((squad) => squad.slug).join(', ')}`);
|
|
583
|
+
}
|
|
584
|
+
}
|