ftown 0.19.44 → 0.19.48

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 (53) hide show
  1. package/claude-mod/.claude-plugin/plugin.json +6 -0
  2. package/claude-mod/hooks/hooks.json +3 -0
  3. package/claude-mod/hooks/register.js +137 -0
  4. package/dist/agent-commands.js +17 -2
  5. package/dist/agent-commands.js.map +1 -1
  6. package/dist/attention-store.d.ts +21 -1
  7. package/dist/attention-store.js +131 -3
  8. package/dist/attention-store.js.map +1 -1
  9. package/dist/claude-mod-installer.d.ts +9 -0
  10. package/dist/claude-mod-installer.js +55 -0
  11. package/dist/claude-mod-installer.js.map +1 -0
  12. package/dist/ftown-cli.d.ts +1 -1
  13. package/dist/ftown-cli.js +11 -3
  14. package/dist/ftown-cli.js.map +1 -1
  15. package/dist/harness-registry.js +4 -0
  16. package/dist/harness-registry.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +719 -671
  19. package/dist/index.js.map +1 -1
  20. package/dist/install-ftown-command-cli.d.ts +1 -1
  21. package/dist/install-ftown-command-cli.js +35 -44
  22. package/dist/install-ftown-command-cli.js.map +1 -1
  23. package/dist/installation-manifest.d.ts +25 -0
  24. package/dist/installation-manifest.js +37 -0
  25. package/dist/installation-manifest.js.map +1 -0
  26. package/dist/local-api-server.js +12 -1
  27. package/dist/local-api-server.js.map +1 -1
  28. package/dist/mail-delivery.d.ts +24 -0
  29. package/dist/mail-delivery.js +155 -7
  30. package/dist/mail-delivery.js.map +1 -1
  31. package/dist/mcp/fleet.d.ts +2 -2
  32. package/dist/native-mail.d.ts +31 -0
  33. package/dist/native-mail.js +142 -0
  34. package/dist/native-mail.js.map +1 -0
  35. package/dist/production-dependencies.d.ts +13 -0
  36. package/dist/production-dependencies.js +175 -0
  37. package/dist/production-dependencies.js.map +1 -0
  38. package/dist/push-notifier.d.ts +40 -0
  39. package/dist/push-notifier.js +126 -0
  40. package/dist/push-notifier.js.map +1 -0
  41. package/dist/sessions-commands.js +278 -310
  42. package/dist/sessions-commands.js.map +1 -1
  43. package/dist/setup-state.d.ts +1 -2
  44. package/dist/setup-state.js +31 -11
  45. package/dist/setup-state.js.map +1 -1
  46. package/dist/setup.js +3 -1
  47. package/dist/setup.js.map +1 -1
  48. package/dist/start-commands.d.ts +10 -0
  49. package/dist/start-commands.js +15 -0
  50. package/dist/start-commands.js.map +1 -0
  51. package/opencode-plugin/ftown.js +142 -129
  52. package/package.json +3 -2
  53. package/skills/ftown/SKILL.md +7 -2
@@ -0,0 +1,175 @@
1
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
2
+ import { basename, dirname, isAbsolute, join, relative, sep } from 'node:path';
3
+ function metadata(directory) {
4
+ return JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
5
+ }
6
+ function inside(root, path) {
7
+ const part = relative(root, path);
8
+ return part === '' || (!isAbsolute(part) && part !== '..' && !part.startsWith(`..${sep}`));
9
+ }
10
+ function moduleTreeRoot(packageRoot) {
11
+ const parent = dirname(packageRoot);
12
+ if (basename(parent) === 'node_modules')
13
+ return parent;
14
+ if (basename(parent).startsWith('@') && basename(dirname(parent)) === 'node_modules')
15
+ return dirname(parent);
16
+ return join(packageRoot, 'node_modules');
17
+ }
18
+ function findDependency(from, name) {
19
+ if (!/^(@[a-z0-9_.-]+\/)?[a-z0-9_.-]+$/i.test(name) || name.split('/').some((part) => part === '.' || part === '..'))
20
+ throw new Error(`Invalid production dependency name: ${name}`);
21
+ let current = from;
22
+ for (;;) {
23
+ const candidate = join(current, 'node_modules', name);
24
+ if (existsSync(join(candidate, 'package.json')))
25
+ return candidate;
26
+ const parent = dirname(current);
27
+ if (parent === current)
28
+ return undefined;
29
+ current = parent;
30
+ }
31
+ }
32
+ /** Preserve each resolved production edge, including root-local/outer-hoisted version conflicts. */
33
+ export function productionDependencies(packageRoot) {
34
+ const root = realpathSync(packageRoot);
35
+ const tree = moduleTreeRoot(root);
36
+ const packages = new Map();
37
+ const queue = [];
38
+ function edges(directory) {
39
+ const pkg = metadata(directory);
40
+ const names = new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.optionalDependencies ?? {}),
41
+ ...Object.keys(pkg.peerDependencies ?? {}), ...(pkg.bundledDependencies ?? pkg.bundleDependencies ?? [])]);
42
+ const result = [];
43
+ for (const name of names) {
44
+ const source = findDependency(directory, name);
45
+ const optional = Object.hasOwn(pkg.optionalDependencies ?? {}, name) || pkg.peerDependenciesMeta?.[name]?.optional;
46
+ if (!source) {
47
+ if (optional)
48
+ continue;
49
+ throw new Error(`Missing production dependency ${name} required by ${directory}`);
50
+ }
51
+ if (!inside(tree, source) || !inside(tree, realpathSync(source)))
52
+ throw new Error(`Production dependency escapes its module tree: ${name}`);
53
+ result.push({ name, source });
54
+ }
55
+ return result;
56
+ }
57
+ function add(source, target) {
58
+ const previous = packages.get(target);
59
+ if (previous) {
60
+ if (previous.source !== source)
61
+ throw new Error(`Production dependency placement conflict: ${target}`);
62
+ return;
63
+ }
64
+ const dependency = { source, target };
65
+ packages.set(target, dependency);
66
+ queue.push(dependency);
67
+ }
68
+ function accessible(from, name) {
69
+ let current = from;
70
+ for (;;) {
71
+ if (basename(current) !== 'node_modules') {
72
+ const dependency = packages.get(join(current, 'node_modules', name));
73
+ if (dependency)
74
+ return dependency;
75
+ }
76
+ const parent = dirname(current);
77
+ if (current === '.' || parent === current)
78
+ return undefined;
79
+ current = parent;
80
+ }
81
+ }
82
+ // Reserve direct dependencies before hoisting transitive ones: the application wins its own slots.
83
+ for (const edge of edges(root))
84
+ add(edge.source, join('node_modules', edge.name));
85
+ for (let index = 0; index < queue.length; index++) {
86
+ const owner = queue[index];
87
+ for (const edge of edges(owner.source)) {
88
+ const existing = accessible(owner.target, edge.name);
89
+ if (existing?.source === edge.source)
90
+ continue;
91
+ const sourceLocal = inside(join(owner.source, 'node_modules'), edge.source);
92
+ const target = existing || sourceLocal ? join(owner.target, 'node_modules', edge.name) : join('node_modules', edge.name);
93
+ add(edge.source, target);
94
+ }
95
+ }
96
+ return [...packages.values()].sort((a, b) => a.target.localeCompare(b.target));
97
+ }
98
+ export function assertSafeDirectory(path) {
99
+ if (!existsSync(path))
100
+ mkdirSync(path, { recursive: true, mode: 0o700 });
101
+ if (lstatSync(path).isSymbolicLink() || !lstatSync(path).isDirectory())
102
+ throw new Error(`Setup refuses symlink or non-directory: ${path}`);
103
+ }
104
+ export function assertNoSymlinks(path) {
105
+ let stat;
106
+ try {
107
+ stat = lstatSync(path);
108
+ }
109
+ catch (error) {
110
+ if (error.code === 'ENOENT')
111
+ return;
112
+ throw error;
113
+ }
114
+ if (stat.isSymbolicLink())
115
+ throw new Error(`Setup refuses symlink: ${path}`);
116
+ if (stat.isDirectory())
117
+ for (const name of readdirSync(path))
118
+ assertNoSymlinks(join(path, name));
119
+ }
120
+ /** Copy own package files and native assets; node_modules are reconstructed from the production graph. */
121
+ export function copyRuntimeTree(source, target, inventoryRoot) {
122
+ if (!inside(inventoryRoot, target))
123
+ throw new Error('Runtime target escapes installation directory');
124
+ const files = [];
125
+ function copy(from, to) {
126
+ const stat = lstatSync(from);
127
+ if (stat.isSymbolicLink())
128
+ throw new Error(`Setup refuses source symlink: ${from}`);
129
+ if (stat.isDirectory()) {
130
+ assertSafeDirectory(to);
131
+ for (const name of readdirSync(from).sort()) {
132
+ if (name !== 'node_modules')
133
+ copy(join(from, name), join(to, name));
134
+ }
135
+ }
136
+ else if (stat.isFile()) {
137
+ let targetStat;
138
+ try {
139
+ targetStat = lstatSync(to);
140
+ }
141
+ catch (error) {
142
+ if (error.code !== 'ENOENT')
143
+ throw error;
144
+ }
145
+ if (targetStat && (targetStat.isSymbolicLink() || !targetStat.isFile()))
146
+ throw new Error(`Setup refuses unsafe runtime file: ${to}`);
147
+ writeFileSync(to, readFileSync(from), { mode: stat.mode & 0o777 });
148
+ chmodSync(to, stat.mode & 0o777);
149
+ files.push(relative(inventoryRoot, to).split(sep).join('/'));
150
+ }
151
+ else
152
+ throw new Error(`Unsupported runtime asset: ${from}`);
153
+ }
154
+ copy(source, target);
155
+ return files;
156
+ }
157
+ /** Public fixture/installer helper: package metadata is copied separately by its caller. */
158
+ export function copyProductionDependencies(packageRoot, targetRoot) {
159
+ const dependencies = productionDependencies(packageRoot);
160
+ const files = [];
161
+ assertSafeDirectory(targetRoot);
162
+ assertSafeDirectory(join(targetRoot, 'node_modules'));
163
+ for (const dependency of dependencies) {
164
+ const target = join(targetRoot, dependency.target);
165
+ // Check every ancestor before mkdir/copy so a nested/scoped symlink cannot redirect writes.
166
+ let directory = targetRoot;
167
+ for (const part of dependency.target.split(sep)) {
168
+ directory = join(directory, part);
169
+ assertSafeDirectory(directory);
170
+ }
171
+ files.push(...copyRuntimeTree(dependency.source, target, targetRoot));
172
+ }
173
+ return files;
174
+ }
175
+ //# sourceMappingURL=production-dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"production-dependencies.js","sourceRoot":"","sources":["../src/production-dependencies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export interface PushNotice {
2
+ kind: 'question' | 'idle';
3
+ eventKey: string;
4
+ title: string;
5
+ message: string;
6
+ occurredAt: string;
7
+ }
8
+ export interface BridgePushNotifierOptions {
9
+ apiUrl: string;
10
+ bridgeId: string;
11
+ getToken: () => string | Promise<string>;
12
+ getSession: (id: string) => Promise<{
13
+ name: string;
14
+ shellType?: string;
15
+ status?: string;
16
+ } | null | undefined>;
17
+ }
18
+ interface Dependencies {
19
+ fetch: typeof fetch;
20
+ now: () => number;
21
+ schedule: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
22
+ cancel: (timer: ReturnType<typeof setTimeout>) => void;
23
+ }
24
+ /** A bounded, best-effort queue. Cloud event/delivery keys provide durable dedup. */
25
+ export declare class BridgePushNotifier {
26
+ private options;
27
+ private deps;
28
+ private queue;
29
+ private running;
30
+ private stopped;
31
+ private timer?;
32
+ private inflight?;
33
+ constructor(options: BridgePushNotifierOptions, deps?: Dependencies);
34
+ notify(sessionId: string, notice: PushNotice): void;
35
+ cancelIdle(sessionId: string): void;
36
+ stop(): void;
37
+ private wake;
38
+ private drain;
39
+ }
40
+ export {};
@@ -0,0 +1,126 @@
1
+ const defaults = { fetch: (...args) => fetch(...args), now: Date.now,
2
+ schedule: (fn, ms) => setTimeout(fn, ms), cancel: clearTimeout };
3
+ /** A bounded, best-effort queue. Cloud event/delivery keys provide durable dedup. */
4
+ export class BridgePushNotifier {
5
+ options;
6
+ deps;
7
+ queue = new Map();
8
+ running = false;
9
+ stopped = false;
10
+ timer;
11
+ inflight;
12
+ constructor(options, deps = defaults) {
13
+ this.options = options;
14
+ this.deps = deps;
15
+ }
16
+ notify(sessionId, notice) {
17
+ if (this.stopped || !/^[a-zA-Z0-9_-]+$/.test(sessionId))
18
+ return;
19
+ const key = `${sessionId}:${notice.eventKey}`;
20
+ if (this.queue.has(key) || this.queue.size >= 100)
21
+ return;
22
+ this.queue.set(key, { sessionId, notice, expires: this.deps.now() + 300_000, attempt: 0, nextAttemptAt: this.deps.now(), canceled: false });
23
+ this.wake();
24
+ }
25
+ cancelIdle(sessionId) {
26
+ for (const [key, item] of this.queue) {
27
+ if (item.sessionId !== sessionId || item.notice.kind !== 'idle')
28
+ continue;
29
+ item.canceled = true;
30
+ this.queue.delete(key);
31
+ if (this.inflight?.item === item)
32
+ this.inflight.controller.abort();
33
+ }
34
+ this.wake();
35
+ }
36
+ stop() {
37
+ this.stopped = true;
38
+ for (const item of this.queue.values())
39
+ item.canceled = true;
40
+ this.queue.clear();
41
+ if (this.timer)
42
+ this.deps.cancel(this.timer);
43
+ this.timer = undefined;
44
+ this.inflight?.controller.abort();
45
+ }
46
+ wake() {
47
+ if (this.stopped || this.running)
48
+ return;
49
+ if (this.timer)
50
+ this.deps.cancel(this.timer);
51
+ this.timer = undefined;
52
+ void this.drain();
53
+ }
54
+ async drain() {
55
+ if (this.running || this.stopped)
56
+ return;
57
+ this.running = true;
58
+ try {
59
+ while (this.queue.size && !this.stopped) {
60
+ for (const [key, item] of this.queue)
61
+ if (this.deps.now() >= item.expires)
62
+ this.queue.delete(key);
63
+ if (!this.queue.size)
64
+ return;
65
+ const entry = Array.from(this.queue.entries()).find(([, item]) => item.nextAttemptAt <= this.deps.now());
66
+ if (!entry) {
67
+ const next = Math.min(...Array.from(this.queue.values(), (item) => Math.min(item.nextAttemptAt, item.expires)));
68
+ this.timer = this.deps.schedule(() => { this.timer = undefined; void this.drain(); }, Math.max(0, next - this.deps.now()));
69
+ return;
70
+ }
71
+ const [key, item] = entry;
72
+ const current = () => !this.stopped && !item.canceled && this.queue.get(key) === item;
73
+ let transient = false;
74
+ try {
75
+ const session = await this.options.getSession(item.sessionId);
76
+ if (!current())
77
+ continue;
78
+ if (!session || session.shellType === 'shell' || (session.status !== undefined && session.status !== 'running')) {
79
+ this.queue.delete(key);
80
+ continue;
81
+ }
82
+ const token = await this.options.getToken();
83
+ if (!current())
84
+ continue;
85
+ if (this.deps.now() >= item.expires) {
86
+ this.queue.delete(key);
87
+ continue;
88
+ }
89
+ const controller = new AbortController();
90
+ this.inflight = { item, controller };
91
+ const timeout = this.deps.schedule(() => controller.abort(), Math.min(10_000, item.expires - this.deps.now()));
92
+ try {
93
+ if (!current())
94
+ continue;
95
+ const response = await this.deps.fetch(`${this.options.apiUrl.replace(/\/$/, '')}/api/push/events`, {
96
+ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
97
+ body: JSON.stringify({ ...item.notice, bridgeId: this.options.bridgeId, sessionId: item.sessionId, sessionName: session.name }),
98
+ signal: controller.signal,
99
+ });
100
+ transient = response.status === 408 || response.status === 429 || response.status >= 500;
101
+ }
102
+ finally {
103
+ this.deps.cancel(timeout);
104
+ if (this.inflight?.item === item)
105
+ this.inflight = undefined;
106
+ }
107
+ }
108
+ catch {
109
+ transient = true;
110
+ }
111
+ if (!current())
112
+ continue;
113
+ if (!transient || this.deps.now() >= item.expires) {
114
+ this.queue.delete(key);
115
+ continue;
116
+ }
117
+ const wait = Math.min([5_000, 15_000, 60_000][Math.min(item.attempt++, 2)], item.expires - this.deps.now());
118
+ item.nextAttemptAt = this.deps.now() + wait;
119
+ }
120
+ }
121
+ finally {
122
+ this.running = false;
123
+ }
124
+ }
125
+ }
126
+ //# sourceMappingURL=push-notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-notifier.js","sourceRoot":"","sources":["../src/push-notifier.ts"],"names":[],"mappings":"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"}