New package @hanzo/imanage: an embedded-app panel over @hanzo/ai for iManage Work (the dominant legal document management system), mirroring the packages/procore OAuth + API-proxy + pure-core shape. - OAuth2 Authorization Code against the iManage Control Center (/auth/oauth2/authorize + /token); server-side token exchange + refresh, the client secret never reaches the browser. - Work API v2 wrappers (workspaces, folder children, document profile + content, search, gated profile write-back) scoped by customer + library in the path, X-Auth-Token auth, envelope/offset-limit pagination. Pure. - Pure legal document-context assembly with text extraction (binary/OCR out of scope, reported honestly) and one truncation-honest windowing algorithm. - Four AI actions — summarize document, extract key clauses/dates/parties, search-and-synthesize a matter, compare a document set — plus freeform ask, over a single grounded ask() path. - Same-origin proxy keeps tokens + secret server-side; NEVER logs document content. Legal confidentiality posture documented in the README. - 121 vitest tests (config/oauth/api/parse/documents/hanzo/actions/panel), tsc --noEmit clean, node build.js green. Registers packages/imanage in pnpm-workspace.yaml (one line).
87 lines
3.2 KiB
JavaScript
87 lines
3.2 KiB
JavaScript
// Build @hanzo/imanage into dist/: bundle the web panel (app.ts) as ESM for the
|
|
// browser, copy index.html (with its entry script stamped) + styles.css, and
|
|
// bundle the OAuth + Work-API-proxy server for Node. No framework — esbuild + Node
|
|
// stdlib only. @hanzo/ai is bundled into both outputs (it is the headless client
|
|
// the panel and server both call).
|
|
//
|
|
// node build.js → production build
|
|
// node build.js --watch → rebuild on change
|
|
// HANZO_IMANAGE_BASE=https://localhost:8443 node build.js → dev base (informational)
|
|
//
|
|
// Output layout:
|
|
// dist/index.html dist/app.js dist/styles.css (panel)
|
|
// dist/server.js (service)
|
|
//
|
|
// The panel is HOSTED over hanzoai/static behind hanzoai/ingress at
|
|
// imanage.hanzo.ai; the server runs as a small service (OAuth callback + Work API
|
|
// proxy). All model calls go to api.hanzo.ai regardless of the host base.
|
|
|
|
import esbuild from 'esbuild';
|
|
import { rmSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const BASE = (process.env.HANZO_IMANAGE_BASE || 'https://imanage.hanzo.ai').replace(/\/+$/, '');
|
|
const watch = process.argv.includes('--watch');
|
|
const src = join(__dirname, 'src');
|
|
const dist = join(__dirname, 'dist');
|
|
|
|
async function build() {
|
|
rmSync(dist, { recursive: true, force: true });
|
|
mkdirSync(dist, { recursive: true });
|
|
|
|
// Bundle the panel as ESM for the browser.
|
|
const panelCtx = await esbuild.context({
|
|
entryPoints: { app: join(src, 'app.ts') },
|
|
outdir: dist,
|
|
bundle: true,
|
|
format: 'esm',
|
|
platform: 'browser',
|
|
target: ['chrome90', 'edge90', 'firefox90', 'safari15'],
|
|
sourcemap: true,
|
|
minify: !watch,
|
|
logLevel: 'info',
|
|
});
|
|
await panelCtx.rebuild();
|
|
|
|
// Copy index.html (stamp __ENTRY__ + base) and styles.css.
|
|
const html = readFileSync(join(src, 'index.html'), 'utf8')
|
|
.split('__ENTRY__').join('app.js')
|
|
.replace('</head>', ` <meta name="hanzo:base" content="${BASE}" />\n</head>`);
|
|
writeFileSync(join(dist, 'index.html'), html);
|
|
copyFileSync(join(src, 'styles.css'), join(dist, 'styles.css'));
|
|
|
|
// Bundle the server as a Node ESM binary — our pure stdlib code + @hanzo/ai.
|
|
const serverCtx = await esbuild.context({
|
|
entryPoints: [join(src, 'server.ts')],
|
|
outfile: join(dist, 'server.js'),
|
|
bundle: true,
|
|
format: 'esm',
|
|
platform: 'node',
|
|
target: ['node18'],
|
|
sourcemap: true,
|
|
minify: !watch,
|
|
banner: { js: "import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);" },
|
|
logLevel: 'info',
|
|
});
|
|
await serverCtx.rebuild();
|
|
|
|
console.log(`Hanzo AI for iManage built -> dist/ (base ${BASE})`);
|
|
console.log(' Panel: dist/index.html · Service: dist/server.js');
|
|
|
|
if (watch) {
|
|
await panelCtx.watch();
|
|
await serverCtx.watch();
|
|
console.log('watching...');
|
|
} else {
|
|
await panelCtx.dispose();
|
|
await serverCtx.dispose();
|
|
}
|
|
}
|
|
|
|
build().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|